Merge branch 'master' (early part) into medusa
jump to
@@ -61,6 +61,7 @@ - GBA SIO: Fix unconnected SIOCNT for multi mode (fixes mgba.io/i/1105)
- GBA BIOS: Fix BitUnPack final byte - GB I/O: DMA register is R/W - GB Video: Fix SCX timing + - GBA Video: Improve sprite cycle counting (fixes mgba.io/i/1126) Misc: - GBA Timer: Use global cycles for timers - GBA: Extend oddly-sized ROMs to full address space (fixes mgba.io/i/722)@@ -88,6 +89,9 @@ - GB, GBA Audio: Increase max audio volume
- GB: Fix VRAM/palette locking (fixes mgba.io/i/1109) - GB Video: Darken colors in GBA mode - 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) Bugfixes:
@@ -521,17 +521,17 @@ link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWRESAMPLE_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS})
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]+" 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]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_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}) 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}") 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}") 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}") endif() 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* patch; char* cheatsFile; - char* movie; + char* savestate; char* bios; int logLevel; int frameskip;
@@ -83,6 +83,14 @@ #ifndef USE_LIBAV
#cmakedefine USE_LIBAV #endif +#ifndef USE_LIBAVRESAMPLE +#cmakedefine USE_LIBAVRESAMPLE +#endif + +#ifndef USE_LIBSWRESAMPLE +#cmakedefine USE_LIBSWRESAMPLE +#endif + #ifndef USE_LIBZIP #cmakedefine USE_LIBZIP #endif
@@ -201,16 +201,19 @@ MutexLock(&impl->stateMutex);
while (impl->state > THREAD_MAX_RUNNING && impl->state < THREAD_EXITING) { deferred = impl->state; - if (impl->state == THREAD_INTERRUPTING) { + switch (deferred) { + case THREAD_INTERRUPTING: impl->state = THREAD_INTERRUPTED; ConditionWake(&impl->stateCond); - } - - if (impl->state == THREAD_PAUSING) { + break; + case THREAD_PAUSING: impl->state = THREAD_PAUSED; - } - if (impl->state == THREAD_RESETING) { + break; + case THREAD_RESETING: impl->state = THREAD_RUNNING; + break; + default: + break; } if (deferred >= THREAD_MIN_DEFERRED && deferred <= THREAD_MAX_DEFERRED) {@@ -218,6 +221,9 @@ break;
} deferred = impl->state; + if (deferred == THREAD_INTERRUPTED) { + deferred = impl->savedState; + } while (impl->state >= THREAD_WAITING && impl->state <= THREAD_MAX_WAITING) { ConditionWait(&impl->stateCond, &impl->stateMutex);
@@ -39,7 +39,7 @@ { "gdb", no_argument, 0, 'g' },
#endif { "help", no_argument, 0, 'h' }, { "log-level", required_argument, 0, 'l' }, - { "movie", required_argument, 0, 'v' }, + { "savestate", required_argument, 0, 't' }, { "patch", required_argument, 0, 'p' }, { "version", no_argument, 0, '\0' }, { 0, 0, 0, 0 }@@ -68,7 +68,7 @@
bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct mSubParser* subparser) { int ch; char options[64] = - "b:c:C:hl:p:s:v:" + "b:c:C:hl:p:s:t:" #ifdef USE_EDITLINE "d" #endif@@ -132,8 +132,8 @@ break;
case 's': args->frameskip = atoi(optarg); break; - case 'v': - args->movie = strdup(optarg); + case 't': + args->savestate = strdup(optarg); break; default: if (subparser) {@@ -179,8 +179,8 @@
free(args->patch); args->patch = 0; - free(args->movie); - args->movie = 0; + free(args->savestate); + args->savestate = 0; free(args->cheatsFile); args->cheatsFile = 0;@@ -244,7 +244,7 @@ #ifdef USE_GDB_STUB
puts(" -g, --gdb Start GDB session (default port 2345)"); #endif 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(" -s, --frameskip N Skip every N frames"); puts(" --version Print version and exit");
@@ -318,6 +318,15 @@ #else
encoder->video->flags |= CODEC_FLAG_GLOBAL_HEADER; #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) { // Try to adaptively figure out when you can use a slower encoder if (encoder->width * encoder->height > 1000000) {@@ -327,16 +336,12 @@ av_opt_set(encoder->video->priv_data, "preset", "veryfast", 0);
} else { 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); #if LIBAVCODEC_VERSION_MAJOR >= 55 encoder->videoFrame = av_frame_alloc();
@@ -446,6 +446,7 @@ }
address &= 0x1FF; memory->sramBank[(address >> 1)] &= 0xF0 >> shift; memory->sramBank[(address >> 1)] |= (value & 0xF) << shift; + break; default: // TODO mLOG(GB_MBC, STUB, "MBC2 unknown address: %04X:%02X", address, value);@@ -640,6 +641,7 @@ }
break; case 0x5: _GBMBC7Write(&gb->memory, address, value); + break; default: // TODO mLOG(GB_MBC, STUB, "MBC7 unknown address: %04X:%02X", address, value);
@@ -294,7 +294,6 @@ uint32_t current;
if (GBAObjAttributesAIsTransformed(sprite->a)) { int totalWidth = width << GBAObjAttributesAGetDoubleSize(sprite->a); int totalHeight = height << GBAObjAttributesAGetDoubleSize(sprite->a); - renderer->spriteCyclesRemaining -= 10; struct GBAOAMMatrix mat; 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);@@ -354,6 +353,7 @@
if (outX < start || outX >= condition) { return 0; } + renderer->spriteCyclesRemaining -= 10; if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_BITMAP && renderer->bitmapStride) { int alpha = GBAObjAttributesCGetPalette(sprite->c);@@ -393,7 +393,7 @@ } else {
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; } } else {@@ -485,7 +485,7 @@ SPRITE_NORMAL_LOOP(256, NORMAL);
} } - if (x + width > renderer->masterEnd) { + if (end = renderer->masterEnd && x + width > renderer->masterEnd) { renderer->spriteCyclesRemaining -= x + width - renderer->masterEnd; } }
@@ -470,6 +470,26 @@ }
}); } +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) { if (slot > 0) { m_stateSlot = slot;@@ -483,6 +503,25 @@ vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
vf->close(vf); } mCoreSaveState(context->core, controller->m_stateSlot, controller->m_saveStateFlags); + }); +} + +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); }); }@@ -559,7 +598,7 @@ }
QString fname = info.canonicalFilePath(); Interrupter interrupter(this); mDirectorySetDetachBase(&m_threadContext.core->dirs); - mCoreLoadFile(m_threadContext.core, fname.toLocal8Bit().constData()); + mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData()); } void CoreController::yankPak() {
@@ -104,7 +104,9 @@ void setFastForward(bool);
void forceFastForward(bool); void loadState(int slot = 0); + void loadState(const QString& path); void saveState(int slot = 0); + void saveState(const QString& path); void loadBackupState(); void saveBackupState();@@ -185,6 +187,7 @@
VFileDevice m_backupLoadState; QByteArray m_backupSaveState{nullptr}; int m_stateSlot = 1; + QString m_statePath; int m_loadStateFlags; int m_saveStateFlags;
@@ -99,7 +99,7 @@ updatePresets();
setPreset({ .container = "MKV", - .vcodec = "PNG", + .vcodec = "h.264", .acodec = "FLAC", .vbr = 0, .abr = 0,@@ -179,7 +179,7 @@
if (m_nativeWidth && m_nativeHeight) { addPreset(m_ui.presetLossless, { .container = "MKV", - .vcodec = "PNG", + .vcodec = "h.264", .acodec = "FLAC", .vbr = 0, .abr = 0,
@@ -256,11 +256,6 @@ <bool>true</bool>
</property> <item> <property name="text"> - <string>PNG</string> - </property> - </item> - <item> - <property name="text"> <string>h.264</string> </property> </item>@@ -352,9 +347,6 @@ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property> <property name="suffix"> <string/> - </property> - <property name="minimum"> - <number>200</number> </property> <property name="maximum"> <number>10000</number>@@ -523,7 +515,7 @@ </tabstops>
<resources/> <connections/> <buttongroups> - <buttongroup name="presets"/> <buttongroup name="resolutions"/> + <buttongroup name="presets"/> </buttongroups> </ui>
@@ -200,6 +200,14 @@
void Window::argumentsPassed(mArguments* args) { loadConfig(); + if (args->patch) { + m_pendingPatch = args->patch; + } + + if (args->savestate) { + m_pendingState = args->savestate; + } + if (args->fname) { setController(m_manager->loadGame(args->fname), args->fname); }@@ -1252,12 +1260,15 @@ pause->setChecked(false);
pause->setCheckable(true); pause->setShortcut(tr("Ctrl+P")); 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) { pause->setChecked(paused); }); - m_gameActions.append(pause); addControlledAction(emulationMenu, pause, "pause"); QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu);@@ -1911,6 +1922,16 @@ }
m_controller->loadConfig(m_config); 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)
@@ -202,6 +202,8 @@ QTimer m_focusCheck;
bool m_autoresume = false; bool m_wasOpened = false; QString m_pendingPatch; + QString m_pendingState; + bool m_pendingPause = false; bool m_hitUnimplementedBiosCall;
@@ -25,6 +25,7 @@ #include <mgba/core/cheats.h>
#include <mgba/core/core.h> #include <mgba/core/config.h> #include <mgba/core/input.h> +#include <mgba/core/serialize.h> #include <mgba/core/thread.h> #include <mgba/internal/gba/input.h>@@ -42,6 +43,12 @@ static bool mSDLInit(struct mSDLRenderer* renderer);
static void mSDLDeinit(struct mSDLRenderer* renderer); 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) { struct mSDLRenderer renderer = {0};@@ -232,6 +239,15 @@ mSDLSetScreensaverSuspendable(&renderer->events, renderer->core->opts.suspendScreensaver);
mSDLSuspendScreensaver(&renderer->events); #endif 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); mSDLPauseAudio(&renderer->audio); if (mCoreThreadHasCrashed(&thread)) {
@@ -407,7 +407,7 @@ int key = -1;
if (!event->keysym.mod) { key = mInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.sym); } - if (key != -1 && !event->repeat) { + if (key != -1) { mCoreThreadInterrupt(context); if (event->type == SDL_KEYDOWN) { context->core->addKeys(context->core, 1 << key);@@ -485,7 +485,7 @@ case SDLK_F7:
case SDLK_F8: case SDLK_F9: 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); break; default:@@ -503,7 +503,7 @@ case SDLK_F7:
case SDLK_F8: case SDLK_F9: 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); break; default:
@@ -26,14 +26,12 @@ "\nAdditional options:\n" \
" -F FRAMES Run for the specified number of FRAMES before exiting\n" \ " -N Disable video rendering entirely\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" \ struct FuzzOpts { bool noVideo; int frames; size_t overlayOffset; - char* savestate; char* ssOverlay; };@@ -108,9 +106,8 @@ struct VFile* savestate = 0;
struct VFile* savestateOverlay = 0; size_t overlayOffset; - if (fuzzOpts.savestate) { - savestate = VFileOpen(fuzzOpts.savestate, O_RDONLY); - free(fuzzOpts.savestate); + if (args.savestate) { + savestate = VFileOpen(args.savestate, O_RDONLY); } if (fuzzOpts.ssOverlay) { overlayOffset = fuzzOpts.overlayOffset;@@ -200,9 +197,6 @@ return true;
case 'O': opts->overlayOffset = strtoul(arg, 0, 10); return !errno; - case 'S': - opts->savestate = strdup(arg); - return true; case 'V': opts->ssOverlay = strdup(arg); return true;