GBA: Rip out old rr code

This commit is contained in:
Vicki Pfau 2020-01-28 18:15:49 -08:00
parent cd2bb61b0e
commit 41775416ce
12 changed files with 22 additions and 1178 deletions

View File

@ -91,7 +91,6 @@ struct GBA {
struct mRTCSource* rtcSource;
struct mRumble* rumble;
struct GBARRContext* rr;
bool isPristine;
size_t pristineRomSize;
size_t yankedRomSize;

View File

@ -1,87 +0,0 @@
/* 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 <mgba-util/common.h>
CXX_GUARD_START
#include <mgba/internal/gba/rr/rr.h>
struct GBA;
struct VDir;
struct VFile;
enum GBAMGMTag {
// Playback tags
TAG_INVALID = 0x00,
TAG_INPUT = 0x01,
TAG_FRAME = 0x02,
TAG_LAG = 0x03,
TAG_RESET = 0x04,
// 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*);
bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream);
bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom);
CXX_GUARD_END
#endif

View File

@ -1,65 +0,0 @@
/* 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 GBA_RR_H
#define GBA_RR_H
#include <mgba-util/common.h>
CXX_GUARD_START
#include <mgba/core/log.h>
#include <mgba/internal/gba/serialize.h>
struct VFile;
mLOG_DECLARE_CATEGORY(GBA_RR);
enum GBARRInitFrom {
INIT_EX_NIHILO = 0,
INIT_FROM_SAVEGAME = 1,
INIT_FROM_SAVESTATE = 2,
INIT_FROM_BOTH = 3,
};
struct GBARRContext {
void (*destroy)(struct GBARRContext*);
bool (*startPlaying)(struct GBARRContext*, bool autorecord);
void (*stopPlaying)(struct GBARRContext*);
bool (*startRecording)(struct GBARRContext*);
void (*stopRecording)(struct GBARRContext*);
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*);
bool (*queryReset)(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);
uint32_t frames;
uint32_t lagFrames;
enum GBARRInitFrom initFrom;
uint32_t rrCount;
struct VFile* savedata;
};
void GBARRDestroy(struct GBARRContext*);
void GBARRInitRecord(struct GBA*);
void GBARRInitPlay(struct GBA*);
CXX_GUARD_END
#endif

View File

@ -1,30 +0,0 @@
/* 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 GBA_VBM_H
#define GBA_VBM_H
#include <mgba-util/common.h>
CXX_GUARD_START
#include <mgba/internal/gba/rr/rr.h>
struct GBAVBMContext {
struct GBARRContext d;
bool isPlaying;
struct VFile* vbmFile;
int32_t inputOffset;
};
void GBAVBMContextCreate(struct GBAVBMContext*);
bool GBAVBMSetStream(struct GBAVBMContext*, struct VFile*);
CXX_GUARD_END
#endif

View File

@ -193,8 +193,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
* | 0x002F4 - 0x002F7: GBA BIOS bus prefetch
* | 0x002F8 - 0x002FB: CPU prefecth (decode slot)
* | 0x002FC - 0x002FF: CPU prefetch (fetch slot)
* 0x00300 - 0x00303: Associated movie stream ID for record/replay (or 0 if no stream)
* 0x00304 - 0x00317: Savestate creation time (usec since 1970)
* 0x00300 - 0x00317: Reserved (leave zero)
* 0x00318 - 0x0031B: Last prefetched program counter
* 0x0031C - 0x0031F: Miscellaneous flags
* | bit 0: Is CPU halted?
@ -327,8 +326,7 @@ struct GBASerializedState {
uint32_t biosPrefetch;
uint32_t cpuPrefetch[2];
uint32_t associatedStreamId;
uint32_t reservedRr[5];
uint32_t reservedCpu[6];
uint32_t lastPrefetchedPc;
GBASerializedMiscFlags miscFlags;

View File

@ -39,10 +39,7 @@ set(SIO_FILES
set(EXTRA_FILES
extra/audio-mixer.c
extra/battlechip.c
extra/proxy.c
rr/mgm.c
rr/rr.c
rr/vbm.c)
extra/proxy.c)
set(DEBUGGER_FILES
debugger/cli.c)

View File

@ -13,7 +13,6 @@
#include <mgba/internal/gba/cheats.h>
#include <mgba/internal/gba/io.h>
#include <mgba/internal/gba/overrides.h>
#include <mgba/internal/gba/rr/rr.h>
#include <mgba-util/patch.h>
#include <mgba-util/crc32.h>
@ -96,7 +95,6 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
gba->luminanceSource = 0;
gba->rtcSource = 0;
gba->rumble = 0;
gba->rr = 0;
gba->romVf = 0;
gba->biosVf = 0;
@ -170,7 +168,6 @@ void GBADestroy(struct GBA* gba) {
GBAVideoDeinit(&gba->video);
GBAAudioDeinit(&gba->audio);
GBASIODeinit(&gba->sio);
gba->rr = 0;
mTimingDeinit(&gba->timing);
mCoreCallbacksListDeinit(&gba->coreCallbacks);
}
@ -196,10 +193,8 @@ void GBAReset(struct ARMCore* cpu) {
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM;
struct GBA* gba = (struct GBA*) cpu->master;
if (!gba->rr || (!gba->rr->isPlaying(gba->rr) && !gba->rr->isRecording(gba->rr))) {
gba->memory.savedata.maskWriteback = false;
GBASavedataUnmask(&gba->memory.savedata);
}
gba->memory.savedata.maskWriteback = false;
GBASavedataUnmask(&gba->memory.savedata);
gba->cpuBlocked = false;
gba->earlyExit = false;
@ -813,10 +808,6 @@ void GBAFrameStarted(struct GBA* gba) {
void GBAFrameEnded(struct GBA* gba) {
GBASavedataClean(&gba->memory.savedata, gba->video.frameCounter);
if (gba->rr) {
gba->rr->nextFrame(gba->rr);
}
if (gba->cpu->components && gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]) {
struct mCheatDevice* device = (struct mCheatDevice*) gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE];
size_t i;

View File

@ -8,7 +8,6 @@
#include <mgba/internal/arm/macros.h>
#include <mgba/internal/gba/dma.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/rr/rr.h>
#include <mgba/internal/gba/serialize.h>
mLOG_DEFINE_CATEGORY(GBA_IO, "GBA I/O", "gba.io");
@ -734,31 +733,24 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
}
}
}
if (gba->rr && gba->rr->isPlaying(gba->rr)) {
return 0x3FF ^ gba->rr->queryInput(gba->rr);
} else {
uint16_t input = 0;
if (gba->keyCallback) {
input = gba->keyCallback->readKeys(gba->keyCallback);
if (gba->keySource) {
*gba->keySource = input;
}
} else if (gba->keySource) {
input = *gba->keySource;
if (!gba->allowOpposingDirections) {
unsigned rl = input & 0x030;
unsigned ud = input & 0x0C0;
input &= 0x30F;
if (rl != 0x030) {
input |= rl;
}
if (ud != 0x0C0) {
input |= ud;
}
}
uint16_t input = 0;
if (gba->keyCallback) {
input = gba->keyCallback->readKeys(gba->keyCallback);
if (gba->keySource) {
*gba->keySource = input;
}
if (gba->rr && gba->rr->isRecording(gba->rr)) {
gba->rr->logInput(gba->rr, input);
} else if (gba->keySource) {
input = *gba->keySource;
if (!gba->allowOpposingDirections) {
unsigned rl = input & 0x030;
unsigned ud = input & 0x0C0;
input &= 0x30F;
if (rl != 0x030) {
input |= rl;
}
if (ud != 0x0C0) {
input |= ud;
}
}
return 0x3FF ^ input;
}

View File

@ -1,590 +0,0 @@
/* 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 <mgba/internal/gba/rr/mgm.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/serialize.h>
#include <mgba-util/vfs.h>
#define BINARY_EXT ".mgm"
#define BINARY_MAGIC "GBAb"
#define METADATA_FILENAME "metadata" BINARY_EXT
enum {
INVALID_INPUT = 0x8000
};
static void GBAMGMContextDestroy(struct GBARRContext*);
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 bool GBAMGMQueryReset(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.destroy = GBAMGMContextDestroy;
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.queryReset = GBAMGMQueryReset;
mgm->d.stateSaved = GBAMGMStateSaved;
mgm->d.stateLoaded = GBAMGMStateLoaded;
mgm->d.openSavedata = GBAMGMOpenSavedata;
mgm->d.openSavestate = GBAMGMOpenSavestate;
}
void GBAMGMContextDestroy(struct GBARRContext* rr) {
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
if (mgm->metadataFile) {
mgm->metadataFile->close(mgm->metadataFile);
}
}
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);
}
}
mLOG(GBA_RR, DEBUG, "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;
}
mLOG(GBA_RR, DEBUG, "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);
mLOG(GBA_RR, WARN, "Desync detected!");
}
if (mgm->peekedTag == TAG_LAG) {
mLOG(GBA_RR, DEBUG, "Lag frame marked in stream");
if (mgm->inputThisFrame) {
mLOG(GBA_RR, WARN, "Lag frame in stream does not match movie");
}
}
}
++mgm->d.frames;
mLOG(GBA_RR, DEBUG, "Frame: %u", mgm->d.frames);
if (!mgm->inputThisFrame) {
++mgm->d.lagFrames;
mLOG(GBA_RR, DEBUG, "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;
}
mLOG(GBA_RR, DEBUG, "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) {
mLOG(GBA_RR, WARN, "Stream did not specify input");
}
mLOG(GBA_RR, DEBUG, "Input replay: %03X", mgm->currentInput);
return mgm->currentInput;
}
bool GBAMGMQueryReset(struct GBARRContext* rr) {
if (!rr->isPlaying(rr)) {
return 0;
}
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
return mgm->peekedTag == TAG_RESET;
}
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_RESET:
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);
}

View File

@ -1,77 +0,0 @@
/* 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 <mgba/internal/gba/rr/rr.h>
#include <mgba/core/log.h>
#include <mgba/core/serialize.h>
#include <mgba-util/vfs.h>
mLOG_DEFINE_CATEGORY(GBA_RR, "GBA RR", "gba.rr");
void GBARRInitRecord(struct GBA* gba) {
if (!gba || !gba->rr) {
return;
}
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
if (gba->rr->savedata) {
gba->rr->savedata->close(gba->rr->savedata);
}
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 = gba->rr->openSavedata(gba->rr, O_RDONLY);
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata, false);
} else {
GBASavedataMask(&gba->memory.savedata, 0, false);
}
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
//GBASaveStateNamed(gba, vf, SAVESTATE_SAVEDATA);
vf->close(vf);
} else {
ARMReset(gba->cpu);
}
}
void GBARRInitPlay(struct GBA* gba) {
if (!gba || !gba->rr) {
return;
}
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
if (gba->rr->savedata) {
gba->rr->savedata->close(gba->rr->savedata);
}
gba->rr->savedata = gba->rr->openSavedata(gba->rr, O_RDONLY);
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata, false);
} else {
GBASavedataMask(&gba->memory.savedata, 0, false);
}
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY);
//GBALoadStateNamed(gba, vf, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA);
vf->close(vf);
} else {
ARMReset(gba->cpu);
}
}
void GBARRDestroy(struct GBARRContext* rr) {
if (rr->isPlaying(rr)) {
rr->stopPlaying(rr);
}
if (rr->isRecording(rr)) {
rr->stopRecording(rr);
}
if (rr->savedata) {
rr->savedata->close(rr->savedata);
rr->savedata = 0;
}
rr->destroy(rr);
}

View File

@ -1,274 +0,0 @@
/* 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 <mgba/internal/gba/rr/vbm.h>
#include <mgba/internal/gba/gba.h>
#include <mgba/internal/gba/serialize.h>
#include <mgba-util/vfs.h>
#ifdef USE_ZLIB
#include <zlib.h>
#endif
static const char VBM_MAGIC[] = "VBM\x1A";
static void GBAVBMContextDestroy(struct GBARRContext*);
static bool GBAVBMStartPlaying(struct GBARRContext*, bool autorecord);
static void GBAVBMStopPlaying(struct GBARRContext*);
static bool GBAVBMStartRecording(struct GBARRContext*);
static void GBAVBMStopRecording(struct GBARRContext*);
static bool GBAVBMIsPlaying(const struct GBARRContext*);
static bool GBAVBMIsRecording(const struct GBARRContext*);
static void GBAVBMNextFrame(struct GBARRContext*);
static uint16_t GBAVBMQueryInput(struct GBARRContext*);
static bool GBAVBMQueryReset(struct GBARRContext*);
static void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state);
static void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state);
static struct VFile* GBAVBMOpenSavedata(struct GBARRContext*, int flags);
static struct VFile* GBAVBMOpenSavestate(struct GBARRContext*, int flags);
void GBAVBMContextCreate(struct GBAVBMContext* vbm) {
memset(vbm, 0, sizeof(*vbm));
vbm->d.destroy = GBAVBMContextDestroy;
vbm->d.startPlaying = GBAVBMStartPlaying;
vbm->d.stopPlaying = GBAVBMStopPlaying;
vbm->d.startRecording = GBAVBMStartRecording;
vbm->d.stopRecording = GBAVBMStopRecording;
vbm->d.isPlaying = GBAVBMIsPlaying;
vbm->d.isRecording = GBAVBMIsRecording;
vbm->d.nextFrame = GBAVBMNextFrame;
vbm->d.logInput = 0;
vbm->d.queryInput = GBAVBMQueryInput;
vbm->d.queryReset = GBAVBMQueryReset;
vbm->d.stateSaved = GBAVBMStateSaved;
vbm->d.stateLoaded = GBAVBMStateLoaded;
vbm->d.openSavedata = GBAVBMOpenSavedata;
vbm->d.openSavestate = GBAVBMOpenSavestate;
}
bool GBAVBMStartPlaying(struct GBARRContext* rr, bool autorecord) {
if (rr->isRecording(rr) || rr->isPlaying(rr) || autorecord) {
return false;
}
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
vbm->isPlaying = true;
vbm->vbmFile->seek(vbm->vbmFile, vbm->inputOffset, SEEK_SET);
return true;
}
void GBAVBMStopPlaying(struct GBARRContext* rr) {
if (!rr->isPlaying(rr)) {
return;
}
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
vbm->isPlaying = false;
}
bool GBAVBMStartRecording(struct GBARRContext* rr) {
UNUSED(rr);
return false;
}
void GBAVBMStopRecording(struct GBARRContext* rr) {
UNUSED(rr);
}
bool GBAVBMIsPlaying(const struct GBARRContext* rr) {
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
return vbm->isPlaying;
}
bool GBAVBMIsRecording(const struct GBARRContext* rr) {
UNUSED(rr);
return false;
}
void GBAVBMNextFrame(struct GBARRContext* rr) {
if (!rr->isPlaying(rr)) {
return;
}
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
vbm->vbmFile->seek(vbm->vbmFile, sizeof(uint16_t), SEEK_CUR);
}
uint16_t GBAVBMQueryInput(struct GBARRContext* rr) {
if (!rr->isPlaying(rr)) {
return 0;
}
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
uint16_t input;
vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input));
vbm->vbmFile->seek(vbm->vbmFile, -sizeof(input), SEEK_CUR);
return input & 0x3FF;
}
bool GBAVBMQueryReset(struct GBARRContext* rr) {
if (!rr->isPlaying(rr)) {
return false;
}
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
uint16_t input;
vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input));
vbm->vbmFile->seek(vbm->vbmFile, -sizeof(input), SEEK_CUR);
return input & 0x800;
}
void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) {
UNUSED(rr);
UNUSED(state);
}
void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) {
UNUSED(rr);
UNUSED(state);
}
struct VFile* GBAVBMOpenSavedata(struct GBARRContext* rr, int flags) {
UNUSED(flags);
#ifndef USE_ZLIB
UNUSED(rr);
return 0;
#else
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
off_t pos = vbm->vbmFile->seek(vbm->vbmFile, 0, SEEK_CUR);
uint32_t saveType, flashSize, sramOffset;
vbm->vbmFile->seek(vbm->vbmFile, 0x18, SEEK_SET);
vbm->vbmFile->read(vbm->vbmFile, &saveType, sizeof(saveType));
vbm->vbmFile->read(vbm->vbmFile, &flashSize, sizeof(flashSize));
vbm->vbmFile->seek(vbm->vbmFile, 0x38, SEEK_SET);
vbm->vbmFile->read(vbm->vbmFile, &sramOffset, sizeof(sramOffset));
if (!sramOffset) {
vbm->vbmFile->seek(vbm->vbmFile, pos, SEEK_SET);
return 0;
}
vbm->vbmFile->seek(vbm->vbmFile, sramOffset, SEEK_SET);
struct VFile* save = VFileMemChunk(0, 0);
size_t size;
switch (saveType) {
case 1:
size = SIZE_CART_SRAM;
break;
case 2:
size = flashSize;
if (size > SIZE_CART_FLASH1M) {
size = SIZE_CART_FLASH1M;
}
break;
case 3:
size = SIZE_CART_EEPROM;
break;
default:
size = SIZE_CART_FLASH1M;
break;
}
uLong zlen = vbm->inputOffset - sramOffset;
char buffer[8761];
char* zbuffer = malloc(zlen);
vbm->vbmFile->read(vbm->vbmFile, zbuffer, zlen);
z_stream zstr;
zstr.zalloc = Z_NULL;
zstr.zfree = Z_NULL;
zstr.opaque = Z_NULL;
zstr.avail_in = zlen;
zstr.next_in = (Bytef*) zbuffer;
zstr.avail_out = 0;
inflateInit2(&zstr, 31);
// Skip header, we know where the save file is
zstr.avail_out = sizeof(buffer);
zstr.next_out = (Bytef*) &buffer;
int err = inflate(&zstr, 0);
while (err != Z_STREAM_END && !zstr.avail_out) {
zstr.avail_out = sizeof(buffer);
zstr.next_out = (Bytef*) &buffer;
int err = inflate(&zstr, 0);
if (err < 0) {
break;
}
save->write(save, buffer, sizeof(buffer) - zstr.avail_out);
}
inflateEnd(&zstr);
vbm->vbmFile->seek(vbm->vbmFile, pos, SEEK_SET);
return save;
#endif
}
struct VFile* GBAVBMOpenSavestate(struct GBARRContext* rr, int flags) {
UNUSED(rr);
UNUSED(flags);
return 0;
}
void GBAVBMContextDestroy(struct GBARRContext* rr) {
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
if (vbm->vbmFile) {
vbm->vbmFile->close(vbm->vbmFile);
}
}
bool GBAVBMSetStream(struct GBAVBMContext* vbm, struct VFile* vf) {
vf->seek(vf, 0, SEEK_SET);
char magic[4];
vf->read(vf, magic, sizeof(magic));
if (memcmp(magic, VBM_MAGIC, sizeof(magic)) != 0) {
return false;
}
uint32_t id;
vf->read(vf, &id, sizeof(id));
if (id != 1) {
return false;
}
vf->seek(vf, 4, SEEK_CUR);
vf->read(vf, &vbm->d.frames, sizeof(vbm->d.frames));
vf->read(vf, &vbm->d.rrCount, sizeof(vbm->d.rrCount));
uint8_t flags;
vf->read(vf, &flags, sizeof(flags));
if (flags & 2) {
#ifdef USE_ZLIB
vbm->d.initFrom = INIT_FROM_SAVEGAME;
#else
// zlib is needed to parse the savegame
return false;
#endif
}
if (flags & 1) {
// Incompatible savestate format
return false;
}
vf->seek(vf, 1, SEEK_CUR);
vf->read(vf, &flags, sizeof(flags));
if ((flags & 0x7) != 1) {
// Non-GBA movie
return false;
}
// TODO: parse more flags
vf->seek(vf, 0x3C, SEEK_SET);
vf->read(vf, &vbm->inputOffset, sizeof(vbm->inputOffset));
vf->seek(vf, vbm->inputOffset, SEEK_SET);
vbm->vbmFile = vf;
return true;
}

View File

@ -7,7 +7,6 @@
#include <mgba/internal/arm/macros.h>
#include <mgba/internal/gba/io.h>
#include <mgba/internal/gba/rr/rr.h>
#include <mgba-util/memory.h>
#include <mgba-util/vfs.h>
@ -73,11 +72,6 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
GBAVideoSerialize(&gba->video, state);
GBAAudioSerialize(&gba->audio, state);
GBASavedataSerialize(&gba->memory.savedata, state);
state->associatedStreamId = 0;
if (gba->rr) {
gba->rr->stateSaved(gba->rr, state);
}
}
bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
@ -195,10 +189,6 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
GBAAudioDeserialize(&gba->audio, state);
GBASavedataDeserialize(&gba->memory.savedata, state);
if (gba->rr) {
gba->rr->stateLoaded(gba->rr, state);
}
gba->timing.reroot = gba->timing.root;
gba->timing.root = NULL;