diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d5801063..4d0a7b77d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ set(BUILD_STATIC OFF CACHE BOOL "Build a static library") set(BUILD_SHARED ON CACHE BOOL "Build a shared library") file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c) file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c) +file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c) file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c) file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs]) file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c) @@ -25,7 +26,7 @@ file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.c) list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c) source_group("ARM core" FILES ${ARM_SRC}) source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC}) -source_group("GBA supervisor" FILES ${GBA_SV_SRC}) +source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC}) source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}}) include_directories(${CMAKE_SOURCE_DIR}/src/arm) include_directories(${CMAKE_SOURCE_DIR}/src) @@ -272,6 +273,7 @@ endif() set(SRC ${ARM_SRC} ${GBA_SRC} + ${GBA_RR_SRC} ${GBA_SV_SRC} ${DEBUGGER_SRC} ${RENDERER_SRC} diff --git a/src/gba/gba.c b/src/gba/gba.c index 88d546b10..07dcc512b 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -106,7 +106,7 @@ void GBADestroy(struct GBA* gba) { GBAVideoDeinit(&gba->video); GBAAudioDeinit(&gba->audio); GBASIODeinit(&gba->sio); - GBARRContextDestroy(gba); + gba->rr = 0; } void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) { @@ -130,7 +130,7 @@ void GBAReset(struct ARMCore* cpu) { cpu->gprs[ARM_SP] = SP_BASE_SYSTEM; struct GBA* gba = (struct GBA*) cpu->master; - if (!GBARRIsPlaying(gba->rr) && !GBARRIsRecording(gba->rr)) { + if (!gba->rr || (!gba->rr->isPlaying(gba->rr) && !gba->rr->isRecording(gba->rr))) { GBASavedataUnmask(&gba->memory.savedata); } GBAMemoryReset(gba); @@ -713,7 +713,7 @@ void GBAFrameStarted(struct GBA* gba) { void GBAFrameEnded(struct GBA* gba) { if (gba->rr) { - GBARRNextFrame(gba->rr); + gba->rr->nextFrame(gba->rr); } if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) { diff --git a/src/gba/io.c b/src/gba/io.c index 6e3361f4b..271aec632 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -583,12 +583,12 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) { break; case REG_KEYINPUT: - if (GBARRIsPlaying(gba->rr)) { - return 0x3FF ^ GBARRQueryInput(gba->rr); + if (gba->rr && gba->rr->isPlaying(gba->rr)) { + return 0x3FF ^ gba->rr->queryInput(gba->rr); } else if (gba->keySource) { uint16_t input = *gba->keySource; - if (GBARRIsRecording(gba->rr)) { - GBARRLogInput(gba->rr, input); + if (gba->rr && gba->rr->isRecording(gba->rr)) { + gba->rr->logInput(gba->rr, input); } return 0x3FF ^ input; } diff --git a/src/gba/rr/mgm.c b/src/gba/rr/mgm.c new file mode 100644 index 000000000..bc487a7b2 --- /dev/null +++ b/src/gba/rr/mgm.c @@ -0,0 +1,583 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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 "mgm.h" + +#include "gba/gba.h" +#include "gba/serialize.h" +#include "util/vfs.h" + +#define BINARY_EXT ".mgm" +#define BINARY_MAGIC "GBAb" +#define METADATA_FILENAME "metadata" BINARY_EXT + +enum { + INVALID_INPUT = 0x8000 +}; + +static bool GBAMGMStartPlaying(struct GBARRContext*, bool autorecord); +static void GBAMGMStopPlaying(struct GBARRContext*); +static bool GBAMGMStartRecording(struct GBARRContext*); +static void GBAMGMStopRecording(struct GBARRContext*); + +static bool GBAMGMIsPlaying(const struct GBARRContext*); +static bool GBAMGMIsRecording(const struct GBARRContext*); + +static void GBAMGMNextFrame(struct GBARRContext*); +static void GBAMGMLogInput(struct GBARRContext*, uint16_t input); +static uint16_t GBAMGMQueryInput(struct GBARRContext*); + +static void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state); +static void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state); + +static bool _loadStream(struct GBAMGMContext*, uint32_t streamId); +static bool _incrementStream(struct GBAMGMContext*, bool recursive); +static bool _finishSegment(struct GBAMGMContext*); +static bool _skipSegment(struct GBAMGMContext*); +static bool _markRerecord(struct GBAMGMContext*); + +static bool _emitMagic(struct GBAMGMContext*, struct VFile* vf); +static bool _verifyMagic(struct GBAMGMContext*, struct VFile* vf); +static enum GBAMGMTag _readTag(struct GBAMGMContext*, struct VFile* vf); +static bool _seekTag(struct GBAMGMContext*, struct VFile* vf, enum GBAMGMTag tag); +static bool _emitTag(struct GBAMGMContext*, struct VFile* vf, uint8_t tag); +static bool _emitEnd(struct GBAMGMContext*, struct VFile* vf); + +static bool _parseMetadata(struct GBAMGMContext*, struct VFile* vf); + +static bool _markStreamNext(struct GBAMGMContext*, uint32_t newStreamId, bool recursive); +static void _streamEndReached(struct GBAMGMContext*); + +static struct VFile* GBAMGMOpenSavedata(struct GBARRContext*, int flags); +static struct VFile* GBAMGMOpenSavestate(struct GBARRContext*, int flags); + +void GBAMGMContextCreate(struct GBAMGMContext* mgm) { + memset(mgm, 0, sizeof(*mgm)); + + mgm->d.startPlaying = GBAMGMStartPlaying; + mgm->d.stopPlaying = GBAMGMStopPlaying; + mgm->d.startRecording = GBAMGMStartRecording; + mgm->d.stopRecording = GBAMGMStopRecording; + + mgm->d.isPlaying = GBAMGMIsPlaying; + mgm->d.isRecording = GBAMGMIsRecording; + + mgm->d.nextFrame = GBAMGMNextFrame; + mgm->d.logInput = GBAMGMLogInput; + mgm->d.queryInput = GBAMGMQueryInput; + + mgm->d.stateSaved = GBAMGMStateSaved; + mgm->d.stateLoaded = GBAMGMStateLoaded; + + mgm->d.openSavedata = GBAMGMOpenSavedata; + mgm->d.openSavestate = GBAMGMOpenSavestate; +} + +void GBAMGMContextDestroy(struct GBAMGMContext* mgm) { + if (mgm->d.isPlaying(&mgm->d)) { + mgm->d.stopPlaying(&mgm->d); + } + if (mgm->d.isRecording(&mgm->d)) { + mgm->d.stopRecording(&mgm->d); + } + if (mgm->metadataFile) { + mgm->metadataFile->close(mgm->metadataFile); + } + if (mgm->d.savedata) { + mgm->d.savedata->close(mgm->d.savedata); + mgm->d.savedata = 0; + } +} + +bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream) { + if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) { + return false; + } + + if (mgm->metadataFile && !mgm->metadataFile->close(mgm->metadataFile)) { + return false; + } + + mgm->streamDir = stream; + mgm->metadataFile = mgm->streamDir->openFile(mgm->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR); + mgm->currentInput = INVALID_INPUT; + if (!_parseMetadata(mgm, mgm->metadataFile)) { + mgm->metadataFile->close(mgm->metadataFile); + mgm->metadataFile = 0; + mgm->maxStreamId = 0; + } + mgm->streamId = 1; + mgm->movieStream = 0; + return true; +} + +bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom) { + if (mgm->metadataFile) { + mgm->metadataFile->truncate(mgm->metadataFile, 0); + } else { + mgm->metadataFile = mgm->streamDir->openFile(mgm->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR); + } + _emitMagic(mgm, mgm->metadataFile); + + mgm->d.initFrom = initFrom; + mgm->initFromOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR); + _emitTag(mgm, mgm->metadataFile, TAG_INIT | initFrom); + + mgm->streamId = 0; + mgm->maxStreamId = 0; + _emitTag(mgm, mgm->metadataFile, TAG_MAX_STREAM); + mgm->maxStreamIdOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR); + mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId)); + + mgm->d.rrCount = 0; + _emitTag(mgm, mgm->metadataFile, TAG_RR_COUNT); + mgm->rrCountOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR); + mgm->metadataFile->write(mgm->metadataFile, &mgm->d.rrCount, sizeof(mgm->d.rrCount)); + return true; +} + +bool _loadStream(struct GBAMGMContext* mgm, uint32_t streamId) { + if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) { + return false; + } + mgm->movieStream = 0; + mgm->streamId = streamId; + mgm->currentInput = INVALID_INPUT; + char buffer[14]; + snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId); + if (mgm->d.isRecording(&mgm->d)) { + int flags = O_CREAT | O_RDWR; + if (streamId > mgm->maxStreamId) { + flags |= O_TRUNC; + } + mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, flags); + } else if (mgm->d.isPlaying(&mgm->d)) { + mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, O_RDONLY); + mgm->peekedTag = TAG_INVALID; + if (!mgm->movieStream || !_verifyMagic(mgm, mgm->movieStream) || !_seekTag(mgm, mgm->movieStream, TAG_BEGIN)) { + mgm->d.stopPlaying(&mgm->d); + } + } + GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId); + mgm->d.frames = 0; + mgm->d.lagFrames = 0; + return true; +} + +bool _incrementStream(struct GBAMGMContext* mgm, bool recursive) { + uint32_t newStreamId = mgm->maxStreamId + 1; + uint32_t oldStreamId = mgm->streamId; + if (mgm->d.isRecording(&mgm->d) && mgm->movieStream) { + if (!_markStreamNext(mgm, newStreamId, recursive)) { + return false; + } + } + if (!_loadStream(mgm, newStreamId)) { + return false; + } + GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId); + _emitMagic(mgm, mgm->movieStream); + mgm->maxStreamId = newStreamId; + _emitTag(mgm, mgm->movieStream, TAG_PREVIOUSLY); + mgm->movieStream->write(mgm->movieStream, &oldStreamId, sizeof(oldStreamId)); + _emitTag(mgm, mgm->movieStream, TAG_BEGIN); + + mgm->metadataFile->seek(mgm->metadataFile, mgm->maxStreamIdOffset, SEEK_SET); + mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId)); + mgm->previously = oldStreamId; + return true; +} + +bool GBAMGMStartPlaying(struct GBARRContext* rr, bool autorecord) { + if (rr->isRecording(rr) || rr->isPlaying(rr)) { + return false; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + mgm->isPlaying = true; + if (!_loadStream(mgm, 1)) { + mgm->isPlaying = false; + return false; + } + mgm->autorecord = autorecord; + return true; +} + +void GBAMGMStopPlaying(struct GBARRContext* rr) { + if (!rr->isPlaying(rr)) { + return; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + mgm->isPlaying = false; + if (mgm->movieStream) { + mgm->movieStream->close(mgm->movieStream); + mgm->movieStream = 0; + } +} + +bool GBAMGMStartRecording(struct GBARRContext* rr) { + if (rr->isRecording(rr) || rr->isPlaying(rr)) { + return false; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (!mgm->maxStreamIdOffset) { + _emitTag(mgm, mgm->metadataFile, TAG_MAX_STREAM); + mgm->maxStreamIdOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR); + mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId)); + } + + mgm->isRecording = true; + return _incrementStream(mgm, false); +} + +void GBAMGMStopRecording(struct GBARRContext* rr) { + if (!rr->isRecording(rr)) { + return; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + mgm->isRecording = false; + if (mgm->movieStream) { + _emitEnd(mgm, mgm->movieStream); + mgm->movieStream->close(mgm->movieStream); + mgm->movieStream = 0; + } +} + +bool GBAMGMIsPlaying(const struct GBARRContext* rr) { + const struct GBAMGMContext* mgm = (const struct GBAMGMContext*) rr; + return mgm->isPlaying; +} + +bool GBAMGMIsRecording(const struct GBARRContext* rr) { + const struct GBAMGMContext* mgm = (const struct GBAMGMContext*) rr; + return mgm->isRecording; +} + +void GBAMGMNextFrame(struct GBARRContext* rr) { + if (!rr->isRecording(rr) && !rr->isPlaying(rr)) { + return; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (rr->isPlaying(rr)) { + while (mgm->peekedTag == TAG_INPUT) { + _readTag(mgm, mgm->movieStream); + GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!"); + } + if (mgm->peekedTag == TAG_LAG) { + GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream"); + if (mgm->inputThisFrame) { + GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie"); + } + } + } + + ++mgm->d.frames; + GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", mgm->d.frames); + if (!mgm->inputThisFrame) { + ++mgm->d.lagFrames; + GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", mgm->d.lagFrames); + } + + if (rr->isRecording(rr)) { + if (!mgm->inputThisFrame) { + _emitTag(mgm, mgm->movieStream, TAG_LAG); + } + _emitTag(mgm, mgm->movieStream, TAG_FRAME); + mgm->inputThisFrame = false; + } else { + if (!_seekTag(mgm, mgm->movieStream, TAG_FRAME)) { + _streamEndReached(mgm); + } + } +} + +void GBAMGMLogInput(struct GBARRContext* rr, uint16_t keys) { + if (!rr->isRecording(rr)) { + return; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (keys != mgm->currentInput) { + _emitTag(mgm, mgm->movieStream, TAG_INPUT); + mgm->movieStream->write(mgm->movieStream, &keys, sizeof(keys)); + mgm->currentInput = keys; + } + GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", mgm->currentInput); + mgm->inputThisFrame = true; +} + +uint16_t GBAMGMQueryInput(struct GBARRContext* rr) { + if (!rr->isPlaying(rr)) { + return 0; + } + + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (mgm->peekedTag == TAG_INPUT) { + _readTag(mgm, mgm->movieStream); + } + mgm->inputThisFrame = true; + if (mgm->currentInput == INVALID_INPUT) { + GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input"); + } + GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", mgm->currentInput); + return mgm->currentInput; +} + +void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) { + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (rr->isRecording(rr)) { + state->associatedStreamId = mgm->streamId; + _finishSegment(mgm); + } +} + +void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) { + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + if (rr->isRecording(rr)) { + if (state->associatedStreamId != mgm->streamId) { + _loadStream(mgm, state->associatedStreamId); + _incrementStream(mgm, true); + } else { + _finishSegment(mgm); + } + _markRerecord(mgm); + } else if (rr->isPlaying(rr)) { + _loadStream(mgm, state->associatedStreamId); + _skipSegment(mgm); + } +} + +bool _finishSegment(struct GBAMGMContext* mgm) { + if (mgm->movieStream) { + if (!_emitEnd(mgm, mgm->movieStream)) { + return false; + } + } + return _incrementStream(mgm, false); +} + +bool _skipSegment(struct GBAMGMContext* mgm) { + mgm->nextTime = 0; + while (_readTag(mgm, mgm->movieStream) != TAG_EOF); + if (!mgm->nextTime || !_loadStream(mgm, mgm->nextTime)) { + _streamEndReached(mgm); + return false; + } + return true; +} + +bool _markRerecord(struct GBAMGMContext* mgm) { + ++mgm->d.rrCount; + mgm->metadataFile->seek(mgm->metadataFile, mgm->rrCountOffset, SEEK_SET); + mgm->metadataFile->write(mgm->metadataFile, &mgm->d.rrCount, sizeof(mgm->d.rrCount)); + return true; +} + +bool _emitMagic(struct GBAMGMContext* mgm, struct VFile* vf) { + UNUSED(mgm); + return vf->write(vf, BINARY_MAGIC, 4) == 4; +} + +bool _verifyMagic(struct GBAMGMContext* mgm, struct VFile* vf) { + UNUSED(mgm); + char buffer[4]; + if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) { + return false; + } + if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) { + return false; + } + return true; +} + +enum GBAMGMTag _readTag(struct GBAMGMContext* mgm, struct VFile* vf) { + if (!mgm || !vf) { + return TAG_EOF; + } + + enum GBAMGMTag tag = mgm->peekedTag; + switch (tag) { + case TAG_INPUT: + vf->read(vf, &mgm->currentInput, sizeof(uint16_t)); + break; + case TAG_PREVIOUSLY: + vf->read(vf, &mgm->previously, sizeof(mgm->previously)); + break; + case TAG_NEXT_TIME: + vf->read(vf, &mgm->nextTime, sizeof(mgm->nextTime)); + break; + case TAG_MAX_STREAM: + vf->read(vf, &mgm->maxStreamId, sizeof(mgm->maxStreamId)); + break; + case TAG_FRAME_COUNT: + vf->read(vf, &mgm->d.frames, sizeof(mgm->d.frames)); + break; + case TAG_LAG_COUNT: + vf->read(vf, &mgm->d.lagFrames, sizeof(mgm->d.lagFrames)); + break; + case TAG_RR_COUNT: + vf->read(vf, &mgm->d.rrCount, sizeof(mgm->d.rrCount)); + break; + + case TAG_INIT_EX_NIHILO: + mgm->d.initFrom = INIT_EX_NIHILO; + break; + case TAG_INIT_FROM_SAVEGAME: + mgm->d.initFrom = INIT_FROM_SAVEGAME; + break; + case TAG_INIT_FROM_SAVESTATE: + mgm->d.initFrom = INIT_FROM_SAVESTATE; + break; + case TAG_INIT_FROM_BOTH: + mgm->d.initFrom = INIT_FROM_BOTH; + break; + + // To be spec'd + case TAG_AUTHOR: + case TAG_COMMENT: + break; + + // Empty markers + case TAG_FRAME: + case TAG_LAG: + case TAG_BEGIN: + case TAG_END: + case TAG_INVALID: + case TAG_EOF: + break; + } + + uint8_t tagBuffer; + if (vf->read(vf, &tagBuffer, 1) != 1) { + mgm->peekedTag = TAG_EOF; + } else { + mgm->peekedTag = tagBuffer; + } + + if (mgm->peekedTag == TAG_END) { + _skipSegment(mgm); + } + return tag; +} + +bool _seekTag(struct GBAMGMContext* mgm, struct VFile* vf, enum GBAMGMTag tag) { + enum GBAMGMTag readTag; + while ((readTag = _readTag(mgm, vf)) != tag) { + if (readTag == TAG_EOF) { + return false; + } + } + return true; +} + +bool _emitTag(struct GBAMGMContext* mgm, struct VFile* vf, uint8_t tag) { + UNUSED(mgm); + return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag); +} + +bool _parseMetadata(struct GBAMGMContext* mgm, struct VFile* vf) { + if (!_verifyMagic(mgm, vf)) { + return false; + } + while (_readTag(mgm, vf) != TAG_EOF) { + switch (mgm->peekedTag) { + case TAG_MAX_STREAM: + mgm->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR); + break; + case TAG_INIT_EX_NIHILO: + case TAG_INIT_FROM_SAVEGAME: + case TAG_INIT_FROM_SAVESTATE: + case TAG_INIT_FROM_BOTH: + mgm->initFromOffset = vf->seek(vf, 0, SEEK_CUR); + break; + case TAG_RR_COUNT: + mgm->rrCountOffset = vf->seek(vf, 0, SEEK_CUR); + break; + default: + break; + } + } + return true; +} + +bool _emitEnd(struct GBAMGMContext* mgm, struct VFile* vf) { + // TODO: Error check + _emitTag(mgm, vf, TAG_END); + _emitTag(mgm, vf, TAG_FRAME_COUNT); + vf->write(vf, &mgm->d.frames, sizeof(mgm->d.frames)); + _emitTag(mgm, vf, TAG_LAG_COUNT); + vf->write(vf, &mgm->d.lagFrames, sizeof(mgm->d.lagFrames)); + _emitTag(mgm, vf, TAG_NEXT_TIME); + + uint32_t newStreamId = 0; + vf->write(vf, &newStreamId, sizeof(newStreamId)); + return true; +} + +bool _markStreamNext(struct GBAMGMContext* mgm, uint32_t newStreamId, bool recursive) { + if (mgm->movieStream->seek(mgm->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) { + return false; + } + + uint8_t tagBuffer; + if (mgm->movieStream->read(mgm->movieStream, &tagBuffer, 1) != 1) { + return false; + } + if (tagBuffer != TAG_NEXT_TIME) { + return false; + } + if (mgm->movieStream->write(mgm->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) { + return false; + } + if (recursive) { + if (mgm->movieStream->seek(mgm->movieStream, 0, SEEK_SET) < 0) { + return false; + } + if (!_verifyMagic(mgm, mgm->movieStream)) { + return false; + } + _readTag(mgm, mgm->movieStream); + if (_readTag(mgm, mgm->movieStream) != TAG_PREVIOUSLY) { + return false; + } + if (mgm->previously == 0) { + return true; + } + uint32_t currentStreamId = mgm->streamId; + if (!_loadStream(mgm, mgm->previously)) { + return false; + } + return _markStreamNext(mgm, currentStreamId, mgm->previously); + } + return true; +} + +void _streamEndReached(struct GBAMGMContext* mgm) { + if (!mgm->d.isPlaying(&mgm->d)) { + return; + } + + uint32_t endStreamId = mgm->streamId; + mgm->d.stopPlaying(&mgm->d); + if (mgm->autorecord) { + mgm->isRecording = true; + _loadStream(mgm, endStreamId); + _incrementStream(mgm, false); + } +} + +struct VFile* GBAMGMOpenSavedata(struct GBARRContext* rr, int flags) { + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + return mgm->streamDir->openFile(mgm->streamDir, "movie.sav", flags); +} + +struct VFile* GBAMGMOpenSavestate(struct GBARRContext* rr, int flags) { + struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr; + return mgm->streamDir->openFile(mgm->streamDir, "movie.ssm", flags); +} diff --git a/src/gba/rr/mgm.h b/src/gba/rr/mgm.h new file mode 100644 index 000000000..3b08655ca --- /dev/null +++ b/src/gba/rr/mgm.h @@ -0,0 +1,83 @@ +/* Copyright (c) 2013-2015 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ +#ifndef RR_MGM_H +#define RR_MGM_H + +#include "util/common.h" + +#include "gba/supervisor/rr.h" + +struct GBA; +struct VDir; +struct VFile; + +enum GBAMGMTag { + // Playback tags + TAG_INVALID = 0x00, + TAG_INPUT = 0x01, + TAG_FRAME = 0x02, + TAG_LAG = 0x03, + + // Stream chunking tags + TAG_BEGIN = 0x10, + TAG_END = 0x11, + TAG_PREVIOUSLY = 0x12, + TAG_NEXT_TIME = 0x13, + TAG_MAX_STREAM = 0x14, + + // Recording information tags + TAG_FRAME_COUNT = 0x20, + TAG_LAG_COUNT = 0x21, + TAG_RR_COUNT = 0x22, + TAG_INIT = 0x24, + TAG_INIT_EX_NIHILO = 0x24 | INIT_EX_NIHILO, + TAG_INIT_FROM_SAVEGAME = 0x24 | INIT_FROM_SAVEGAME, + TAG_INIT_FROM_SAVESTATE = 0x24 | INIT_FROM_SAVESTATE, + TAG_INIT_FROM_BOTH = 0x24 | INIT_FROM_BOTH, + + // User metadata tags + TAG_AUTHOR = 0x30, + TAG_COMMENT = 0x31, + + TAG_EOF = INT_MAX +}; + +struct GBAMGMContext { + struct GBARRContext d; + + // Playback state + bool isPlaying; + bool autorecord; + + // Recording state + bool isRecording; + bool inputThisFrame; + + // Metadata + uint32_t streamId; + + uint32_t maxStreamId; + off_t maxStreamIdOffset; + off_t initFromOffset; + off_t rrCountOffset; + + // Streaming state + struct VDir* streamDir; + struct VFile* metadataFile; + struct VFile* movieStream; + uint16_t currentInput; + enum GBAMGMTag peekedTag; + uint32_t nextTime; + uint32_t previously; +}; + +void GBAMGMContextCreate(struct GBAMGMContext*); +void GBAMGMContextDestroy(struct GBAMGMContext*); + +bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream); +bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom); + +#endif diff --git a/src/gba/serialize.c b/src/gba/serialize.c index 56914f3dc..93a873c6e 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -49,11 +49,9 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) { GBAVideoSerialize(&gba->video, state); GBAAudioSerialize(&gba->audio, state); - if (GBARRIsRecording(gba->rr)) { - state->associatedStreamId = gba->rr->streamId; - GBARRFinishSegment(gba->rr); - } else { - state->associatedStreamId = 0; + state->associatedStreamId = 0; + if (gba->rr) { + gba->rr->stateSaved(gba->rr, state); } } @@ -114,17 +112,8 @@ void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) { GBAVideoDeserialize(&gba->video, state); GBAAudioDeserialize(&gba->audio, state); - if (GBARRIsRecording(gba->rr)) { - if (state->associatedStreamId != gba->rr->streamId) { - GBARRLoadStream(gba->rr, state->associatedStreamId); - GBARRIncrementStream(gba->rr, true); - } else { - GBARRFinishSegment(gba->rr); - } - GBARRMarkRerecord(gba->rr); - } else if (GBARRIsPlaying(gba->rr)) { - GBARRLoadStream(gba->rr, state->associatedStreamId); - GBARRSkipSegment(gba->rr); + if (gba->rr) { + gba->rr->stateLoaded(gba->rr, state); } } diff --git a/src/gba/supervisor/rr.c b/src/gba/supervisor/rr.c index 4e88bbdd4..06ffc07e0 100644 --- a/src/gba/supervisor/rr.c +++ b/src/gba/supervisor/rr.c @@ -5,63 +5,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "rr.h" -#include "gba/gba.h" -#include "gba/serialize.h" #include "util/vfs.h" -#define BINARY_EXT ".dat" -#define BINARY_MAGIC "GBAb" -#define METADATA_FILENAME "metadata" BINARY_EXT - -enum { - INVALID_INPUT = 0x8000 -}; - -static bool _emitMagic(struct GBARRContext* rr, struct VFile* vf); -static bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf); -static enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf); -static bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag); -static bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag); -static bool _emitEnd(struct GBARRContext* rr, struct VFile* vf); - -static bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf); - -static bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive); -static void _streamEndReached(struct GBARRContext* rr); - -static struct VFile* _openSavedata(struct GBARRContext* rr, int flags); -static struct VFile* _openSavestate(struct GBARRContext* rr, int flags); - -void GBARRContextCreate(struct GBA* gba) { - if (gba->rr) { - return; - } - - gba->rr = calloc(1, sizeof(*gba->rr)); -} - -void GBARRContextDestroy(struct GBA* gba) { - if (!gba->rr) { - return; - } - - if (GBARRIsPlaying(gba->rr)) { - GBARRStopPlaying(gba->rr); - } - if (GBARRIsRecording(gba->rr)) { - GBARRStopRecording(gba->rr); - } - if (gba->rr->metadataFile) { - gba->rr->metadataFile->close(gba->rr->metadataFile); - } - if (gba->rr->savedata) { - gba->rr->savedata->close(gba->rr->savedata); - } - - free(gba->rr); - gba->rr = 0; -} - void GBARRSaveState(struct GBA* gba) { if (!gba || !gba->rr) { return; @@ -71,17 +16,17 @@ void GBARRSaveState(struct GBA* gba) { if (gba->rr->savedata) { gba->rr->savedata->close(gba->rr->savedata); } - gba->rr->savedata = _openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY); + gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY); GBASavedataClone(&gba->memory.savedata, gba->rr->savedata); gba->rr->savedata->close(gba->rr->savedata); - gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY); + gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY); GBASavedataMask(&gba->memory.savedata, gba->rr->savedata); } else { GBASavedataMask(&gba->memory.savedata, 0); } if (gba->rr->initFrom & INIT_FROM_SAVESTATE) { - struct VFile* vf = _openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR); + struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR); GBASaveStateNamed(gba, vf, false); vf->close(vf); } else { @@ -98,475 +43,17 @@ void GBARRLoadState(struct GBA* gba) { if (gba->rr->savedata) { gba->rr->savedata->close(gba->rr->savedata); } - gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY); + gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY); GBASavedataMask(&gba->memory.savedata, gba->rr->savedata); } else { GBASavedataMask(&gba->memory.savedata, 0); } if (gba->rr->initFrom & INIT_FROM_SAVESTATE) { - struct VFile* vf = _openSavestate(gba->rr, O_RDONLY); + struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY); GBALoadStateNamed(gba, vf); vf->close(vf); } else { ARMReset(gba->cpu); } } - -bool GBARRInitStream(struct GBARRContext* rr, struct VDir* stream) { - if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) { - return false; - } - - if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) { - return false; - } - - rr->streamDir = stream; - rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR); - rr->currentInput = INVALID_INPUT; - if (!_parseMetadata(rr, rr->metadataFile)) { - rr->metadataFile->close(rr->metadataFile); - rr->metadataFile = 0; - rr->maxStreamId = 0; - } - rr->streamId = 1; - rr->movieStream = 0; - return true; -} - -bool GBARRReinitStream(struct GBARRContext* rr, enum GBARRInitFrom initFrom) { - if (!rr) { - return false; - } - - if (rr->metadataFile) { - rr->metadataFile->truncate(rr->metadataFile, 0); - } else { - rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR); - } - _emitMagic(rr, rr->metadataFile); - - rr->initFrom = initFrom; - rr->initFromOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR); - _emitTag(rr, rr->metadataFile, TAG_INIT | initFrom); - - rr->streamId = 0; - rr->maxStreamId = 0; - _emitTag(rr, rr->metadataFile, TAG_MAX_STREAM); - rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR); - rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId)); - - rr->rrCount = 0; - _emitTag(rr, rr->metadataFile, TAG_RR_COUNT); - rr->rrCountOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR); - rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount)); - return true; -} - -bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) { - if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) { - return false; - } - rr->movieStream = 0; - rr->streamId = streamId; - rr->currentInput = INVALID_INPUT; - char buffer[14]; - snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId); - if (GBARRIsRecording(rr)) { - int flags = O_CREAT | O_RDWR; - if (streamId > rr->maxStreamId) { - flags |= O_TRUNC; - } - rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags); - } else if (GBARRIsPlaying(rr)) { - rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY); - rr->peekedTag = TAG_INVALID; - if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) { - GBARRStopPlaying(rr); - } - } - GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId); - rr->frames = 0; - rr->lagFrames = 0; - return true; -} - -bool GBARRIncrementStream(struct GBARRContext* rr, bool recursive) { - uint32_t newStreamId = rr->maxStreamId + 1; - uint32_t oldStreamId = rr->streamId; - if (GBARRIsRecording(rr) && rr->movieStream) { - if (!_markStreamNext(rr, newStreamId, recursive)) { - return false; - } - } - if (!GBARRLoadStream(rr, newStreamId)) { - return false; - } - GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId); - _emitMagic(rr, rr->movieStream); - rr->maxStreamId = newStreamId; - _emitTag(rr, rr->movieStream, TAG_PREVIOUSLY); - rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId)); - _emitTag(rr, rr->movieStream, TAG_BEGIN); - - rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET); - rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId)); - rr->previously = oldStreamId; - return true; -} - -bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) { - if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) { - return false; - } - - rr->isPlaying = true; - if (!GBARRLoadStream(rr, 1)) { - rr->isPlaying = false; - return false; - } - rr->autorecord = autorecord; - return true; -} - -void GBARRStopPlaying(struct GBARRContext* rr) { - if (!GBARRIsPlaying(rr)) { - return; - } - rr->isPlaying = false; - if (rr->movieStream) { - rr->movieStream->close(rr->movieStream); - rr->movieStream = 0; - } -} - -bool GBARRStartRecording(struct GBARRContext* rr) { - if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) { - return false; - } - - if (!rr->maxStreamIdOffset) { - _emitTag(rr, rr->metadataFile, TAG_MAX_STREAM); - rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR); - rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId)); - } - - rr->isRecording = true; - return GBARRIncrementStream(rr, false); -} - -void GBARRStopRecording(struct GBARRContext* rr) { - if (!GBARRIsRecording(rr)) { - return; - } - rr->isRecording = false; - if (rr->movieStream) { - _emitEnd(rr, rr->movieStream); - rr->movieStream->close(rr->movieStream); - rr->movieStream = 0; - } -} - -bool GBARRIsPlaying(struct GBARRContext* rr) { - return rr && rr->isPlaying; -} - -bool GBARRIsRecording(struct GBARRContext* rr) { - return rr && rr->isRecording; -} - -void GBARRNextFrame(struct GBARRContext* rr) { - if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) { - return; - } - - if (GBARRIsPlaying(rr)) { - while (rr->peekedTag == TAG_INPUT) { - _readTag(rr, rr->movieStream); - GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!"); - } - if (rr->peekedTag == TAG_LAG) { - GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream"); - if (rr->inputThisFrame) { - GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie"); - } - } - } - - ++rr->frames; - GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", rr->frames); - if (!rr->inputThisFrame) { - ++rr->lagFrames; - GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", rr->lagFrames); - } - - if (GBARRIsRecording(rr)) { - if (!rr->inputThisFrame) { - _emitTag(rr, rr->movieStream, TAG_LAG); - } - _emitTag(rr, rr->movieStream, TAG_FRAME); - rr->inputThisFrame = false; - } else { - if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) { - _streamEndReached(rr); - } - } -} - -void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) { - if (!GBARRIsRecording(rr)) { - return; - } - - if (keys != rr->currentInput) { - _emitTag(rr, rr->movieStream, TAG_INPUT); - rr->movieStream->write(rr->movieStream, &keys, sizeof(keys)); - rr->currentInput = keys; - } - GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", rr->currentInput); - rr->inputThisFrame = true; -} - -uint16_t GBARRQueryInput(struct GBARRContext* rr) { - if (!GBARRIsPlaying(rr)) { - return 0; - } - - if (rr->peekedTag == TAG_INPUT) { - _readTag(rr, rr->movieStream); - } - rr->inputThisFrame = true; - if (rr->currentInput == INVALID_INPUT) { - GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input"); - } - GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", rr->currentInput); - return rr->currentInput; -} - -bool GBARRFinishSegment(struct GBARRContext* rr) { - if (rr->movieStream) { - if (!_emitEnd(rr, rr->movieStream)) { - return false; - } - } - return GBARRIncrementStream(rr, false); -} - -bool GBARRSkipSegment(struct GBARRContext* rr) { - rr->nextTime = 0; - while (_readTag(rr, rr->movieStream) != TAG_EOF); - if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) { - _streamEndReached(rr); - return false; - } - return true; -} - -bool GBARRMarkRerecord(struct GBARRContext* rr) { - ++rr->rrCount; - rr->metadataFile->seek(rr->metadataFile, rr->rrCountOffset, SEEK_SET); - rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount)); - return true; -} - -bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) { - UNUSED(rr); - return vf->write(vf, BINARY_MAGIC, 4) == 4; -} - -bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) { - UNUSED(rr); - char buffer[4]; - if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) { - return false; - } - if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) { - return false; - } - return true; -} - -enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) { - if (!rr || !vf) { - return TAG_EOF; - } - - enum GBARRTag tag = rr->peekedTag; - switch (tag) { - case TAG_INPUT: - vf->read(vf, &rr->currentInput, sizeof(uint16_t)); - break; - case TAG_PREVIOUSLY: - vf->read(vf, &rr->previously, sizeof(rr->previously)); - break; - case TAG_NEXT_TIME: - vf->read(vf, &rr->nextTime, sizeof(rr->nextTime)); - break; - case TAG_MAX_STREAM: - vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId)); - break; - case TAG_FRAME_COUNT: - vf->read(vf, &rr->frames, sizeof(rr->frames)); - break; - case TAG_LAG_COUNT: - vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames)); - break; - case TAG_RR_COUNT: - vf->read(vf, &rr->rrCount, sizeof(rr->rrCount)); - break; - - case TAG_INIT_EX_NIHILO: - rr->initFrom = INIT_EX_NIHILO; - break; - case TAG_INIT_FROM_SAVEGAME: - rr->initFrom = INIT_FROM_SAVEGAME; - break; - case TAG_INIT_FROM_SAVESTATE: - rr->initFrom = INIT_FROM_SAVESTATE; - break; - case TAG_INIT_FROM_BOTH: - rr->initFrom = INIT_FROM_BOTH; - break; - - // To be spec'd - case TAG_AUTHOR: - case TAG_COMMENT: - break; - - // Empty markers - case TAG_FRAME: - case TAG_LAG: - case TAG_BEGIN: - case TAG_END: - case TAG_INVALID: - case TAG_EOF: - break; - } - - uint8_t tagBuffer; - if (vf->read(vf, &tagBuffer, 1) != 1) { - rr->peekedTag = TAG_EOF; - } else { - rr->peekedTag = tagBuffer; - } - - if (rr->peekedTag == TAG_END) { - GBARRSkipSegment(rr); - } - return tag; -} - -bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) { - enum GBARRTag readTag; - while ((readTag = _readTag(rr, vf)) != tag) { - if (readTag == TAG_EOF) { - return false; - } - } - return true; -} - -bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) { - UNUSED(rr); - return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag); -} - -bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) { - if (!_verifyMagic(rr, vf)) { - return false; - } - while (_readTag(rr, vf) != TAG_EOF) { - switch (rr->peekedTag) { - case TAG_MAX_STREAM: - rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR); - break; - case TAG_INIT_EX_NIHILO: - case TAG_INIT_FROM_SAVEGAME: - case TAG_INIT_FROM_SAVESTATE: - case TAG_INIT_FROM_BOTH: - rr->initFromOffset = vf->seek(vf, 0, SEEK_CUR); - break; - case TAG_RR_COUNT: - rr->rrCountOffset = vf->seek(vf, 0, SEEK_CUR); - break; - default: - break; - } - } - return true; -} - -bool _emitEnd(struct GBARRContext* rr, struct VFile* vf) { - // TODO: Error check - _emitTag(rr, vf, TAG_END); - _emitTag(rr, vf, TAG_FRAME_COUNT); - vf->write(vf, &rr->frames, sizeof(rr->frames)); - _emitTag(rr, vf, TAG_LAG_COUNT); - vf->write(vf, &rr->lagFrames, sizeof(rr->lagFrames)); - _emitTag(rr, vf, TAG_NEXT_TIME); - - uint32_t newStreamId = 0; - vf->write(vf, &newStreamId, sizeof(newStreamId)); - return true; -} - -bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive) { - if (rr->movieStream->seek(rr->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) { - return false; - } - - uint8_t tagBuffer; - if (rr->movieStream->read(rr->movieStream, &tagBuffer, 1) != 1) { - return false; - } - if (tagBuffer != TAG_NEXT_TIME) { - return false; - } - if (rr->movieStream->write(rr->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) { - return false; - } - if (recursive) { - if (rr->movieStream->seek(rr->movieStream, 0, SEEK_SET) < 0) { - return false; - } - if (!_verifyMagic(rr, rr->movieStream)) { - return false; - } - _readTag(rr, rr->movieStream); - if (_readTag(rr, rr->movieStream) != TAG_PREVIOUSLY) { - return false; - } - if (rr->previously == 0) { - return true; - } - uint32_t currentStreamId = rr->streamId; - if (!GBARRLoadStream(rr, rr->previously)) { - return false; - } - return _markStreamNext(rr, currentStreamId, rr->previously); - } - return true; -} - -void _streamEndReached(struct GBARRContext* rr) { - if (!GBARRIsPlaying(rr)) { - return; - } - - uint32_t endStreamId = rr->streamId; - GBARRStopPlaying(rr); - if (rr->autorecord) { - rr->isRecording = true; - GBARRLoadStream(rr, endStreamId); - GBARRIncrementStream(rr, false); - } -} - -struct VFile* _openSavedata(struct GBARRContext* rr, int flags) { - return rr->streamDir->openFile(rr->streamDir, "movie.sav", flags); -} - -struct VFile* _openSavestate(struct GBARRContext* rr, int flags) { - return rr->streamDir->openFile(rr->streamDir, "movie.ssm", flags); -} diff --git a/src/gba/supervisor/rr.h b/src/gba/supervisor/rr.h index fb94ae02d..ae0b2b832 100644 --- a/src/gba/supervisor/rr.h +++ b/src/gba/supervisor/rr.h @@ -8,8 +8,8 @@ #include "util/common.h" -struct GBA; -struct VDir; +#include "gba/serialize.h" + struct VFile; enum GBARRInitFrom { @@ -19,95 +19,32 @@ enum GBARRInitFrom { INIT_FROM_BOTH = 3, }; -enum GBARRTag { - // Playback tags - TAG_INVALID = 0x00, - TAG_INPUT = 0x01, - TAG_FRAME = 0x02, - TAG_LAG = 0x03, - - // Stream chunking tags - TAG_BEGIN = 0x10, - TAG_END = 0x11, - TAG_PREVIOUSLY = 0x12, - TAG_NEXT_TIME = 0x13, - TAG_MAX_STREAM = 0x14, - - // Recording information tags - TAG_FRAME_COUNT = 0x20, - TAG_LAG_COUNT = 0x21, - TAG_RR_COUNT = 0x22, - TAG_INIT = 0x24, - TAG_INIT_EX_NIHILO = 0x24 | INIT_EX_NIHILO, - TAG_INIT_FROM_SAVEGAME = 0x24 | INIT_FROM_SAVEGAME, - TAG_INIT_FROM_SAVESTATE = 0x24 | INIT_FROM_SAVESTATE, - TAG_INIT_FROM_BOTH = 0x24 | INIT_FROM_BOTH, - - // User metadata tags - TAG_AUTHOR = 0x30, - TAG_COMMENT = 0x31, - - TAG_EOF = INT_MAX -}; - struct GBARRContext { - // Playback state - bool isPlaying; - bool autorecord; + bool (*startPlaying)(struct GBARRContext*, bool autorecord); + void (*stopPlaying)(struct GBARRContext*); + bool (*startRecording)(struct GBARRContext*); + void (*stopRecording)(struct GBARRContext*); - // Recording state - bool isRecording; - bool inputThisFrame; + bool (*isPlaying)(const struct GBARRContext*); + bool (*isRecording)(const struct GBARRContext*); + + void (*nextFrame)(struct GBARRContext*); + void (*logInput)(struct GBARRContext*, uint16_t input); + uint16_t (*queryInput)(struct GBARRContext*); + + void (*stateSaved)(struct GBARRContext*, struct GBASerializedState*); + void (*stateLoaded)(struct GBARRContext*, const struct GBASerializedState*); + + struct VFile* (*openSavedata)(struct GBARRContext* mgm, int flags); + struct VFile* (*openSavestate)(struct GBARRContext* mgm, int flags); - // Metadata uint32_t frames; uint32_t lagFrames; - uint32_t streamId; - - uint32_t maxStreamId; - off_t maxStreamIdOffset; - enum GBARRInitFrom initFrom; - off_t initFromOffset; uint32_t rrCount; - off_t rrCountOffset; struct VFile* savedata; - - // Streaming state - struct VDir* streamDir; - struct VFile* metadataFile; - struct VFile* movieStream; - uint16_t currentInput; - enum GBARRTag peekedTag; - uint32_t nextTime; - uint32_t previously; }; -void GBARRContextCreate(struct GBA*); -void GBARRContextDestroy(struct GBA*); -void GBARRSaveState(struct GBA*); -void GBARRLoadState(struct GBA*); - -bool GBARRInitStream(struct GBARRContext*, struct VDir*); -bool GBARRReinitStream(struct GBARRContext*, enum GBARRInitFrom); -bool GBARRLoadStream(struct GBARRContext*, uint32_t streamId); -bool GBARRIncrementStream(struct GBARRContext*, bool recursive); -bool GBARRFinishSegment(struct GBARRContext*); -bool GBARRSkipSegment(struct GBARRContext*); -bool GBARRMarkRerecord(struct GBARRContext*); - -bool GBARRStartPlaying(struct GBARRContext*, bool autorecord); -void GBARRStopPlaying(struct GBARRContext*); -bool GBARRStartRecording(struct GBARRContext*); -void GBARRStopRecording(struct GBARRContext*); - -bool GBARRIsPlaying(struct GBARRContext*); -bool GBARRIsRecording(struct GBARRContext*); - -void GBARRNextFrame(struct GBARRContext*); -void GBARRLogInput(struct GBARRContext*, uint16_t input); -uint16_t GBARRQueryInput(struct GBARRContext*); - #endif