Core: Video log recording
jump to
@@ -26,10 +26,10 @@
enum mPlatform { PLATFORM_NONE = -1, #ifdef M_CORE_GBA - PLATFORM_GBA, + PLATFORM_GBA = 0, #endif #ifdef M_CORE_GB - PLATFORM_GB, + PLATFORM_GB = 1, #endif };@@ -40,6 +40,7 @@
struct mCoreConfig; struct mCoreSync; struct mStateExtdata; +struct mVideoLogContext; struct mCore { void* cpu; void* board;@@ -145,6 +146,9 @@ size_t (*listVideoLayers)(const struct mCore*, const struct mCoreChannelInfo**);
size_t (*listAudioChannels)(const struct mCore*, const struct mCoreChannelInfo**); void (*enableVideoLayer)(struct mCore*, size_t id, bool enable); void (*enableAudioChannel)(struct mCore*, size_t id, bool enable); + + void (*startVideoLog)(struct mCore*, struct mVideoLogContext*); + void (*endVideoLog)(struct mCore*); }; #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
@@ -27,6 +27,7 @@ uint16_t value;
uint32_t padding; }; +struct VFile; struct mVideoLogger { bool (*writeData)(struct mVideoLogger* logger, const void* data, size_t length); bool (*readData)(struct mVideoLogger* logger, void* data, size_t length, bool block);@@ -45,8 +46,40 @@
uint16_t* vram; uint16_t* oam; uint16_t* palette; + + struct VFile* vf; +}; + +struct mVideoLogChannel { + uint32_t type; + void* initialState; + size_t initialStateSize; + struct VFile* channelData; }; +struct mVideoLogContext { + void* initialState; + size_t initialStateSize; + uint32_t nChannels; + struct mVideoLogChannel channels[32]; +}; + +struct mVideoLogHeader { + char magic[4]; + uint32_t platform; + uint32_t nChannels; + uint32_t initialStatePointer; + uint32_t channelPointers[32]; +}; + +struct mVideoLogChannelHeader { + uint32_t type; + uint32_t channelInitialStatePointer; + uint32_t channelSize; + uint32_t reserved; +}; + +void mVideoLoggerRendererCreate(struct mVideoLogger* logger); void mVideoLoggerRendererInit(struct mVideoLogger* logger); void mVideoLoggerRendererDeinit(struct mVideoLogger* logger); void mVideoLoggerRendererReset(struct mVideoLogger* logger);@@ -60,6 +93,11 @@ void mVideoLoggerRendererDrawScanline(struct mVideoLogger* logger, int y);
void mVideoLoggerRendererFlush(struct mVideoLogger* logger); bool mVideoLoggerRendererRun(struct mVideoLogger* logger); + +struct mCore; +struct mVideoLogContext* mVideoLoggerCreate(struct mCore* core); +void mVideoLoggerDestroy(struct mCore* core, struct mVideoLogContext*); +void mVideoLoggerWrite(struct mCore* core, struct mVideoLogContext*, struct VFile*); CXX_GUARD_END
@@ -31,6 +31,8 @@ void (*wake)(struct GBAVideoProxyRenderer*, int y);
}; void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend); +void GBAVideoProxyRendererShim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer); +void GBAVideoProxyRendererUnshim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer); CXX_GUARD_END
@@ -5,11 +5,24 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <mgba/core/video-logger.h> +#include <mgba/core/core.h> #include <mgba-util/memory.h> +#include <mgba-util/vfs.h> + +const char mVL_MAGIC[] = "mVL\0"; + +static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length); +static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block); static inline size_t _roundUp(size_t value, int shift) { value += (1 << shift) - 1; return value >> shift; +} + +void mVideoLoggerRendererCreate(struct mVideoLogger* logger) { + logger->writeData = _writeData; + logger->readData = _readData; + logger->vf = NULL; } void mVideoLoggerRendererInit(struct mVideoLogger* logger) {@@ -134,3 +147,79 @@ }
} return true; } + +static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) { + return logger->vf->write(logger->vf, data, length) == (ssize_t) length; +} + +static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) { + return logger->vf->read(logger->vf, data, length) == (ssize_t) length || !block; +} + +struct mVideoLogContext* mVideoLoggerCreate(struct mCore* core) { + struct mVideoLogContext* context = malloc(sizeof(*context)); + core->startVideoLog(core, context); + return context; +} + +void mVideoLoggerDestroy(struct mCore* core, struct mVideoLogContext* context) { + if (core) { + core->endVideoLog(core); + } + free(context); +} + +void mVideoLoggerWrite(struct mCore* core, struct mVideoLogContext* context, struct VFile* vf) { + struct mVideoLogHeader header = {{0}}; + memcpy(header.magic, mVL_MAGIC, sizeof(mVL_MAGIC)); + + enum mPlatform platform = core->platform(core); + STORE_32LE(platform, 0, &header.platform); + STORE_32LE(context->nChannels, 0, &header.nChannels); + + ssize_t pointer = vf->seek(vf, sizeof(header), SEEK_SET); + if (context->initialStateSize) { + ssize_t written = vf->write(vf, context->initialState, context->initialStateSize); + if (written > 0) { + STORE_32LE(pointer, 0, &header.initialStatePointer); + pointer += written; + } else { + header.initialStatePointer = 0; + } + } else { + header.initialStatePointer = 0; + } + + size_t i; + for (i = 0; i < context->nChannels && i < 32; ++i) { + struct VFile* channel = context->channels[i].channelData; + void* block = channel->map(channel, channel->size(channel), MAP_READ); + + struct mVideoLogChannelHeader chHeader = {0}; + STORE_32LE(context->channels[i].type, 0, &chHeader.type); + STORE_32LE(channel->size(channel), 0, &chHeader.channelSize); + + if (context->channels[i].initialStateSize) { + ssize_t written = vf->write(vf, context->channels[i].initialState, context->channels[i].initialStateSize); + if (written > 0) { + STORE_32LE(pointer, 0, &chHeader.channelInitialStatePointer); + pointer += written; + } else { + chHeader.channelInitialStatePointer = 0; + } + } + STORE_32LE(pointer, 0, &header.channelPointers[i]); + ssize_t written = vf->write(vf, &chHeader, sizeof(chHeader)); + if (written != sizeof(chHeader)) { + continue; + } + pointer += written; + written = vf->write(vf, block, channel->size(channel)); + if (written != channel->size(channel)) { + break; + } + pointer += written; + } + vf->seek(vf, 0, SEEK_SET); + vf->write(vf, &header, sizeof(header)); +}
@@ -42,6 +42,8 @@
struct GBACore { struct mCore d; struct GBAVideoSoftwareRenderer renderer; + struct GBAVideoProxyRenderer logProxy; + struct mVideoLogContext* logContext; #ifndef DISABLE_THREADING struct GBAVideoThreadProxyRenderer threadProxy; int threadedVideo;@@ -69,6 +71,7 @@ core->debugger = NULL;
gbacore->overrides = NULL; gbacore->debuggerPlatform = NULL; gbacore->cheatDevice = NULL; + gbacore->logContext = NULL; GBACreate(gba); // TODO: Restore cheats@@ -646,6 +649,37 @@ break;
} } +static void _GBACoreStartVideoLog(struct mCore* core, struct mVideoLogContext* context) { + struct GBACore* gbacore = (struct GBACore*) core; + struct GBA* gba = core->board; + gbacore->logContext = context; + + GBAVideoProxyRendererCreate(&gbacore->logProxy, gba->video.renderer); + + context->initialStateSize = core->stateSize(core); + context->initialState = anonymousMemoryMap(context->initialStateSize); + core->saveState(core, context->initialState); + + struct VFile* vf = VFileMemChunk(NULL, 0); + context->nChannels = 1; + context->channels[0].initialState = NULL; + context->channels[0].initialStateSize = 0; + context->channels[0].channelData = vf; + gbacore->logProxy.logger.vf = vf; + gbacore->logProxy.block = false; + + GBAVideoProxyRendererShim(&gba->video, &gbacore->logProxy); +} + +static void _GBACoreEndVideoLog(struct mCore* core) { + struct GBACore* gbacore = (struct GBACore*) core; + struct GBA* gba = core->board; + GBAVideoProxyRendererUnshim(&gba->video, &gbacore->logProxy); + + mappedMemoryFree(gbacore->logContext->initialState, gbacore->logContext->initialStateSize); + gbacore->logContext->channels[0].channelData->close(gbacore->logContext->channels[0].channelData); +} + struct mCore* GBACoreCreate(void) { struct GBACore* gbacore = malloc(sizeof(*gbacore)); struct mCore* core = &gbacore->d;@@ -718,5 +752,7 @@ core->listVideoLayers = _GBACoreListVideoLayers;
core->listAudioChannels = _GBACoreListAudioChannels; core->enableVideoLayer = _GBACoreEnableVideoLayer; core->enableAudioChannel = _GBACoreEnableAudioChannel; + core->startVideoLog = _GBACoreStartVideoLog; + core->endVideoLog = _GBACoreEndVideoLog; return core; }
@@ -25,6 +25,8 @@ static bool _parsePacket(struct mVideoLogger* logger, const struct mVideoLoggerDirtyInfo* packet);
static uint16_t* _vramBlock(struct mVideoLogger* logger, uint32_t address); void GBAVideoProxyRendererCreate(struct GBAVideoProxyRenderer* renderer, struct GBAVideoRenderer* backend) { + mVideoLoggerRendererCreate(&renderer->logger); + renderer->d.init = GBAVideoProxyRendererInit; renderer->d.reset = GBAVideoProxyRendererReset; renderer->d.deinit = GBAVideoProxyRendererDeinit;@@ -43,9 +45,11 @@ renderer->d.disableBG[2] = false;
renderer->d.disableBG[3] = false; renderer->d.disableOBJ = false; + renderer->init = NULL; + renderer->deinit = NULL; + renderer->reset = NULL; + renderer->logger.context = renderer; - renderer->logger.writeData = NULL; - renderer->logger.readData = NULL; renderer->logger.parsePacket = _parsePacket; renderer->logger.vramBlock = _vramBlock; renderer->logger.paletteSize = SIZE_PALETTE_RAM;@@ -55,9 +59,7 @@
renderer->backend = backend; } -void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) { - struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; - +static void _init(struct GBAVideoProxyRenderer* proxyRenderer) { mVideoLoggerRendererInit(&proxyRenderer->logger); if (proxyRenderer->block) {@@ -67,20 +69,65 @@ proxyRenderer->backend->oam = (union GBAOAM*) proxyRenderer->logger.oam;
proxyRenderer->backend->cache = NULL; } - proxyRenderer->init(proxyRenderer); + if (proxyRenderer->init) { + proxyRenderer->init(proxyRenderer); + } +} + +static void _reset(struct GBAVideoProxyRenderer* proxyRenderer) { + memcpy(proxyRenderer->logger.oam, &proxyRenderer->d.oam->raw, SIZE_OAM); + memcpy(proxyRenderer->logger.palette, &proxyRenderer->d.palette, SIZE_PALETTE_RAM); + memcpy(proxyRenderer->logger.vram, &proxyRenderer->d.vram, SIZE_VRAM); + + mVideoLoggerRendererReset(&proxyRenderer->logger); + + if (proxyRenderer->reset) { + proxyRenderer->reset(proxyRenderer); + } +} + +void GBAVideoProxyRendererShim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer) { + if (video->renderer != renderer->backend) { + return; + } + renderer->d.cache = video->renderer->cache; + video->renderer = &renderer->d; + renderer->d.palette = video->palette; + renderer->d.vram = video->vram; + renderer->d.oam = &video->oam; + _init(renderer); + _reset(renderer); +} + +void GBAVideoProxyRendererUnshim(struct GBAVideo* video, struct GBAVideoProxyRenderer* renderer) { + if (video->renderer != &renderer->d) { + return; + } + renderer->backend->cache = video->renderer->cache; + video->renderer = renderer->backend; + renderer->backend->palette = video->palette; + renderer->backend->vram = video->vram; + renderer->backend->oam = &video->oam; + + if (renderer->deinit) { + renderer->deinit(renderer); + } + + mVideoLoggerRendererDeinit(&renderer->logger); +} + +void GBAVideoProxyRendererInit(struct GBAVideoRenderer* renderer) { + struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; + + _init(proxyRenderer); proxyRenderer->backend->init(proxyRenderer->backend); } void GBAVideoProxyRendererReset(struct GBAVideoRenderer* renderer) { struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; - memcpy(proxyRenderer->logger.oam, &renderer->oam->raw, SIZE_OAM); - memcpy(proxyRenderer->logger.palette, renderer->palette, SIZE_PALETTE_RAM); - memcpy(proxyRenderer->logger.vram, renderer->vram, SIZE_VRAM); - mVideoLoggerRendererReset(&proxyRenderer->logger); - - proxyRenderer->reset(proxyRenderer); + _reset(proxyRenderer); proxyRenderer->backend->reset(proxyRenderer->backend); }@@ -88,7 +135,9 @@
void GBAVideoProxyRendererDeinit(struct GBAVideoRenderer* renderer) { struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer; - proxyRenderer->deinit(proxyRenderer); + if (proxyRenderer->deinit) { + proxyRenderer->deinit(proxyRenderer); + } proxyRenderer->backend->deinit(proxyRenderer->backend);
@@ -39,6 +39,7 @@ renderer->d.wake = _wake;
renderer->d.logger.writeData = _writeData; renderer->d.logger.readData = _readData; + renderer->d.logger.vf = NULL; } void GBAVideoThreadProxyRendererInit(struct GBAVideoProxyRenderer* renderer) {
@@ -20,6 +20,7 @@ #include <mgba/core/config.h>
#include <mgba/core/directories.h> #include <mgba/core/serialize.h> #include <mgba/core/tile-cache.h> +#include <mgba/core/video-logger.h> #ifdef M_CORE_GBA #include <mgba/gba/interface.h> #include <mgba/internal/gba/gba.h>@@ -71,6 +72,7 @@ , m_saveStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_CHEATS | SAVESTATE_RTC)
, m_loadStateFlags(SAVESTATE_SCREENSHOT | SAVESTATE_RTC) , m_preload(false) , m_override(nullptr) + , m_vl(nullptr) { #ifdef M_CORE_GBA m_lux.p = this;@@ -1197,6 +1199,27 @@
void GameController::disableLogLevel(int levels) { Interrupter interrupter(this); m_logLevels &= ~levels; +} + +void GameController::startVideoLog(const QString& path) { + if (!isLoaded() || m_vl) { + return; + } + m_vlPath = path; + m_vl = mVideoLoggerCreate(m_threadContext.core); +} + +void GameController::endVideoLog() { + if (!m_vl) { + return; + } + if (isLoaded()) { + VFile* vf = VFileDevice::open(m_vlPath, O_WRONLY | O_CREAT | O_TRUNC); + mVideoLoggerWrite(m_threadContext.core, m_vl, vf); + vf->close(vf); + } + mVideoLoggerDestroy(m_threadContext.core, m_vl); + m_vf = nullptr; } void GameController::pollEvents() {
@@ -29,6 +29,7 @@ struct GBAAudio;
struct mCoreConfig; struct mDebugger; struct mTileCache; +struct mVideoLogContext; namespace QGBA {@@ -174,6 +175,9 @@ void setLogLevel(int);
void enableLogLevel(int); void disableLogLevel(int); + void startVideoLog(const QString& path); + void endVideoLog(); + private slots: void openGame(bool bios = false); void crashGame(const QString& crashMessage);@@ -241,6 +245,9 @@ InputController* m_inputController;
MultiplayerController* m_multiplayer; mAVStream* m_stream; + + mVideoLogContext* m_vl; + QString m_vlPath; struct GameControllerLux : GBALuminanceSource { GameController* p;
@@ -478,6 +478,13 @@ AboutScreen* about = new AboutScreen();
openView(about); } +void Window::startVideoLog() { + QString filename = GBAApp::app()->getSaveFileName(this, tr("Select video log"), tr("Video logs (*.mvl)")); + if (!filename.isEmpty()) { + m_controller->startVideoLog(filename); + } +} + template <typename T, typename A> std::function<void()> Window::openTView(A arg) { return [=]() {@@ -1349,6 +1356,16 @@ QAction* recordGIF = new QAction(tr("Record GIF..."), avMenu);
connect(recordGIF, SIGNAL(triggered()), this, SLOT(openGIFWindow())); addControlledAction(avMenu, recordGIF, "recordGIF"); #endif + + QAction* recordVL = new QAction(tr("Record video log..."), avMenu); + connect(recordVL, SIGNAL(triggered()), this, SLOT(startVideoLog())); + addControlledAction(avMenu, recordVL, "recordVL"); + m_gameActions.append(recordVL); + + QAction* stopVL = new QAction(tr("Stop video log"), avMenu); + connect(stopVL, SIGNAL(triggered()), m_controller, SLOT(endVideoLog())); + addControlledAction(avMenu, stopVL, "stopVL"); + m_gameActions.append(stopVL); avMenu->addSeparator(); m_videoLayers = avMenu->addMenu(tr("Video layers"));
@@ -81,6 +81,8 @@
void openSettingsWindow(); void openAboutScreen(); + void startVideoLog(); + #ifdef USE_DEBUGGERS void consoleOpen(); #endif