shithub: zelda3

Download patch

ref: 70ba5d1d963b61a309735e9f56f36f156883927a
parent: 166acc4dfb5e27cff23b3672366b6979815f5dad
author: Snesrev <[email protected]>
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);