ref: 70ba5d1d963b61a309735e9f56f36f156883927a
parent: 166acc4dfb5e27cff23b3672366b6979815f5dad
author: Snesrev <snesrev@protonmail.com>
date: Wed Aug 31 03:03:29 EDT 2022
Fix text scroll speed and crystal animation rate Possibly fixes #20 Crystal cutscene refill lag
--- a/ancilla.cpp
+++ b/ancilla.cpp
@@ -7187,6 +7187,11 @@
}
int Ancilla_AllocInit(uint8 type, uint8 y) { // 8ff577
+ // snes bug: R14 is used in tile detection already
+ // unless this is here it the memcmp will fail when entering/leaving a water through steps quickly
+ if (g_ram[kRam_BugsFixed] >= kBugFix_PolyRenderer)
+ BYTE(R14) = y + 1;
+
int n = 0;
for (int k = 0; k < 5; k++) {
if (ancilla_type[k] == type)
--- a/nmi.cpp
+++ b/nmi.cpp
@@ -100,10 +100,6 @@
if (is_nmi_thread_active) {
NMI_SwitchThread();
- if (thread_other_stack != 0x1f31)
- thread_other_stack = 0x1f31;
- else
- thread_other_stack = 0x1f2;
} else {
zelda_ppu_write(W12SEL, W12SEL_copy);
zelda_ppu_write(W34SEL, W34SEL_copy);
@@ -185,6 +181,7 @@
zelda_ppu_write(BG3VOFS, BG3VOFS_copy2 >> 8);
zelda_ppu_write(INIDISP, INIDISP_copy);
zelda_snes_dummy_write(HDMAEN, HDMAEN_copy);
+ thread_other_stack = (thread_other_stack != 0x1f31) ? 0x1f31 : 0x1f2;
}
void NMI_ReadJoypads(uint16 joypad_input) { // 8083d1
--- a/zelda_cpu_infra.cpp
+++ b/zelda_cpu_infra.cpp
@@ -103,6 +103,10 @@
b->ram[0xa0] = a->ram[0xa0];
b->ram[0x128] = a->ram[0x128]; // irq_flag
b->ram[0x463] = a->ram[0x463]; // which_staircase_index_padding
+
+ // c code is authoritative
+ WORD(a->ram[0x1f0a]) = WORD(b->ram[0x1f0a]);
+
memcpy(&b->ram[0x1f0d], &a->ram[0x1f0d], 0x3f - 0xd);
memcpy(b->ram + 0x138, a->ram + 0x138, 256 - 0x38); // copy the stack over
@@ -242,6 +246,12 @@
}
void RunOrigAsmCodeOneLoop(Snes *snes) {
+ Cpu *cpu = snes->cpu;
+ cpu->a = cpu->x = cpu->y = 0;
+ cpu->e = false;
+ cpu->irqWanted = cpu->nmiWanted = cpu->waiting = cpu->stopped = 0;
+ cpu_setFlags(cpu, 0x30);
+
// Run until the wait loop in Interrupt_Reset,
// Or the polyhedral main function.
for(int loops = 0;;loops++) {
@@ -251,23 +261,40 @@
dma_doDma(snes->dma);
uint32_t pc = snes->cpu->k << 16 | snes->cpu->pc;
- if (pc == 0x8034 || pc == 0x9f81d && loops >= 10)
+ if (pc == 0x8034 || pc == 0x9f81d && loops >= 10 || pc == 0x8225 || pc == 0x82D2)
break;
}
}
-void RunEmulatedSnesFrame(Snes *snes) {
+void RunEmulatedSnesFrame(Snes *snes, int run_what) {
// First call runs until init
if (snes->cpu->pc == 0x8000 && snes->cpu->k == 0) {
RunOrigAsmCodeOneLoop(snes);
g_emulated_ram[0x12] = 1;
-
// Fixup uninitialized variable
*(uint16*)(g_emulated_ram+0xAE0) = 0xb280;
*(uint16*)(g_emulated_ram+0xAE2) = 0xb280 + 0x60;
}
- RunOrigAsmCodeOneLoop(snes);
+ // Run poly code
+ if (run_what & 2) {
+ Cpu *cpu = snes->cpu;
+ cpu->sp = 0x1f3e;
+ cpu->pc = 0xf81d;
+ cpu->db = cpu->k = 9;
+ cpu->dp = 0x1f00;
+ RunOrigAsmCodeOneLoop(snes);
+ }
+
+ // Run main code
+ if (run_what & 1) {
+ Cpu *cpu = g_snes->cpu;
+ cpu->sp = 0x1ff;
+ cpu->pc = 0x8034;
+ cpu->k = cpu->dp = cpu->db = 0;
+ RunOrigAsmCodeOneLoop(snes);
+ }
+
snes_doAutoJoypad(snes);
// animated_tile_vram_addr uninited
@@ -277,17 +304,13 @@
// In one code path flag_update_hud_in_nmi uses an undefined value
snes_write(snes, DMAP0, 0x01);
snes_write(snes, BBAD0, 0x18);
- snes->cpu->nmiWanted = true;
- for (;;) {
- snes_printCpuLine(snes);
- cpu_runOpcode(snes->cpu);
- while (snes->dma->dmaBusy)
- dma_doDma(snes->dma);
- uint32_t pc = snes->cpu->k << 16 | snes->cpu->pc;
- if (pc == 0x8039 || pc == 0x9f81d)
- break;
- }
+ // Run NMI handler
+ Cpu *cpu = g_snes->cpu;
+ cpu->sp = 0x1ff;
+ cpu->pc = 0x80D9;
+ cpu->k = cpu->dp = cpu->db = 0;
+ RunOrigAsmCodeOneLoop(snes);
}
struct Ppu *GetPpuForRendering() {
@@ -332,27 +355,6 @@
memcpy(g_zenv.dma->channel, g_snes->dma->channel, sizeof(Dma) - offsetof(Dma, channel));
g_zenv.player->timer_cycles = 0;
-
- if (!is_reset) {
- // Setup some fake cpu state cause we can't depend on the savegame's
- Cpu *cpu = g_snes->cpu;
- cpu->a = cpu->x = cpu->y = 0;
- cpu->pc = 0x8034;
- cpu->sp = 0x1ff;
- cpu->k = cpu->dp = cpu->db = 0;
- cpu_setFlags(cpu, 0x30);
- cpu->irqWanted = cpu->nmiWanted = cpu->waiting = cpu->stopped = 0;
- cpu->e = false;
-
- if (thread_other_stack == 0x1f2) {
- cpu->sp = 0x1f3e;
- cpu->pc = 0xf81d;
- cpu->db = cpu->k = 9;
- cpu->dp = 0x1f00;
- static const uint8 kStackInit[] = { 0x82, 0, 0, 0, 0, 0, 0, 0, 0x40, 0xb7, 0xb0, 0x34, 0x80, 0 };
- memcpy(g_snes->ram + 0x1f2, kStackInit, sizeof(kStackInit));
- }
- }
}
std::vector<uint8> SaveSnesState() {
@@ -602,6 +604,12 @@
StateRecorder input_recorder;
static int frame_ctr;
+int IncrementCrystalCountdown(uint8 *a, int v) {
+ int t = *a + v;
+ *a = t;
+ return t >> 8;
+}
+
bool RunOneFrame(Snes *snes, int input_state, bool turbo) {
frame_ctr++;
@@ -619,14 +627,34 @@
// This is whether APUI00 is true or false, this is used by the ancilla code.
uint8 apui00 = g_zenv.player->port_to_snes[0] != 0;
- if (apui00 != g_ram[0x648]) {
- g_emulated_ram[0x648] = g_ram[0x648] = apui00;
- input_recorder.RecordPatchByte(0x648, &apui00, 1);
+ if (apui00 != g_ram[kRam_APUI00]) {
+ g_emulated_ram[kRam_APUI00] = g_ram[kRam_APUI00] = apui00;
+ input_recorder.RecordPatchByte(kRam_APUI00, &apui00, 1);
}
+
+ // Whenever we're no longer replaying, we'll remember what bugs were fixed,
+ // but only if game is initialized.
+ if (g_ram[kRam_BugsFixed] < kBugFix_Latest && animated_tile_data_src != 0) {
+ g_emulated_ram[kRam_BugsFixed] = g_ram[kRam_BugsFixed] = kBugFix_Latest;
+ input_recorder.RecordPatchByte(kRam_BugsFixed, &g_ram[kRam_BugsFixed], 1);
+ }
}
+ int run_what;
+ if (g_ram[kRam_BugsFixed] < kBugFix_PolyRenderer) {
+ // A previous version of this code alternated the game loop with
+ // the poly renderer.
+ run_what = (is_nmi_thread_active && thread_other_stack != 0x1f31) ? 2 : 1;
+ } else {
+ // The snes seems to let poly rendering run for a little
+ // while each fram until it eventually completes a frame.
+ // Simulate this by rendering the poly every n:th frame.
+ run_what = (is_nmi_thread_active && IncrementCrystalCountdown(&g_ram[kRam_CrystalRotateCounter], virq_trigger)) ? 3 : 1;
+ g_emulated_ram[kRam_CrystalRotateCounter] = g_ram[kRam_CrystalRotateCounter];
+ }
+
if (snes == NULL) {
- ZeldaRunFrame(input_state);
+ ZeldaRunFrame(input_state, run_what);
return turbo;
}
@@ -644,12 +672,12 @@
again:
// Run orig version then snapshot
snes->input1->currentState = input_state;
- RunEmulatedSnesFrame(snes);
+ RunEmulatedSnesFrame(snes, run_what);
MakeSnapshot(&g_snapshot_theirs);
// Run my version and snapshot
again_mine:
- ZeldaRunFrame(input_state);
+ ZeldaRunFrame(input_state, run_what);
MakeMySnapshot(&g_snapshot_mine);
--- a/zelda_rtl.cpp
+++ b/zelda_rtl.cpp
@@ -61,7 +61,7 @@
// from the apu and we don't want to make the core code
// dependent on the apu timings, so relocated this value
// to 0x648.
- return g_ram[0x648];
+ return g_ram[kRam_APUI00];
}
uint8_t zelda_apu_read(uint32_t adr) {
@@ -237,21 +237,22 @@
ppu_reset(g_zenv.ppu);
}
-void ZeldaRunFrame(uint16 input) {
+void ZeldaRunPolyLoop() {
+ if (intro_did_run_step && !nmi_flag_update_polyhedral) {
+ Poly_RunFrame();
+ intro_did_run_step = 0;
+ nmi_flag_update_polyhedral = 0xff;
+ }
+}
+
+void ZeldaRunFrame(uint16 input, int run_what) {
if (animated_tile_data_src == 0)
ZeldaInitializationCode();
- // When poly is active, the main game loop is not run. They alternate.
- if (is_nmi_thread_active && thread_other_stack != 0x1f31) {
- if (intro_did_run_step && !nmi_flag_update_polyhedral) {
- Poly_RunFrame();
- intro_did_run_step = 0;
- nmi_flag_update_polyhedral = 0xff;
- }
- } else {
+ if (run_what & 2)
+ ZeldaRunPolyLoop();
+ if (run_what & 1)
ZeldaRunGameLoop();
- }
-
Interrupt_NMI(input);
}
--- a/zelda_rtl.h
+++ b/zelda_rtl.h
@@ -91,9 +91,21 @@
};
-// Various level tables
+// Special RAM locations that are unused but I use for compat things.
+enum {
+ kRam_APUI00 = 0x648,
+ kRam_CrystalRotateCounter = 0x649,
+ kRam_BugsFixed = 0x64a,
+};
+enum {
+ // Poly rendered uses correct speed
+ kBugFix_PolyRenderer = 1,
+ kBugFix_AncillaOverwrites = 1,
+ kBugFix_Latest = 1,
+};
+
#define scratch_0 (*(uint16*)(g_ram+0x72))
#define scratch_1 (*(uint16*)(g_ram+0x74))
#define srm_var1 (*(uint16*)(g_zenv.sram+0x1ffe))
@@ -183,7 +195,7 @@
void ZeldaInitializationCode();
void ZeldaRunGameLoop();
void ZeldaInitialize();
-void ZeldaRunFrame(uint16 input);
+void ZeldaRunFrame(uint16 input, int run_what);
void ClearOamBuffer();
void Startup_InitializeMemory();
void LoadSongBank(const uint8 *p);