ref: d1248d9a7336c37599283bfca157fba4c5a2cdb7
parent: dd0c15589d5802bb39843c6a1454daa1a534963c
author: Snesrev <snesrev@protonmail.com>
date: Fri Sep 2 15:23:32 EDT 2022
Improve replay handling - Saving during a replay now resumes playback when loading - Press L to stop a replay - Press K to clear the log of pressed keys (to make replays shorter)
--- a/config.c
+++ b/config.c
@@ -31,8 +31,8 @@
_(SDLK_1), _(SDLK_2), _(SDLK_3), _(SDLK_4), _(SDLK_5), _(SDLK_6), _(SDLK_7), _(SDLK_8), _(SDLK_9), _(SDLK_0), _(SDLK_MINUS), _(SDLK_EQUALS), _(SDLK_BACKSPACE), N, N, N, N, N, N, N,
// Replay Ref State
C(SDLK_1), C(SDLK_2), C(SDLK_3), C(SDLK_4), C(SDLK_5), C(SDLK_6), C(SDLK_7), C(SDLK_8), C(SDLK_9), C(SDLK_0), C(SDLK_MINUS), C(SDLK_EQUALS), C(SDLK_BACKSPACE), N, N, N, N, N, N, N,
- // CheatLife, CheatKeys, MigrateSnapshot, Fullscreen, Reset, Pause, PauseDimmed, Turbo, ZoomIn, ZoomOut
- _(SDLK_w), _(SDLK_o), _(SDLK_k), A(SDLK_RETURN), _(SDLK_e), S(SDLK_p), _(SDLK_p), _(SDLK_t), N, N
+ // CheatLife, CheatKeys, ClearKeyLog, StopReplay, Fullscreen, Reset, Pause, PauseDimmed, Turbo, ZoomIn, ZoomOut
+ _(SDLK_w), _(SDLK_o), _(SDLK_k), _(SDLK_l), A(SDLK_RETURN), _(SDLK_e), S(SDLK_p), _(SDLK_p), _(SDLK_t), N, N
};
#undef _
#undef A
@@ -49,7 +49,7 @@
#define S(n) {#n, kKeys_##n, 1}
static const KeyNameId kKeyNameId[] = {
M(Controls), M(Load), M(Save), M(Replay), M(LoadRef), M(ReplayRef),
- S(CheatLife), S(CheatKeys), S(MigrateSnapshot), S(Fullscreen), S(Reset),
+ S(CheatLife), S(CheatKeys), S(ClearKeyLog), S(StopReplay), S(Fullscreen), S(Reset),
S(Pause), S(PauseDimmed), S(Turbo), S(ZoomIn), S(ZoomOut),
};
#undef S
--- a/config.h
+++ b/config.h
@@ -17,7 +17,8 @@
kKeys_ReplayRef_Last = kKeys_ReplayRef + 19,
kKeys_CheatLife,
kKeys_CheatKeys,
- kKeys_MigrateSnapshot,
+ kKeys_ClearKeyLog,
+ kKeys_StopReplay,
kKeys_Fullscreen,
kKeys_Reset,
kKeys_Pause,
--- a/main.c
+++ b/main.c
@@ -342,7 +342,8 @@
switch (j) {
case kKeys_CheatLife: PatchCommand('w'); break;
case kKeys_CheatKeys: PatchCommand('o'); break;
- case kKeys_MigrateSnapshot: PatchCommand('k'); break;
+ case kKeys_ClearKeyLog: PatchCommand('k'); break;
+ case kKeys_StopReplay: PatchCommand('l'); break;
case kKeys_Fullscreen:
g_win_flags ^= SDL_WINDOW_FULLSCREEN_DESKTOP;
SDL_SetWindowFullscreen(g_window, g_win_flags & SDL_WINDOW_FULLSCREEN_DESKTOP);
--- a/zelda3.ini
+++ b/zelda3.ini
@@ -13,7 +13,8 @@
CheatLife = w
CheatKeys = o
-MigrateSnapshot = k
+ClearKeyLog = k
+StopReplay = l
Fullscreen = Alt+Return
Reset = Ctrl+e
Pause = Shift+p
--- a/zelda_cpu_infra.c
+++ b/zelda_cpu_infra.c
@@ -339,12 +339,27 @@
arr->data = NULL;
}
-void saveFunc(void *ctx_in, void *data, size_t data_size) {
- ByteArray *arr = (ByteArray *)ctx_in;
+void ByteArray_AppendData(ByteArray *arr, const uint8 *data, size_t data_size) {
ByteArray_Resize(arr, arr->size + data_size);
memcpy(arr->data + arr->size - data_size, data, data_size);
}
+void ByteArray_AppendByte(ByteArray *arr, uint8 v) {
+ ByteArray_Resize(arr, arr->size + 1);
+ arr->data[arr->size - 1] = v;
+}
+
+void ByteArray_AppendVl(ByteArray *arr, uint32 v) {
+ for (; v >= 255; v -= 255)
+ ByteArray_AppendByte(arr, 255);
+ ByteArray_AppendByte(arr, v);
+}
+
+
+void saveFunc(void *ctx_in, void *data, size_t data_size) {
+ ByteArray_AppendData((ByteArray *)ctx_in, data, data_size);
+}
+
typedef struct LoadFuncState {
uint8 *p, *pend;
} LoadFuncState;
@@ -395,7 +410,7 @@
uint32 total_frames;
// For replay
- uint32 replay_pos;
+ uint32 replay_pos, replay_pos_last_complete;
uint32 replay_frame_counter;
uint32 replay_next_cmd_at;
uint8 replay_cmd;
@@ -411,24 +426,13 @@
memset(sr, 0, sizeof(*sr));
}
-void StateRecorder_AppendByte(StateRecorder *sr, uint8 v) {
- ByteArray_Resize(&sr->log, sr->log.size + 1);
- sr->log.data[sr->log.size - 1] = v;
- printf("%.2x ", v);
-}
-
-void StateRecorder_AppendVl(StateRecorder *sr, uint32 v) {
- for (; v >= 255; v -= 255)
- StateRecorder_AppendByte(sr, 255);
- StateRecorder_AppendByte(sr, v);
-}
-
-void StateRecorder_RecordJoypadBit(StateRecorder *sr, int command) {
+void StateRecorder_RecordCmd(StateRecorder *sr, uint8 cmd) {
int frames = sr->frames_since_last;
- StateRecorder_AppendByte(sr, command << 4 | (frames < 15 ? frames : 15));
- if (frames >= 15)
- StateRecorder_AppendVl(sr, frames - 15);
sr->frames_since_last = 0;
+ int x = (cmd < 0xc0) ? 0xf : 0x1;
+ ByteArray_AppendByte(&sr->log, cmd | (frames < x ? frames : x));
+ if (frames >= x)
+ ByteArray_AppendVl(&sr->log, frames - x);
}
void StateRecorder_Record(StateRecorder *sr, uint16 inputs) {
@@ -436,10 +440,13 @@
if (diff != 0) {
sr->last_inputs = inputs;
printf("0x%.4x %d: ", diff, sr->frames_since_last);
+ int lb = sr->log.size;
for (int i = 0; i < 12; i++) {
if ((diff >> i) & 1)
- StateRecorder_RecordJoypadBit(sr, i);
+ StateRecorder_RecordCmd(sr, i << 4);
}
+ while (lb < sr->log.size)
+ printf("%.2x ", sr->log.data[lb++]);
printf("\n");
}
sr->frames_since_last++;
@@ -448,27 +455,26 @@
void StateRecorder_RecordPatchByte(StateRecorder *sr, uint32 addr, const uint8 *value, int num) {
assert(addr < 0x20000);
+
printf("%d: PatchByte(0x%x, 0x%x. %d): ", sr->frames_since_last, addr, *value, num);
-
- int frames = sr->frames_since_last;
- sr->frames_since_last = 0;
-
+ int lb = sr->log.size;
int lq = (num - 1) <= 3 ? (num - 1) : 3;
- StateRecorder_AppendByte(sr, 0xc0 | (frames != 0 ? 1 : 0) | (addr & 0x10000 ? 2 : 0) | lq << 2);
- if (frames != 0)
- StateRecorder_AppendVl(sr, frames - 1);
+ StateRecorder_RecordCmd(sr, 0xc0 | (addr & 0x10000 ? 2 : 0) | lq << 2);
if (lq == 3)
- StateRecorder_AppendVl(sr, num - 1 - 3);
- StateRecorder_AppendByte(sr, addr >> 8);
- StateRecorder_AppendByte(sr, addr);
+ ByteArray_AppendVl(&sr->log, num - 1 - 3);
+ ByteArray_AppendByte(&sr->log, addr >> 8);
+ ByteArray_AppendByte(&sr->log, addr);
for(int i = 0; i < num; i++)
- StateRecorder_AppendByte(sr, value[i]);
+ ByteArray_AppendByte(&sr->log, value[i]);
+ while (lb < sr->log.size)
+ printf("%.2x ", sr->log.data[lb++]);
printf("\n");
}
void StateRecorder_Load(StateRecorder *sr, FILE *f, bool replay_mode) {
+ // todo: fix robustness on invalid data.
uint32 hdr[8] = { 0 };
- fread(hdr, 8, 4, f);
+ fread(hdr, 1, sizeof(hdr), f);
assert(hdr[0] == 1);
@@ -481,14 +487,15 @@
ByteArray_Resize(&sr->base_snapshot, (hdr[5] & 1) ? hdr[6] : 0);
fread(sr->base_snapshot.data, 1, sr->base_snapshot.size, f);
+ sr->replay_next_cmd_at = 0;
+
bool is_reset = false;
sr->replay_mode = replay_mode;
if (replay_mode) {
- sr->replay_next_cmd_at = sr->frames_since_last = 0;
+ sr->frames_since_last = 0;
sr->last_inputs = 0;
- sr->replay_pos = 0;
+ sr->replay_pos = sr->replay_pos_last_complete = 0;
sr->replay_frame_counter = 0;
- sr->replay_cmd = 0xff;
// Load snapshot from |base_snapshot_|, or reset if empty.
if (sr->base_snapshot.size) {
@@ -501,6 +508,11 @@
is_reset = true;
}
} else {
+ // Resume replay from the saved position?
+ sr->replay_pos = sr->replay_pos_last_complete = hdr[5] >> 1;
+ sr->replay_frame_counter = hdr[7];
+ sr->replay_mode = (sr->replay_frame_counter != 0);
+
ByteArray arr = { 0 };
ByteArray_Resize(&arr, hdr[6]);
fread(arr.data, 1, arr.size, f);
@@ -525,9 +537,15 @@
hdr[4] = sr->frames_since_last;
hdr[5] = sr->base_snapshot.size ? 1 : 0;
hdr[6] = arr.size;
-
- fwrite(hdr, 8, 4, f);
- fwrite(sr->log.data, 1, sr->log.size, f);
+ // If saving while in replay mode, also need to persist
+ // sr->replay_pos_last_complete and sr->replay_frame_counter
+ // so the replaying can be resumed.
+ if (sr->replay_mode) {
+ hdr[5] |= sr->replay_pos_last_complete << 1;
+ hdr[7] = sr->replay_frame_counter;
+ }
+ fwrite(hdr, 1, sizeof(hdr), f);
+ fwrite(sr->log.data, 1, hdr[2], f);
fwrite(sr->base_snapshot.data, 1, sr->base_snapshot.size, f);
fwrite(arr.data, 1, arr.size, f);
@@ -534,23 +552,49 @@
ByteArray_Destroy(&arr);
}
-void StateRecorder_MigrateToBaseSnapshot(StateRecorder *sr) {
- printf("Migrating to base snapshot!\n");
+void StateRecorder_ClearKeyLog(StateRecorder *sr) {
+ printf("Clearing key log!\n");
sr->base_snapshot.size = 0;
SaveSnesState(&sr->base_snapshot);
- sr->replay_mode = false;
+ ByteArray old_log = sr->log;
+ int old_frames_since_last = sr->frames_since_last;
+ memset(&sr->log, 0, sizeof(sr->log));
+ // If there are currently any active inputs, record them initially at timestamp 0.
sr->frames_since_last = 0;
- sr->last_inputs = 0;
- sr->total_frames = 0;
- sr->log.size = 0;
+ if (sr->last_inputs) {
+ for (int i = 0; i < 12; i++) {
+ if ((sr->last_inputs >> i) & 1)
+ StateRecorder_RecordCmd(sr, i << 4);
+ }
+ }
+ if (sr->replay_mode) {
+ // When clearing the key log while in replay mode, we want to keep
+ // replaying but discarding all key history up until this point.
+ if (sr->replay_next_cmd_at != 0xffffffff) {
+ sr->replay_next_cmd_at -= old_frames_since_last;
+ sr->frames_since_last = sr->replay_next_cmd_at;
+ sr->replay_pos_last_complete = sr->log.size;
+ StateRecorder_RecordCmd(sr, sr->replay_cmd);
+ int old_replay_pos = sr->replay_pos;
+ sr->replay_pos = sr->log.size;
+ ByteArray_AppendData(&sr->log, old_log.data + old_replay_pos, old_log.size - old_replay_pos);
+ }
+ sr->total_frames -= sr->replay_frame_counter;
+ sr->replay_frame_counter = 0;
+ } else {
+ sr->total_frames = 0;
+ }
+ ByteArray_Destroy(&old_log);
+ sr->frames_since_last = 0;
}
uint16 StateRecorder_ReadNextReplayState(StateRecorder *sr) {
assert(sr->replay_mode);
while (sr->frames_since_last >= sr->replay_next_cmd_at) {
- sr->frames_since_last = 0;
- // Apply next command
- if (sr->replay_cmd != 0xff) {
+ int replay_pos = sr->replay_pos;
+ if (replay_pos != sr->replay_pos_last_complete) {
+ // Apply next command
+ sr->frames_since_last = 0;
if (sr->replay_cmd < 0xc0) {
sr->last_inputs ^= 1 << (sr->replay_cmd >> 4);
} else if (sr->replay_cmd < 0xd0) {
@@ -557,32 +601,34 @@
int nb = 1 + ((sr->replay_cmd >> 2) & 3);
uint8 t;
if (nb == 4) do {
- nb += t = sr->log.data[sr->replay_pos++];
+ nb += t = sr->log.data[replay_pos++];
} while (t == 255);
uint32 addr = ((sr->replay_cmd >> 1) & 1) << 16;
- addr |= sr->log.data[sr->replay_pos++] << 8;
- addr |= sr->log.data[sr->replay_pos++];
+ addr |= sr->log.data[replay_pos++] << 8;
+ addr |= sr->log.data[replay_pos++];
do {
- g_emulated_ram[addr & 0x1ffff] = g_ram[addr & 0x1ffff] = sr->log.data[sr->replay_pos++];
+ g_emulated_ram[addr & 0x1ffff] = g_ram[addr & 0x1ffff] = sr->log.data[replay_pos++];
} while (addr++, --nb);
} else {
assert(0);
}
}
- if (sr->replay_pos >= sr->log.size) {
- sr->replay_cmd = 0xff;
+ sr->replay_pos_last_complete = replay_pos;
+ if (replay_pos >= sr->log.size) {
+ sr->replay_pos = replay_pos;
sr->replay_next_cmd_at = 0xffffffff;
break;
}
// Read the next one
- uint8 cmd = sr->log.data[sr->replay_pos++], t;
+ uint8 cmd = sr->log.data[replay_pos++], t;
int mask = (cmd < 0xc0) ? 0xf : 0x1;
int frames = cmd & mask;
if (frames == mask) do {
- frames += t = sr->log.data[sr->replay_pos++];
+ frames += t = sr->log.data[replay_pos++];
} while (t == 255);
sr->replay_next_cmd_at = frames;
sr->replay_cmd = cmd;
+ sr->replay_pos = replay_pos;
}
sr->frames_since_last++;
// Turn off replay mode after we reached the final frame position
@@ -592,6 +638,14 @@
return sr->last_inputs;
}
+void StateRecorder_StopReplay(StateRecorder *sr) {
+ if (!sr->replay_mode)
+ return;
+ sr->replay_mode = false;
+ sr->total_frames = sr->replay_frame_counter;
+ sr->log.size = sr->replay_pos_last_complete;
+}
+
static int frame_ctr;
int IncrementCrystalCountdown(uint8 *a, int v) {
@@ -766,7 +820,6 @@
rom[0xdc0f2] = target;
rom[0xdc0f3] = target >> 8;
rom[0xdc0f4] = target >> 16;
-
}
rom[0x2dec7] = 0; // Fix Uncle_Embark reading bad ram
@@ -921,9 +974,11 @@
StateRecoderMultiPatch_Patch(&mp, 0xf373, 80); // magic filler
// b.Patch(0x1FE01, 25);
} else if (c == 'k') {
- StateRecorder_MigrateToBaseSnapshot(&state_recorder);
+ StateRecorder_ClearKeyLog(&state_recorder);
} else if (c == 'o') {
StateRecoderMultiPatch_Patch(&mp, 0xf36f, 1);
+ } else if (c == 'l') {
+ StateRecorder_StopReplay(&state_recorder);
}
StateRecoderMultiPatch_Commit(&mp);
}