mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
67aaac84b6
4
CHANGES
4
CHANGES
|
@ -61,6 +61,7 @@ Bugfixes:
|
||||||
- GBA BIOS: Fix BitUnPack final byte
|
- GBA BIOS: Fix BitUnPack final byte
|
||||||
- GB I/O: DMA register is R/W
|
- GB I/O: DMA register is R/W
|
||||||
- GB Video: Fix SCX timing
|
- GB Video: Fix SCX timing
|
||||||
|
- GBA Video: Improve sprite cycle counting (fixes mgba.io/i/1126)
|
||||||
Misc:
|
Misc:
|
||||||
- GBA Timer: Use global cycles for timers
|
- GBA Timer: Use global cycles for timers
|
||||||
- GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722)
|
- GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722)
|
||||||
|
@ -88,6 +89,9 @@ Misc:
|
||||||
- GB: Fix VRAM/palette locking (fixes mgba.io/i/1109)
|
- GB: Fix VRAM/palette locking (fixes mgba.io/i/1109)
|
||||||
- GB Video: Darken colors in GBA mode
|
- GB Video: Darken colors in GBA mode
|
||||||
- FFmpeg: Support libswresample (fixes mgba.io/i/1120, mgba.io/b/123)
|
- FFmpeg: Support libswresample (fixes mgba.io/i/1120, mgba.io/b/123)
|
||||||
|
- FFmpeg: Support lossless h.264 encoding
|
||||||
|
- Feature: Added loading savestates from command line
|
||||||
|
- Qt: Allow pausing game at load (fixes mgba.io/i/1129)
|
||||||
|
|
||||||
0.6.3: (2017-04-14)
|
0.6.3: (2017-04-14)
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
|
|
|
@ -521,17 +521,17 @@ if(USE_FFMPEG)
|
||||||
list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/ffmpeg/ffmpeg-encoder.c")
|
list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/ffmpeg/ffmpeg-encoder.c")
|
||||||
string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION})
|
string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION})
|
||||||
string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION})
|
string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION})
|
||||||
string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION})
|
|
||||||
string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION})
|
string(REGEX MATCH "^[0-9]+" LIBAVUTIL_VERSION_MAJOR ${libavutil_VERSION})
|
||||||
string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION})
|
|
||||||
string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION})
|
string(REGEX MATCH "^[0-9]+" LIBSWSCALE_VERSION_MAJOR ${libswscale_VERSION})
|
||||||
math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1")
|
|
||||||
list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES})
|
list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVRESAMPLE_LIBRARIES} ${LIBAVUTIL_LIBRARIES} ${LIBSWSCALE_LIBRARIES} ${LIBSWRESAMPLE_LIBRARIES})
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg-extra${LIBAVCODEC_VERSION_MAJOR}")
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg-extra${LIBAVCODEC_VERSION_MAJOR}")
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}")
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}")
|
||||||
if(USE_LIBSWRESAMPLE)
|
if(USE_LIBSWRESAMPLE)
|
||||||
|
string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION})
|
||||||
|
math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1")
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libswresample${LIBSWRESAMPLE_VERSION_DEBIAN}|libswresample-ffmpeg${LIBSWRESAMPLE_VERSION_DEBIAN}")
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libswresample${LIBSWRESAMPLE_VERSION_DEBIAN}|libswresample-ffmpeg${LIBSWRESAMPLE_VERSION_DEBIAN}")
|
||||||
else()
|
else()
|
||||||
|
string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION})
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavresample${LIBAVRESAMPLE_VERSION_MAJOR}|libavresample-ffmpeg${LIBAVRESAMPLE_VERSION_MAJOR}")
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavresample${LIBAVRESAMPLE_VERSION_MAJOR}|libavresample-ffmpeg${LIBAVRESAMPLE_VERSION_MAJOR}")
|
||||||
endif()
|
endif()
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavutil${LIBAVUTIL_VERSION_MAJOR}|libavutil-ffmpeg${LIBAVUTIL_VERSION_MAJOR}")
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavutil${LIBAVUTIL_VERSION_MAJOR}|libavutil-ffmpeg${LIBAVUTIL_VERSION_MAJOR}")
|
||||||
|
|
|
@ -18,7 +18,7 @@ struct mArguments {
|
||||||
char* fname;
|
char* fname;
|
||||||
char* patch;
|
char* patch;
|
||||||
char* cheatsFile;
|
char* cheatsFile;
|
||||||
char* movie;
|
char* savestate;
|
||||||
char* bios;
|
char* bios;
|
||||||
int logLevel;
|
int logLevel;
|
||||||
int frameskip;
|
int frameskip;
|
||||||
|
|
|
@ -83,6 +83,14 @@
|
||||||
#cmakedefine USE_LIBAV
|
#cmakedefine USE_LIBAV
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef USE_LIBAVRESAMPLE
|
||||||
|
#cmakedefine USE_LIBAVRESAMPLE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef USE_LIBSWRESAMPLE
|
||||||
|
#cmakedefine USE_LIBSWRESAMPLE
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef USE_LIBZIP
|
#ifndef USE_LIBZIP
|
||||||
#cmakedefine USE_LIBZIP
|
#cmakedefine USE_LIBZIP
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -201,16 +201,19 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
|
||||||
while (impl->state > THREAD_MAX_RUNNING && impl->state < THREAD_EXITING) {
|
while (impl->state > THREAD_MAX_RUNNING && impl->state < THREAD_EXITING) {
|
||||||
deferred = impl->state;
|
deferred = impl->state;
|
||||||
|
|
||||||
if (impl->state == THREAD_INTERRUPTING) {
|
switch (deferred) {
|
||||||
|
case THREAD_INTERRUPTING:
|
||||||
impl->state = THREAD_INTERRUPTED;
|
impl->state = THREAD_INTERRUPTED;
|
||||||
ConditionWake(&impl->stateCond);
|
ConditionWake(&impl->stateCond);
|
||||||
}
|
break;
|
||||||
|
case THREAD_PAUSING:
|
||||||
if (impl->state == THREAD_PAUSING) {
|
|
||||||
impl->state = THREAD_PAUSED;
|
impl->state = THREAD_PAUSED;
|
||||||
}
|
break;
|
||||||
if (impl->state == THREAD_RESETING) {
|
case THREAD_RESETING:
|
||||||
impl->state = THREAD_RUNNING;
|
impl->state = THREAD_RUNNING;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deferred >= THREAD_MIN_DEFERRED && deferred <= THREAD_MAX_DEFERRED) {
|
if (deferred >= THREAD_MIN_DEFERRED && deferred <= THREAD_MAX_DEFERRED) {
|
||||||
|
@ -218,6 +221,9 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
deferred = impl->state;
|
deferred = impl->state;
|
||||||
|
if (deferred == THREAD_INTERRUPTED) {
|
||||||
|
deferred = impl->savedState;
|
||||||
|
}
|
||||||
while (impl->state >= THREAD_WAITING && impl->state <= THREAD_MAX_WAITING) {
|
while (impl->state >= THREAD_WAITING && impl->state <= THREAD_MAX_WAITING) {
|
||||||
ConditionWait(&impl->stateCond, &impl->stateMutex);
|
ConditionWait(&impl->stateCond, &impl->stateMutex);
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ static const struct option _options[] = {
|
||||||
#endif
|
#endif
|
||||||
{ "help", no_argument, 0, 'h' },
|
{ "help", no_argument, 0, 'h' },
|
||||||
{ "log-level", required_argument, 0, 'l' },
|
{ "log-level", required_argument, 0, 'l' },
|
||||||
{ "movie", required_argument, 0, 'v' },
|
{ "savestate", required_argument, 0, 't' },
|
||||||
{ "patch", required_argument, 0, 'p' },
|
{ "patch", required_argument, 0, 'p' },
|
||||||
{ "version", no_argument, 0, '\0' },
|
{ "version", no_argument, 0, '\0' },
|
||||||
{ 0, 0, 0, 0 }
|
{ 0, 0, 0, 0 }
|
||||||
|
@ -68,7 +68,7 @@ static void _tableApply(const char* key, void* value, void* user) {
|
||||||
bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparser) {
|
bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparser) {
|
||||||
int ch;
|
int ch;
|
||||||
char options[64] =
|
char options[64] =
|
||||||
"b:c:C:hl:p:s:v:"
|
"b:c:C:hl:p:s:t:"
|
||||||
#ifdef USE_EDITLINE
|
#ifdef USE_EDITLINE
|
||||||
"d"
|
"d"
|
||||||
#endif
|
#endif
|
||||||
|
@ -132,8 +132,8 @@ bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct
|
||||||
case 's':
|
case 's':
|
||||||
args->frameskip = atoi(optarg);
|
args->frameskip = atoi(optarg);
|
||||||
break;
|
break;
|
||||||
case 'v':
|
case 't':
|
||||||
args->movie = strdup(optarg);
|
args->savestate = strdup(optarg);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (subparser) {
|
if (subparser) {
|
||||||
|
@ -179,8 +179,8 @@ void freeArguments(struct mArguments* args) {
|
||||||
free(args->patch);
|
free(args->patch);
|
||||||
args->patch = 0;
|
args->patch = 0;
|
||||||
|
|
||||||
free(args->movie);
|
free(args->savestate);
|
||||||
args->movie = 0;
|
args->savestate = 0;
|
||||||
|
|
||||||
free(args->cheatsFile);
|
free(args->cheatsFile);
|
||||||
args->cheatsFile = 0;
|
args->cheatsFile = 0;
|
||||||
|
@ -244,7 +244,7 @@ void usage(const char* arg0, const char* extraOptions) {
|
||||||
puts(" -g, --gdb Start GDB session (default port 2345)");
|
puts(" -g, --gdb Start GDB session (default port 2345)");
|
||||||
#endif
|
#endif
|
||||||
puts(" -l, --log-level N Log level mask");
|
puts(" -l, --log-level N Log level mask");
|
||||||
puts(" -v, --movie FILE Play back a movie of recorded input");
|
puts(" -t, --savestate FILE Load savestate when starting");
|
||||||
puts(" -p, --patch FILE Apply a specified patch file when running");
|
puts(" -p, --patch FILE Apply a specified patch file when running");
|
||||||
puts(" -s, --frameskip N Skip every N frames");
|
puts(" -s, --frameskip N Skip every N frames");
|
||||||
puts(" --version Print version and exit");
|
puts(" --version Print version and exit");
|
||||||
|
|
|
@ -318,6 +318,15 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
||||||
encoder->video->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
encoder->video->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (encoder->video->codec->id == AV_CODEC_ID_H264 &&
|
||||||
|
(strcasecmp(encoder->containerFormat, "mp4") ||
|
||||||
|
strcasecmp(encoder->containerFormat, "m4v") ||
|
||||||
|
strcasecmp(encoder->containerFormat, "mov"))) {
|
||||||
|
// QuickTime and a few other things require YUV420
|
||||||
|
encoder->video->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp(vcodec->name, "libx264") == 0) {
|
if (strcmp(vcodec->name, "libx264") == 0) {
|
||||||
// Try to adaptively figure out when you can use a slower encoder
|
// Try to adaptively figure out when you can use a slower encoder
|
||||||
if (encoder->width * encoder->height > 1000000) {
|
if (encoder->width * encoder->height > 1000000) {
|
||||||
|
@ -327,16 +336,12 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
||||||
} else {
|
} else {
|
||||||
av_opt_set(encoder->video->priv_data, "preset", "faster", 0);
|
av_opt_set(encoder->video->priv_data, "preset", "faster", 0);
|
||||||
}
|
}
|
||||||
av_opt_set(encoder->video->priv_data, "tune", "zerolatency", 0);
|
if (encoder->videoBitrate == 0) {
|
||||||
|
av_opt_set(encoder->video->priv_data, "crf", "0", 0);
|
||||||
|
encoder->video->pix_fmt = AV_PIX_FMT_YUV444P;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (encoder->video->codec->id == AV_CODEC_ID_H264 &&
|
|
||||||
(strcasecmp(encoder->containerFormat, "mp4") ||
|
|
||||||
strcasecmp(encoder->containerFormat, "m4v") ||
|
|
||||||
strcasecmp(encoder->containerFormat, "mov"))) {
|
|
||||||
// QuickTime and a few other things require YUV420
|
|
||||||
encoder->video->pix_fmt = AV_PIX_FMT_YUV420P;
|
|
||||||
}
|
|
||||||
avcodec_open2(encoder->video, vcodec, 0);
|
avcodec_open2(encoder->video, vcodec, 0);
|
||||||
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
#if LIBAVCODEC_VERSION_MAJOR >= 55
|
||||||
encoder->videoFrame = av_frame_alloc();
|
encoder->videoFrame = av_frame_alloc();
|
||||||
|
|
|
@ -446,6 +446,7 @@ void _GBMBC2(struct GB* gb, uint16_t address, uint8_t value) {
|
||||||
address &= 0x1FF;
|
address &= 0x1FF;
|
||||||
memory->sramBank[(address >> 1)] &= 0xF0 >> shift;
|
memory->sramBank[(address >> 1)] &= 0xF0 >> shift;
|
||||||
memory->sramBank[(address >> 1)] |= (value & 0xF) << shift;
|
memory->sramBank[(address >> 1)] |= (value & 0xF) << shift;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// TODO
|
// TODO
|
||||||
mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value);
|
mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value);
|
||||||
|
@ -640,6 +641,7 @@ void _GBMBC7(struct GB* gb, uint16_t address, uint8_t value) {
|
||||||
break;
|
break;
|
||||||
case 0x5:
|
case 0x5:
|
||||||
_GBMBC7Write(&gb->memory, address, value);
|
_GBMBC7Write(&gb->memory, address, value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// TODO
|
// TODO
|
||||||
mLOG(GB_MBC, STUB, "MBC7 unknown address: %04X:%02X", address, value);
|
mLOG(GB_MBC, STUB, "MBC7 unknown address: %04X:%02X", address, value);
|
||||||
|
|
|
@ -294,7 +294,6 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
||||||
if (GBAObjAttributesAIsTransformed(sprite->a)) {
|
if (GBAObjAttributesAIsTransformed(sprite->a)) {
|
||||||
int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a);
|
int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a);
|
||||||
int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a);
|
int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a);
|
||||||
renderer->spriteCyclesRemaining -= 10;
|
|
||||||
struct GBAOAMMatrix mat;
|
struct GBAOAMMatrix mat;
|
||||||
LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a);
|
LOAD_16(mat.a, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].a);
|
||||||
LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b);
|
LOAD_16(mat.b, 0, &renderer->d.oam->mat[GBAObjAttributesBGetMatIndex(sprite->b)].b);
|
||||||
|
@ -354,6 +353,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
||||||
if (outX < start || outX >= condition) {
|
if (outX < start || outX >= condition) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
renderer->spriteCyclesRemaining -= 10;
|
||||||
|
|
||||||
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) {
|
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) {
|
||||||
int alpha = GBAObjAttributesCGetPalette(sprite->c);
|
int alpha = GBAObjAttributesCGetPalette(sprite->c);
|
||||||
|
@ -393,7 +393,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
||||||
SPRITE_TRANSFORMED_LOOP(256, NORMAL);
|
SPRITE_TRANSFORMED_LOOP(256, NORMAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (x + totalWidth > renderer->masterEnd) {
|
if (end == renderer->masterEnd && x + totalWidth > renderer->masterEnd) {
|
||||||
renderer->spriteCyclesRemaining -= (x + totalWidth - renderer->masterEnd) * 2;
|
renderer->spriteCyclesRemaining -= (x + totalWidth - renderer->masterEnd) * 2;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -485,7 +485,7 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if (x + width > renderer->masterEnd) {
|
if (end = renderer->masterEnd && x + width > renderer->masterEnd) {
|
||||||
renderer->spriteCyclesRemaining -= x + width - renderer->masterEnd;
|
renderer->spriteCyclesRemaining -= x + width - renderer->masterEnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -470,6 +470,26 @@ void CoreController::loadState(int slot) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CoreController::loadState(const QString& path) {
|
||||||
|
m_statePath = path;
|
||||||
|
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||||
|
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||||
|
VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
|
||||||
|
if (!vf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!controller->m_backupLoadState.isOpen()) {
|
||||||
|
controller->m_backupLoadState = VFileMemChunk(nullptr, 0);
|
||||||
|
}
|
||||||
|
mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
|
||||||
|
if (mCoreLoadStateNamed(context->core, vf, controller->m_loadStateFlags)) {
|
||||||
|
emit controller->frameAvailable();
|
||||||
|
emit controller->stateLoaded();
|
||||||
|
}
|
||||||
|
vf->close(vf);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void CoreController::saveState(int slot) {
|
void CoreController::saveState(int slot) {
|
||||||
if (slot > 0) {
|
if (slot > 0) {
|
||||||
m_stateSlot = slot;
|
m_stateSlot = slot;
|
||||||
|
@ -486,6 +506,25 @@ void CoreController::saveState(int slot) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CoreController::saveState(const QString& path) {
|
||||||
|
m_statePath = path;
|
||||||
|
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||||
|
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||||
|
VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
|
||||||
|
if (vf) {
|
||||||
|
controller->m_backupSaveState.resize(vf->size(vf));
|
||||||
|
vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
|
||||||
|
vf->close(vf);
|
||||||
|
}
|
||||||
|
vf = VFileDevice::open(controller->m_statePath, O_WRONLY | O_CREAT | O_TRUNC);
|
||||||
|
if (!vf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mCoreSaveStateNamed(context->core, vf, controller->m_saveStateFlags);
|
||||||
|
vf->close(vf);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void CoreController::loadBackupState() {
|
void CoreController::loadBackupState() {
|
||||||
if (!m_backupLoadState.isOpen()) {
|
if (!m_backupLoadState.isOpen()) {
|
||||||
return;
|
return;
|
||||||
|
@ -559,7 +598,7 @@ void CoreController::replaceGame(const QString& path) {
|
||||||
QString fname = info.canonicalFilePath();
|
QString fname = info.canonicalFilePath();
|
||||||
Interrupter interrupter(this);
|
Interrupter interrupter(this);
|
||||||
mDirectorySetDetachBase(&m_threadContext.core->dirs);
|
mDirectorySetDetachBase(&m_threadContext.core->dirs);
|
||||||
mCoreLoadFile(m_threadContext.core, fname.toLocal8Bit().constData());
|
mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::yankPak() {
|
void CoreController::yankPak() {
|
||||||
|
|
|
@ -104,7 +104,9 @@ public slots:
|
||||||
void forceFastForward(bool);
|
void forceFastForward(bool);
|
||||||
|
|
||||||
void loadState(int slot = 0);
|
void loadState(int slot = 0);
|
||||||
|
void loadState(const QString& path);
|
||||||
void saveState(int slot = 0);
|
void saveState(int slot = 0);
|
||||||
|
void saveState(const QString& path);
|
||||||
void loadBackupState();
|
void loadBackupState();
|
||||||
void saveBackupState();
|
void saveBackupState();
|
||||||
|
|
||||||
|
@ -185,6 +187,7 @@ private:
|
||||||
VFileDevice m_backupLoadState;
|
VFileDevice m_backupLoadState;
|
||||||
QByteArray m_backupSaveState{nullptr};
|
QByteArray m_backupSaveState{nullptr};
|
||||||
int m_stateSlot = 1;
|
int m_stateSlot = 1;
|
||||||
|
QString m_statePath;
|
||||||
int m_loadStateFlags;
|
int m_loadStateFlags;
|
||||||
int m_saveStateFlags;
|
int m_saveStateFlags;
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ VideoView::VideoView(QWidget* parent)
|
||||||
|
|
||||||
setPreset({
|
setPreset({
|
||||||
.container = "MKV",
|
.container = "MKV",
|
||||||
.vcodec = "PNG",
|
.vcodec = "h.264",
|
||||||
.acodec = "FLAC",
|
.acodec = "FLAC",
|
||||||
.vbr = 0,
|
.vbr = 0,
|
||||||
.abr = 0,
|
.abr = 0,
|
||||||
|
@ -179,7 +179,7 @@ void VideoView::updatePresets() {
|
||||||
if (m_nativeWidth && m_nativeHeight) {
|
if (m_nativeWidth && m_nativeHeight) {
|
||||||
addPreset(m_ui.presetLossless, {
|
addPreset(m_ui.presetLossless, {
|
||||||
.container = "MKV",
|
.container = "MKV",
|
||||||
.vcodec = "PNG",
|
.vcodec = "h.264",
|
||||||
.acodec = "FLAC",
|
.acodec = "FLAC",
|
||||||
.vbr = 0,
|
.vbr = 0,
|
||||||
.abr = 0,
|
.abr = 0,
|
||||||
|
|
|
@ -254,11 +254,6 @@
|
||||||
<property name="editable">
|
<property name="editable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
|
||||||
<property name="text">
|
|
||||||
<string>PNG</string>
|
|
||||||
</property>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>h.264</string>
|
<string>h.264</string>
|
||||||
|
@ -353,9 +348,6 @@
|
||||||
<property name="suffix">
|
<property name="suffix">
|
||||||
<string/>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimum">
|
|
||||||
<number>200</number>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
<property name="maximum">
|
||||||
<number>10000</number>
|
<number>10000</number>
|
||||||
</property>
|
</property>
|
||||||
|
@ -523,7 +515,7 @@
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
<buttongroups>
|
<buttongroups>
|
||||||
<buttongroup name="presets"/>
|
|
||||||
<buttongroup name="resolutions"/>
|
<buttongroup name="resolutions"/>
|
||||||
|
<buttongroup name="presets"/>
|
||||||
</buttongroups>
|
</buttongroups>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
|
@ -200,6 +200,14 @@ Window::~Window() {
|
||||||
void Window::argumentsPassed(mArguments* args) {
|
void Window::argumentsPassed(mArguments* args) {
|
||||||
loadConfig();
|
loadConfig();
|
||||||
|
|
||||||
|
if (args->patch) {
|
||||||
|
m_pendingPatch = args->patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args->savestate) {
|
||||||
|
m_pendingState = args->savestate;
|
||||||
|
}
|
||||||
|
|
||||||
if (args->fname) {
|
if (args->fname) {
|
||||||
setController(m_manager->loadGame(args->fname), args->fname);
|
setController(m_manager->loadGame(args->fname), args->fname);
|
||||||
}
|
}
|
||||||
|
@ -1252,12 +1260,15 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
pause->setCheckable(true);
|
pause->setCheckable(true);
|
||||||
pause->setShortcut(tr("Ctrl+P"));
|
pause->setShortcut(tr("Ctrl+P"));
|
||||||
connect(pause, &QAction::triggered, [this](bool paused) {
|
connect(pause, &QAction::triggered, [this](bool paused) {
|
||||||
m_controller->setPaused(paused);
|
if (m_controller) {
|
||||||
|
m_controller->setPaused(paused);
|
||||||
|
} else {
|
||||||
|
m_pendingPause = paused;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
connect(this, &Window::paused, [pause](bool paused) {
|
connect(this, &Window::paused, [pause](bool paused) {
|
||||||
pause->setChecked(paused);
|
pause->setChecked(paused);
|
||||||
});
|
});
|
||||||
m_gameActions.append(pause);
|
|
||||||
addControlledAction(emulationMenu, pause, "pause");
|
addControlledAction(emulationMenu, pause, "pause");
|
||||||
|
|
||||||
QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu);
|
QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu);
|
||||||
|
@ -1911,6 +1922,16 @@ void Window::setController(CoreController* controller, const QString& fname) {
|
||||||
|
|
||||||
m_controller->loadConfig(m_config);
|
m_controller->loadConfig(m_config);
|
||||||
m_controller->start();
|
m_controller->start();
|
||||||
|
|
||||||
|
if (!m_pendingState.isEmpty()) {
|
||||||
|
m_controller->loadState(m_pendingState);
|
||||||
|
m_pendingState = QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pendingPause) {
|
||||||
|
m_controller->setPaused(true);
|
||||||
|
m_pendingPause = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowBackground::WindowBackground(QWidget* parent)
|
WindowBackground::WindowBackground(QWidget* parent)
|
||||||
|
|
|
@ -202,6 +202,8 @@ private:
|
||||||
bool m_autoresume = false;
|
bool m_autoresume = false;
|
||||||
bool m_wasOpened = false;
|
bool m_wasOpened = false;
|
||||||
QString m_pendingPatch;
|
QString m_pendingPatch;
|
||||||
|
QString m_pendingState;
|
||||||
|
bool m_pendingPause = false;
|
||||||
|
|
||||||
bool m_hitUnimplementedBiosCall;
|
bool m_hitUnimplementedBiosCall;
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include <mgba/core/core.h>
|
#include <mgba/core/core.h>
|
||||||
#include <mgba/core/config.h>
|
#include <mgba/core/config.h>
|
||||||
#include <mgba/core/input.h>
|
#include <mgba/core/input.h>
|
||||||
|
#include <mgba/core/serialize.h>
|
||||||
#include <mgba/core/thread.h>
|
#include <mgba/core/thread.h>
|
||||||
#include <mgba/internal/gba/input.h>
|
#include <mgba/internal/gba/input.h>
|
||||||
|
|
||||||
|
@ -43,6 +44,12 @@ static void mSDLDeinit(struct mSDLRenderer* renderer);
|
||||||
|
|
||||||
static int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args);
|
static int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args);
|
||||||
|
|
||||||
|
static struct VFile* _state = NULL;
|
||||||
|
|
||||||
|
static void _loadState(struct mCoreThread* thread) {
|
||||||
|
mCoreLoadStateNamed(thread->core, _state, SAVESTATE_RTC);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
struct mSDLRenderer renderer = {0};
|
struct mSDLRenderer renderer = {0};
|
||||||
|
|
||||||
|
@ -232,6 +239,15 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) {
|
||||||
mSDLSuspendScreensaver(&renderer->events);
|
mSDLSuspendScreensaver(&renderer->events);
|
||||||
#endif
|
#endif
|
||||||
if (mSDLInitAudio(&renderer->audio, &thread)) {
|
if (mSDLInitAudio(&renderer->audio, &thread)) {
|
||||||
|
if (args->savestate) {
|
||||||
|
struct VFile* state = VFileOpen(args->savestate, O_RDONLY);
|
||||||
|
if (state) {
|
||||||
|
_state = state;
|
||||||
|
mCoreThreadRunFunction(&thread, _loadState);
|
||||||
|
_state = NULL;
|
||||||
|
state->close(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
renderer->runloop(renderer, &thread);
|
renderer->runloop(renderer, &thread);
|
||||||
mSDLPauseAudio(&renderer->audio);
|
mSDLPauseAudio(&renderer->audio);
|
||||||
if (mCoreThreadHasCrashed(&thread)) {
|
if (mCoreThreadHasCrashed(&thread)) {
|
||||||
|
|
|
@ -407,7 +407,7 @@ static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer*
|
||||||
if (!event->keysym.mod) {
|
if (!event->keysym.mod) {
|
||||||
key = mInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.sym);
|
key = mInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.sym);
|
||||||
}
|
}
|
||||||
if (key != -1 && !event->repeat) {
|
if (key != -1) {
|
||||||
mCoreThreadInterrupt(context);
|
mCoreThreadInterrupt(context);
|
||||||
if (event->type == SDL_KEYDOWN) {
|
if (event->type == SDL_KEYDOWN) {
|
||||||
context->core->addKeys(context->core, 1 << key);
|
context->core->addKeys(context->core, 1 << key);
|
||||||
|
@ -485,7 +485,7 @@ static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer*
|
||||||
case SDLK_F8:
|
case SDLK_F8:
|
||||||
case SDLK_F9:
|
case SDLK_F9:
|
||||||
mCoreThreadInterrupt(context);
|
mCoreThreadInterrupt(context);
|
||||||
mCoreSaveState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SAVEDATA | SAVESTATE_SCREENSHOT);
|
mCoreSaveState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SAVEDATA | SAVESTATE_SCREENSHOT | SAVESTATE_RTC);
|
||||||
mCoreThreadContinue(context);
|
mCoreThreadContinue(context);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -503,7 +503,7 @@ static void _mSDLHandleKeypress(struct mCoreThread* context, struct mSDLPlayer*
|
||||||
case SDLK_F8:
|
case SDLK_F8:
|
||||||
case SDLK_F9:
|
case SDLK_F9:
|
||||||
mCoreThreadInterrupt(context);
|
mCoreThreadInterrupt(context);
|
||||||
mCoreLoadState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SCREENSHOT);
|
mCoreLoadState(context->core, event->keysym.sym - SDLK_F1 + 1, SAVESTATE_SCREENSHOT | SAVESTATE_RTC);
|
||||||
mCoreThreadContinue(context);
|
mCoreThreadContinue(context);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -26,14 +26,12 @@
|
||||||
" -F FRAMES Run for the specified number of FRAMES before exiting\n" \
|
" -F FRAMES Run for the specified number of FRAMES before exiting\n" \
|
||||||
" -N Disable video rendering entirely\n" \
|
" -N Disable video rendering entirely\n" \
|
||||||
" -O OFFSET Offset to apply savestate overlay\n" \
|
" -O OFFSET Offset to apply savestate overlay\n" \
|
||||||
" -S FILE Load a savestate when starting the test\n" \
|
|
||||||
" -V FILE Overlay a second savestate over the loaded savestate\n" \
|
" -V FILE Overlay a second savestate over the loaded savestate\n" \
|
||||||
|
|
||||||
struct FuzzOpts {
|
struct FuzzOpts {
|
||||||
bool noVideo;
|
bool noVideo;
|
||||||
int frames;
|
int frames;
|
||||||
size_t overlayOffset;
|
size_t overlayOffset;
|
||||||
char* savestate;
|
|
||||||
char* ssOverlay;
|
char* ssOverlay;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -108,9 +106,8 @@ int main(int argc, char** argv) {
|
||||||
struct VFile* savestateOverlay = 0;
|
struct VFile* savestateOverlay = 0;
|
||||||
size_t overlayOffset;
|
size_t overlayOffset;
|
||||||
|
|
||||||
if (fuzzOpts.savestate) {
|
if (args.savestate) {
|
||||||
savestate = VFileOpen(fuzzOpts.savestate, O_RDONLY);
|
savestate = VFileOpen(args.savestate, O_RDONLY);
|
||||||
free(fuzzOpts.savestate);
|
|
||||||
}
|
}
|
||||||
if (fuzzOpts.ssOverlay) {
|
if (fuzzOpts.ssOverlay) {
|
||||||
overlayOffset = fuzzOpts.overlayOffset;
|
overlayOffset = fuzzOpts.overlayOffset;
|
||||||
|
@ -200,9 +197,6 @@ static bool _parseFuzzOpts(struct mSubParser* parser, int option, const char* ar
|
||||||
case 'O':
|
case 'O':
|
||||||
opts->overlayOffset = strtoul(arg, 0, 10);
|
opts->overlayOffset = strtoul(arg, 0, 10);
|
||||||
return !errno;
|
return !errno;
|
||||||
case 'S':
|
|
||||||
opts->savestate = strdup(arg);
|
|
||||||
return true;
|
|
||||||
case 'V':
|
case 'V':
|
||||||
opts->ssOverlay = strdup(arg);
|
opts->ssOverlay = strdup(arg);
|
||||||
return true;
|
return true;
|
||||||
|
|
Loading…
Reference in New Issue