719 lines
20 KiB
C
719 lines
20 KiB
C
#include "sm_rtl.h"
|
|
#include "sm_cpu_infra.h"
|
|
#include "types.h"
|
|
//#include "ida_types.h"
|
|
#include "variables.h"
|
|
#include "funcs.h"
|
|
#include "spc_player.h"
|
|
#include "util.h"
|
|
|
|
struct StateRecorder;
|
|
|
|
static void RtlSaveMusicStateToRam_Locked();
|
|
static void RtlRestoreMusicAfterLoad_Locked(bool is_reset);
|
|
|
|
uint8 g_ram[0x20000];
|
|
uint8 *g_sram;
|
|
|
|
static uint8 *g_rtl_memory_ptr;
|
|
static RunFrameFunc *g_rtl_runframe;
|
|
static SyncAllFunc *g_rtl_syncall;
|
|
|
|
void RtlSetupEmuCallbacks(uint8 *emu_ram, RunFrameFunc *func, SyncAllFunc *sync_all) {
|
|
g_rtl_memory_ptr = emu_ram;
|
|
g_rtl_runframe = func;
|
|
g_rtl_syncall = sync_all;
|
|
}
|
|
|
|
static void RtlSynchronizeWholeState(void) {
|
|
if (g_rtl_syncall)
|
|
g_rtl_syncall();
|
|
}
|
|
|
|
// |ptr| must be a pointer into g_ram, will synchronize the RAM memory with the
|
|
// emulator.
|
|
static void RtlSyncMemoryRegion(void *ptr, size_t n) {
|
|
uint8 *data = (uint8 *)ptr;
|
|
assert(data >= g_ram && data < g_ram + 0x20000);
|
|
if (g_rtl_memory_ptr)
|
|
memcpy(g_rtl_memory_ptr + (data - g_ram), data, n);
|
|
}
|
|
|
|
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, (uint8*)data, data_size);
|
|
}
|
|
|
|
typedef struct LoadFuncState {
|
|
uint8 *p, *pend;
|
|
} LoadFuncState;
|
|
|
|
void loadFunc(void *ctx, void *data, size_t data_size) {
|
|
LoadFuncState *st = (LoadFuncState *)ctx;
|
|
assert((size_t)(st->pend - st->p) >= data_size);
|
|
memcpy(data, st->p, data_size);
|
|
st->p += data_size;
|
|
}
|
|
static void LoadSnesState(SaveLoadFunc *func, void *ctx) {
|
|
// Do the actual loading
|
|
snes_saveload(g_snes, func, ctx);
|
|
|
|
g_snes->cpu->e = false;
|
|
uint32 next = (g_snes->ram[g_snes->cpu->sp + 3] | g_snes->ram[g_snes->cpu->sp + 4] << 8 | g_snes->ram[g_snes->cpu->sp + 5] << 16) + 1;
|
|
if (next == 0x82897e) {
|
|
g_snes->ram[g_snes->cpu->sp + 3] = (0xF71B - 1) & 0xff;
|
|
g_snes->ram[g_snes->cpu->sp + 4] = (0xF71B - 1) >> 8;
|
|
}
|
|
RtlSynchronizeWholeState();
|
|
}
|
|
|
|
static void SaveSnesState(SaveLoadFunc *func, void *ctx) {
|
|
snes_saveload(g_snes, func, ctx);
|
|
}
|
|
|
|
typedef struct StateRecorder {
|
|
uint16 last_inputs;
|
|
uint32 frames_since_last;
|
|
uint32 total_frames;
|
|
|
|
// For replay
|
|
uint32 replay_pos, replay_pos_last_complete;
|
|
uint32 replay_frame_counter;
|
|
uint32 replay_next_cmd_at;
|
|
uint32 snapshot_flags;
|
|
uint8 replay_cmd;
|
|
bool replay_mode;
|
|
|
|
ByteArray log;
|
|
ByteArray base_snapshot;
|
|
} StateRecorder;
|
|
|
|
static StateRecorder state_recorder;
|
|
|
|
void StateRecorder_Init(StateRecorder *sr) {
|
|
ByteArray_Destroy(&sr->log);
|
|
ByteArray_Destroy(&sr->base_snapshot);
|
|
memset(sr, 0, sizeof(*sr));
|
|
}
|
|
|
|
void StateRecorder_RecordCmd(StateRecorder *sr, uint8 cmd) {
|
|
int frames = sr->frames_since_last;
|
|
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) {
|
|
uint16 diff = inputs ^ sr->last_inputs;
|
|
if (diff != 0) {
|
|
sr->last_inputs = inputs;
|
|
// printf("0x%.4x %d: ", diff, sr->frames_since_last);
|
|
// size_t lb = sr->log.size;
|
|
for (int i = 0; i < 12; i++) {
|
|
if ((diff >> i) & 1)
|
|
StateRecorder_RecordCmd(sr, i << 4);
|
|
}
|
|
// while (lb < sr->log.size)
|
|
// printf("%.2x ", sr->log.data[lb++]);
|
|
// printf("\n");
|
|
}
|
|
sr->frames_since_last++;
|
|
sr->total_frames++;
|
|
}
|
|
|
|
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);
|
|
size_t lb = sr->log.size;
|
|
int lq = (num - 1) <= 3 ? (num - 1) : 3;
|
|
StateRecorder_RecordCmd(sr, 0xc0 | (addr & 0x10000 ? 2 : 0) | lq << 2);
|
|
if (lq == 3)
|
|
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++)
|
|
ByteArray_AppendByte(&sr->log, value[i]);
|
|
while (lb < sr->log.size)
|
|
printf("%.2x ", sr->log.data[lb++]);
|
|
printf("\n");
|
|
}
|
|
|
|
void ReadFromFile(FILE *f, void *data, size_t n) {
|
|
if (fread(data, 1, n, f) != n)
|
|
Die("fread failed\n");
|
|
}
|
|
|
|
void RtlReset(bool preserve_sram) {
|
|
snes_frame_counter = 0;
|
|
snes_reset(g_snes, true);
|
|
if (!preserve_sram)
|
|
memset(g_sram, 0, 0x2000);
|
|
|
|
coroutine_state_0 = 1;
|
|
|
|
RtlApuLock();
|
|
RtlRestoreMusicAfterLoad_Locked(true);
|
|
RtlApuUnlock();
|
|
|
|
RtlSynchronizeWholeState();
|
|
|
|
StateRecorder_Init(&state_recorder);
|
|
}
|
|
|
|
int GetFileSize(FILE *f) {
|
|
fseek(f, 0, SEEK_END);
|
|
int r = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
return r;
|
|
}
|
|
|
|
void StateRecorder_Load(StateRecorder *sr, FILE *f, bool replay_mode) {
|
|
uint32 hdr[16] = { 0 };
|
|
|
|
bool is_old = false;
|
|
bool is_reset = false;
|
|
|
|
ReadFromFile(f, hdr, 8 * sizeof(uint32));
|
|
if (hdr[0] != 2) {
|
|
hdr[8] = hdr[7];
|
|
hdr[7] = hdr[5] >> 1;
|
|
hdr[5] = (hdr[5] & 1) ? hdr[6] : 0;
|
|
} else if (hdr[0] == 2) {
|
|
ReadFromFile(f, hdr + 8, 8 * sizeof(uint32));
|
|
|
|
} else {
|
|
assert(0);
|
|
}
|
|
|
|
sr->total_frames = hdr[1];
|
|
ByteArray_Resize(&sr->log, hdr[2]);
|
|
ReadFromFile(f, sr->log.data, sr->log.size);
|
|
sr->last_inputs = hdr[3];
|
|
sr->frames_since_last = hdr[4];
|
|
|
|
ByteArray_Resize(&sr->base_snapshot, hdr[5]);
|
|
ReadFromFile(f, sr->base_snapshot.data, sr->base_snapshot.size);
|
|
|
|
sr->snapshot_flags = hdr[9];
|
|
sr->replay_next_cmd_at = 0;
|
|
sr->replay_mode = replay_mode;
|
|
if (replay_mode) {
|
|
sr->frames_since_last = 0;
|
|
sr->last_inputs = 0;
|
|
sr->replay_pos = sr->replay_pos_last_complete = 0;
|
|
sr->replay_frame_counter = 0;
|
|
// Load snapshot from |base_snapshot_|, or reset if empty.
|
|
if (sr->base_snapshot.size > 8192 ) {
|
|
LoadFuncState state = { sr->base_snapshot.data, sr->base_snapshot.data + sr->base_snapshot.size };
|
|
LoadSnesState(&loadFunc, &state);
|
|
assert(state.p == state.pend);
|
|
} else {
|
|
RtlReset(false);
|
|
if (sr->base_snapshot.size == 8192)
|
|
memcpy(g_sram, sr->base_snapshot.data, 8192);
|
|
is_reset = true;
|
|
}
|
|
} else {
|
|
// Resume replay from the saved position?
|
|
sr->replay_pos = sr->replay_pos_last_complete = hdr[7];
|
|
sr->replay_frame_counter = hdr[8];
|
|
sr->replay_mode = (sr->replay_frame_counter != 0);
|
|
|
|
ByteArray arr = { 0 };
|
|
ByteArray_Resize(&arr, hdr[6]);
|
|
ReadFromFile(f, arr.data, arr.size);
|
|
LoadFuncState state = { arr.data, arr.data + arr.size };
|
|
LoadSnesState(&loadFunc, &state);
|
|
ByteArray_Destroy(&arr);
|
|
assert(state.p == state.pend);
|
|
|
|
if (is_old)
|
|
RtlClearKeyLog();
|
|
}
|
|
|
|
if (!is_reset)
|
|
RtlRestoreMusicAfterLoad_Locked(false);
|
|
|
|
// Temporarily fix reset state
|
|
// if (g_snes->cpu->k == 0x82 && g_snes->cpu->pc == 0xf716)
|
|
// g_snes->cpu->pc = 0xf71c;
|
|
}
|
|
|
|
void StateRecorder_Save(StateRecorder *sr, FILE *f, bool saving_with_bug) {
|
|
uint32 hdr[16] = { 0 };
|
|
ByteArray arr = { 0 };
|
|
SaveSnesState(&saveFunc, &arr);
|
|
assert(sr->base_snapshot.size == 0 || sr->base_snapshot.size == arr.size || sr->base_snapshot.size == 8192);
|
|
|
|
hdr[0] = 2;
|
|
hdr[1] = sr->total_frames;
|
|
hdr[2] = (uint32)sr->log.size;
|
|
hdr[3] = sr->last_inputs;
|
|
hdr[4] = sr->frames_since_last;
|
|
hdr[5] = (uint32)sr->base_snapshot.size;
|
|
hdr[6] = (uint32)arr.size;
|
|
// 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[7] = sr->replay_pos_last_complete;
|
|
hdr[8] = sr->replay_frame_counter;
|
|
}
|
|
hdr[9] = saving_with_bug * 1;
|
|
fwrite(hdr, 1, sizeof(hdr), f);
|
|
fwrite(sr->log.data, 1, sr->log.size, f);
|
|
fwrite(sr->base_snapshot.data, 1, sr->base_snapshot.size, f);
|
|
fwrite(arr.data, 1, arr.size, f);
|
|
|
|
ByteArray_Destroy(&arr);
|
|
}
|
|
|
|
void StateRecorder_ClearKeyLog(StateRecorder *sr) {
|
|
printf("Clearing key log!\n");
|
|
sr->base_snapshot.size = 0;
|
|
SaveSnesState(&saveFunc, &sr->base_snapshot);
|
|
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;
|
|
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 = (uint32)sr->log.size;
|
|
StateRecorder_RecordCmd(sr, sr->replay_cmd);
|
|
int old_replay_pos = sr->replay_pos;
|
|
sr->replay_pos = (uint32)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) {
|
|
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) {
|
|
int nb = 1 + ((sr->replay_cmd >> 2) & 3);
|
|
uint8 t;
|
|
if (nb == 4) do {
|
|
nb += t = sr->log.data[replay_pos++];
|
|
} while (t == 255);
|
|
uint32 addr = ((sr->replay_cmd >> 1) & 1) << 16;
|
|
addr |= sr->log.data[replay_pos++] << 8;
|
|
addr |= sr->log.data[replay_pos++];
|
|
do {
|
|
g_ram[addr & 0x1ffff] = sr->log.data[replay_pos++];
|
|
RtlSyncMemoryRegion(&g_ram[addr & 0x1ffff], 1);
|
|
} while (addr++, --nb);
|
|
} else {
|
|
assert(0);
|
|
}
|
|
}
|
|
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[replay_pos++], t;
|
|
int mask = (cmd < 0xc0) ? 0xf : 0x1;
|
|
int frames = cmd & mask;
|
|
if (frames == mask) do {
|
|
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
|
|
if (++sr->replay_frame_counter >= sr->total_frames) {
|
|
sr->replay_mode = false;
|
|
}
|
|
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;
|
|
}
|
|
|
|
|
|
void RtlClearKeyLog(void) {
|
|
StateRecorder_ClearKeyLog(&state_recorder);
|
|
}
|
|
|
|
void RtlStopReplay(void) {
|
|
StateRecorder_StopReplay(&state_recorder);
|
|
}
|
|
|
|
bool RtlRunFrame(int inputs) {
|
|
// Avoid up/down and left/right from being pressed at the same time
|
|
if ((inputs & 0x30) == 0x30) inputs ^= 0x30;
|
|
if ((inputs & 0xc0) == 0xc0) inputs ^= 0xc0;
|
|
|
|
bool is_replay = state_recorder.replay_mode;
|
|
|
|
// Either copy state or apply state
|
|
if (is_replay) {
|
|
inputs = StateRecorder_ReadNextReplayState(&state_recorder);
|
|
} else {
|
|
// Loading a bug snapshot?
|
|
if (state_recorder.snapshot_flags & 1) {
|
|
state_recorder.snapshot_flags &= ~1;
|
|
inputs = state_recorder.last_inputs;
|
|
}
|
|
|
|
StateRecorder_Record(&state_recorder, inputs);
|
|
}
|
|
|
|
g_rtl_runframe(inputs, 0);
|
|
|
|
snes_frame_counter++;
|
|
|
|
RtlPushApuState();
|
|
return is_replay;
|
|
}
|
|
|
|
void RtlSaveSnapshot(const char *filename, bool saving_with_bug) {
|
|
FILE *f = fopen(filename, "wb");
|
|
RtlApuLock();
|
|
RtlSaveMusicStateToRam_Locked();
|
|
StateRecorder_Save(&state_recorder, f, saving_with_bug);
|
|
RtlApuUnlock();
|
|
fclose(f);
|
|
}
|
|
|
|
void RtlSaveLoad(int cmd, int slot) {
|
|
char name[128];
|
|
sprintf(name, "saves/save%d.sav", slot);
|
|
printf("*** %s slot %d\n",
|
|
cmd == kSaveLoad_Save ? "Saving" : cmd == kSaveLoad_Load ? "Loading" : "Replaying", slot);
|
|
if (cmd != kSaveLoad_Save) {
|
|
FILE *f = fopen(name, "rb");
|
|
if (f == NULL) {
|
|
printf("Failed fopen: %s\n", name);
|
|
return;
|
|
}
|
|
RtlApuLock();
|
|
StateRecorder_Load(&state_recorder, f, cmd == kSaveLoad_Replay);
|
|
ppu_copy(g_snes->my_ppu, g_snes->ppu);
|
|
RtlApuUnlock();
|
|
RtlSynchronizeWholeState();
|
|
fclose(f);
|
|
} else {
|
|
RtlSaveSnapshot(name, false);
|
|
}
|
|
}
|
|
|
|
void MemCpy(void *dst, const void *src, int size) {
|
|
memcpy(dst, src, size);
|
|
}
|
|
|
|
void Negate32(const uint16 *src_hi, const uint16 *src_lo, uint16 *dst_hi, uint16 *dst_lo) {
|
|
uint32 x = (uint32)*src_hi << 16 | *src_lo;
|
|
x = -(int)x;
|
|
*dst_lo = x;
|
|
*dst_hi = x >> 16;
|
|
}
|
|
|
|
PairU16 MakePairU16(uint16 k, uint16 j) {
|
|
PairU16 r = { k, j };
|
|
return r;
|
|
}
|
|
|
|
void mov24(struct LongPtr *a, unsigned int d) {
|
|
a->addr = d & 0xffff;
|
|
a->bank = d >> 16;
|
|
}
|
|
|
|
void copy24(LongPtr *dst, LongPtr *src) {
|
|
*dst = *src;
|
|
}
|
|
|
|
uint32 Load24(void *a) {
|
|
return *(uint32 *)a & 0xffffff;
|
|
}
|
|
|
|
void DecompressToMem_IpArg(const void *p) {
|
|
decompress_dst = *(LongPtr *)p;
|
|
DecompressToMem();
|
|
}
|
|
|
|
bool Unreachable(void) {
|
|
printf("Unreachable!\n");
|
|
assert(0);
|
|
g_ram[0x1ffff] = 1;
|
|
return false;
|
|
}
|
|
|
|
uint8_t *RomPtr(uint32_t addr) {
|
|
if (!(addr & 0x8000)) {
|
|
printf("RomPtr - Invalid access 0x%x!\n", addr);
|
|
g_fail = true;
|
|
}
|
|
return &g_snes->cart->rom[(((addr >> 16) << 15) | (addr & 0x7fff)) & (g_snes->cart->romSize - 1)];
|
|
}
|
|
|
|
uint8_t *IndirPtr(void *ptr, uint16 offs) {
|
|
uint32 a = (*(uint32 *)ptr & 0xffffff) + offs;
|
|
if ((a >> 16) >= 0x7e && (a >> 16) <= 0x7f || a < 0x2000) {
|
|
return &g_ram[a & 0x1ffff];
|
|
} else {
|
|
return RomPtr(a);
|
|
}
|
|
}
|
|
|
|
void IndirWriteWord(void *ptr, uint16 offs, uint16 value) {
|
|
*(uint16 *)IndirPtr(ptr, offs) = value;
|
|
}
|
|
|
|
void IndirWriteByte(void *ptr, uint16 offs, uint8 value) {
|
|
*IndirPtr(ptr, offs) = value;
|
|
}
|
|
|
|
void WriteReg(uint16 reg, uint8 value) {
|
|
snes_write(g_snes, reg, value);
|
|
}
|
|
|
|
uint16 Mult8x8(uint8 a, uint8 b) {
|
|
return a * b;
|
|
}
|
|
|
|
uint16 SnesDivide(uint16 a, uint8 b) {
|
|
return (b == 0) ? 0xffff : a / b;
|
|
}
|
|
|
|
uint16 SnesModulus(uint16 a, uint8 b) {
|
|
return (b == 0) ? a : a % b;
|
|
}
|
|
|
|
|
|
uint8 ReadReg(uint16 reg) {
|
|
return snes_read(g_snes, reg);
|
|
}
|
|
|
|
uint16 ReadRegWord(uint16 reg) {
|
|
uint16_t rv = ReadReg(reg);
|
|
rv |= ReadReg(reg + 1) << 8;
|
|
return rv;
|
|
}
|
|
|
|
void WriteRegWord(uint16 reg, uint16 value) {
|
|
WriteReg(reg, (uint8)value);
|
|
WriteReg(reg + 1, value >> 8);
|
|
}
|
|
|
|
// Maintain a queue cause the snes and audio callback are not in sync.
|
|
// If an entry is 255, it means unset.
|
|
typedef struct ApuWriteEnt {
|
|
uint8 ports[4];
|
|
} ApuWriteEnt;
|
|
|
|
enum {
|
|
kApuMaxQueueSize = 16,
|
|
};
|
|
|
|
static struct ApuWriteEnt g_apu_write_ents[kApuMaxQueueSize], g_apu_write;
|
|
static uint8 g_apu_write_ent_pos, g_apu_queue_size, g_apu_time_since_empty;
|
|
|
|
void RtlApuWrite(uint32 adr, uint8 val) {
|
|
assert(adr >= APUI00 && adr <= APUI03);
|
|
|
|
if (is_uploading_apu) {
|
|
snes_catchupApu(g_snes); // catch up the apu before writing
|
|
g_snes->apu->inPorts[adr & 0x3] = val;
|
|
return;
|
|
}
|
|
|
|
if (g_snes->runningWhichVersion != 2) {
|
|
g_apu_write.ports[adr & 0x3] = val;
|
|
}
|
|
}
|
|
|
|
static bool IsFrameEmpty(ApuWriteEnt *w) {
|
|
return (w->ports[0] == 255) && (w->ports[1] == 255) && (w->ports[2] == 255) && (w->ports[3] == 255);
|
|
}
|
|
|
|
void RtlPushApuState(void) {
|
|
RtlApuLock();
|
|
if (!is_uploading_apu) {
|
|
// Strive for the queue to be empty.
|
|
if (g_apu_queue_size == 0) {
|
|
g_apu_time_since_empty = 0;
|
|
} else {
|
|
if (g_apu_time_since_empty >= 32 && IsFrameEmpty(&g_apu_write)) {
|
|
g_apu_time_since_empty -= 4;
|
|
RtlApuUnlock();
|
|
return;
|
|
}
|
|
g_apu_time_since_empty++;
|
|
}
|
|
|
|
// Merge the two oldest to make space
|
|
ApuWriteEnt *w0 = &g_apu_write_ents[g_apu_write_ent_pos++ & (kApuMaxQueueSize - 1)];
|
|
if (g_apu_queue_size == kApuMaxQueueSize) {
|
|
ApuWriteEnt *w1 = &g_apu_write_ents[g_apu_write_ent_pos & (kApuMaxQueueSize - 1)];
|
|
for (int i = 0; i < 4; i++)
|
|
if (w1->ports[i] == 255)
|
|
w1->ports[i] = w0->ports[i];
|
|
} else {
|
|
g_apu_queue_size++;
|
|
}
|
|
*w0 = g_apu_write;
|
|
memset(&g_apu_write, 0xff, sizeof(g_apu_write));
|
|
} else {
|
|
g_apu_queue_size = 0;
|
|
}
|
|
RtlApuUnlock();
|
|
}
|
|
|
|
static void RtlPopApuState_Locked(void) {
|
|
if (is_uploading_apu)
|
|
return;
|
|
|
|
uint8 *input_ports = g_use_my_apu_code ? g_spc_player->input_ports : g_snes->apu->inPorts;
|
|
if (g_apu_queue_size != 0) {
|
|
ApuWriteEnt *w = &g_apu_write_ents[(g_apu_write_ent_pos - g_apu_queue_size--) & (kApuMaxQueueSize - 1)];
|
|
for (int i = 0; i != 4; i++) {
|
|
if (w->ports[i] != 255)
|
|
input_ports[i] = w->ports[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
static void RtlResetApuQueue(void) {
|
|
g_apu_write_ent_pos = g_apu_time_since_empty = g_apu_queue_size = 0;
|
|
memset(&g_apu_write, 0xff, sizeof(g_apu_write));
|
|
}
|
|
|
|
void RtlApuUpload(const uint8 *p) {
|
|
RtlApuLock();
|
|
RtlResetApuQueue();
|
|
SpcPlayer_Upload(g_spc_player, p);
|
|
RtlApuUnlock();
|
|
}
|
|
|
|
void RtlRestoreMusicAfterLoad_Locked(bool is_reset) {
|
|
if (g_use_my_apu_code) {
|
|
memcpy(g_spc_player->ram, g_snes->apu->ram, 65536);
|
|
memcpy(g_spc_player->dsp->ram, g_snes->apu->dsp->ram, sizeof(Dsp) - offsetof(Dsp, ram));
|
|
SpcPlayer_CopyVariablesFromRam(g_spc_player);
|
|
}
|
|
|
|
if (is_reset) {
|
|
SpcPlayer_Initialize(g_spc_player);
|
|
}
|
|
|
|
RtlResetApuQueue();
|
|
}
|
|
|
|
void RtlSaveMusicStateToRam_Locked(void) {
|
|
if (g_use_my_apu_code) {
|
|
// Apply the whole contents of the queue to input ports
|
|
SpcPlayer *spc_player = g_spc_player;
|
|
for (int i = g_apu_queue_size; i; i--) {
|
|
ApuWriteEnt *we = &g_apu_write_ents[(g_apu_write_ent_pos - i) & (kApuMaxQueueSize - 1)];
|
|
for (int j = 0; j < 4; j++) {
|
|
if (we->ports[j] != 255)
|
|
spc_player->input_ports[j] = we->ports[j];
|
|
}
|
|
}
|
|
SpcPlayer_CopyVariablesToRam(g_spc_player);
|
|
memcpy(g_snes->apu->dsp->ram, g_spc_player->dsp->ram, sizeof(Dsp) - offsetof(Dsp, ram));
|
|
memcpy(g_snes->apu->ram, g_spc_player->ram, 65536);
|
|
}
|
|
}
|
|
|
|
void RtlRenderAudio(int16 *audio_buffer, int samples, int channels) {
|
|
assert(channels == 2);
|
|
RtlApuLock();
|
|
|
|
RtlPopApuState_Locked();
|
|
|
|
if (!g_use_my_apu_code) {
|
|
if (!is_uploading_apu) {
|
|
while (g_snes->apu->dsp->sampleOffset < 534)
|
|
apu_cycle(g_snes->apu);
|
|
dsp_getSamples(g_snes->apu->dsp, audio_buffer, samples);
|
|
}
|
|
} else {
|
|
SpcPlayer_GenerateSamples(g_spc_player);
|
|
dsp_getSamples(g_spc_player->dsp, audio_buffer, samples);
|
|
}
|
|
|
|
RtlApuUnlock();
|
|
}
|
|
|
|
void RtlCheat(char c) {
|
|
if (c == 'w') {
|
|
samus_health = samus_max_health;
|
|
StateRecorder_RecordPatchByte(&state_recorder, 0x9C2, (uint8 *)&samus_health, 2);
|
|
} else if (c == 'q' && 0) {
|
|
samus_y_pos -= 32;
|
|
samus_y_speed = 0;
|
|
StateRecorder_RecordPatchByte(&state_recorder, 0xafa, (uint8 *)&samus_y_pos, 2);
|
|
StateRecorder_RecordPatchByte(&state_recorder, 0xb2e, (uint8 *)&samus_y_speed, 2);
|
|
menu_index = 0;
|
|
} else if (c == 'q') {
|
|
japanese_text_flag = !japanese_text_flag;
|
|
StateRecorder_RecordPatchByte(&state_recorder, 0x9e2, (uint8 *)&japanese_text_flag, 2);
|
|
}
|
|
}
|
|
|
|
void RtlReadSram(void) {
|
|
FILE *f = fopen("saves/sm.srm", "rb");
|
|
if (f) {
|
|
if (fread(g_sram, 1, 8192, f) != 8192)
|
|
fprintf(stderr, "Error reading saves/sm.srm\n");
|
|
fclose(f);
|
|
RtlSynchronizeWholeState();
|
|
ByteArray_Resize(&state_recorder.base_snapshot, 8192);
|
|
memcpy(state_recorder.base_snapshot.data, g_sram, 8192);
|
|
}
|
|
}
|
|
|
|
void RtlWriteSram(void) {
|
|
rename("saves/sm.srm", "saves/sm.srm.bak");
|
|
FILE *f = fopen("saves/sm.srm", "wb");
|
|
if (f) {
|
|
fwrite(g_sram, 1, 8192, f);
|
|
fclose(f);
|
|
} else {
|
|
fprintf(stderr, "Unable to write saves/sm.srm\n");
|
|
}
|
|
}
|