mirror of https://github.com/mgba-emu/mgba.git
GBA RR: Modularize RR
This commit is contained in:
parent
95e4820743
commit
bb77d278dd
|
@ -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}
|
||||
|
|
|
@ -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]) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue