shithub: zelda3

Download patch

ref: 5d1360efa9bd226a8078aeb3e12e2e9b10699558
parent: e6b5294e8775120fa652e1cb1a636c9a034a5bf7
author: Snesrev <snesrev@protonmail.com>
date: Wed Sep 28 14:06:00 EDT 2022

Clean up and refactor the emulator vs own state

--- /dev/null
+++ b/features.h
@@ -1,0 +1,50 @@
+// This file declares extensions to the base game
+#ifndef ZELDA3_FEATURES_H_
+#define ZELDA3_FEATURES_H_
+
+#include "types.h"
+
+// Special RAM locations that are unused but I use for compat things.
+enum {
+  kRam_APUI00 = 0x648,
+  kRam_CrystalRotateCounter = 0x649,
+  kRam_BugsFixed = 0x64a,
+  kRam_Features0 = 0x64c,
+};
+
+enum {
+  // Poly rendered uses correct speed
+  kBugFix_PolyRenderer = 1,
+  kBugFix_AncillaOverwrites = 1,
+  kBugFix_Latest = 1,
+};
+
+// Enum values for kRam_Features0
+enum {
+  kFeatures0_ExtendScreen64 = 1,
+  kFeatures0_SwitchLR = 2,
+  kFeatures0_TurnWhileDashing = 4,
+  kFeatures0_MirrorToDarkworld = 8,
+  kFeatures0_CollectItemsWithSword = 16,
+  kFeatures0_BreakPotsWithSword = 32,
+  kFeatures0_DisableLowHealthBeep = 64,
+  kFeatures0_SkipIntroOnKeypress = 128,
+  kFeatures0_ShowMaxItemsInYellow = 256,
+  kFeatures0_MoreActiveBombs = 512,
+
+  // This is set for visual fixes that don't affect game behavior but will affect ram compare.
+  kFeatures0_WidescreenVisualFixes = 1024,
+};
+
+#define enhanced_features0 (*(uint32*)(g_ram+0x64c))
+#define msu_curr_sample (*(uint32*)(g_ram+0x650))
+#define msu_volume (*(uint8*)(g_ram+0x654))
+#define msu_track (*(uint8*)(g_ram+0x655))
+#define hud_cur_item_x (*(uint8*)(g_ram+0x656))
+#define hud_inventory_order ((uint8*)(g_ram + 0x225)) // 4x6 bytes
+
+extern uint32 g_wanted_zelda_features;
+extern bool msu_enabled;
+
+
+#endif  // ZELDA3_FEATURES_H_
--- a/main.c
+++ b/main.c
@@ -11,31 +11,21 @@
 #include <sys/types.h>
 #include <unistd.h>
 #endif
-#include <math.h>
-#include "snes/snes.h"
-#include "tracing.h"
 
+#include "snes/ppu.h"
+
 #include "types.h"
 #include "variables.h"
 
 #include "zelda_rtl.h"
+#include "zelda_cpu_infra.h"
+
 #include "config.h"
 #include "assets.h"
 
-extern Dsp *GetDspForRendering();
-extern Snes *g_snes;
-extern uint8 g_emulated_ram[0x20000];
-
-void PatchRom(uint8 *rom);
-void SetSnes(Snes *snes);
-void RunAudioPlayer();
-void CopyStateAfterSnapshotRestore(bool is_reset);
-void SaveLoadSlot(int cmd, int which);
-void PatchCommand(char cmd);
-bool RunOneFrame(Snes *snes, int input_state);
-
-static bool LoadRom(const char *name, Snes *snes);
-static void PlayAudio(Snes *snes, SDL_AudioDeviceID device, int channels, int16 *audioBuffer);
+// Forwards
+static bool LoadRom(const char *name);
+static void PlayAudio(SDL_AudioDeviceID device, int channels, int16 *audioBuffer);
 static void RenderScreen(SDL_Window *window, SDL_Renderer *renderer, SDL_Texture *texture, bool fullscreen);
 static void HandleInput(int keyCode, int modCode, bool pressed);
 static void HandleGamepadInput(int button, bool pressed);
@@ -285,18 +275,8 @@
     SDL_PauseAudioDevice(device, 0);
   }
 
-  Snes *snes = snes_init(g_emulated_ram), *snes_run = NULL;
-  if (argc >= 2 && !g_run_without_emu) {
-    // init snes, load rom
-    bool loaded = LoadRom(argv[1], snes);
-    if (!loaded) {
-      puts("No rom loaded");
-      return 1;
-    }
-    snes_run = snes;
-  } else {
-    snes_reset(snes, true);
-  }
+  if (argc >= 2 && !g_run_without_emu)
+    LoadRom(argv[1]);
 
 #if defined(_WIN32)
   _mkdir("saves");
@@ -304,8 +284,7 @@
   mkdir("saves", 0755);
 #endif
 
-  SetSnes(snes);
-  ZeldaReadSram(snes);
+  ZeldaReadSram();
 
   for (int i = 0; i < SDL_NumJoysticks(); i++)
     OpenOneGamepad(i);
@@ -371,12 +350,8 @@
       g_gamepad_buttons = 0;
     inputs |= g_gamepad_buttons;
 
-    // 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 = ZeldaRunFrame(inputs);
 
-    bool is_replay = RunOneFrame(snes_run, inputs);
-
     if ((g_turbo ^ (is_replay & g_replay_turbo)) && (frameCtr++ & (g_turbo ? 0xf : 0x7f)) != 0)
       continue;
 
@@ -383,7 +358,7 @@
 
     uint64 t1 = SDL_GetPerformanceCounter();
     if (audioBuffer)
-      PlayAudio(snes_run, device, have.channels, audioBuffer);
+      PlayAudio(device, have.channels, audioBuffer);
     uint64 t2 = SDL_GetPerformanceCounter();
 
     RenderScreen(window, renderer, texture, (g_win_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0);
@@ -421,8 +396,6 @@
   }
   if (g_config.autosave)
     SaveLoadSlot(kSaveLoad_Save, 0);
-  // clean snes
-  snes_free(snes);
   // clean sdl
   if (g_config.enable_audio) {
     SDL_PauseAudioDevice(device, 1);
@@ -438,20 +411,9 @@
 }
 
 
-static void PlayAudio(Snes *snes, SDL_AudioDeviceID device, int channels, int16 *audioBuffer) {
-  // generate enough samples
-  if (snes) {
-    while (snes->apu->dsp->sampleOffset < 534)
-      apu_cycle(snes->apu);
-    snes->apu->dsp->sampleOffset = 0;
-  }
+static void PlayAudio(SDL_AudioDeviceID device, int channels, int16 *audioBuffer) {
+  ZeldaRenderAudio(audioBuffer, g_samples_per_block, channels);
 
-  dsp_getSamples(GetDspForRendering(), audioBuffer, g_samples_per_block, channels);
-
-  // Mixin the msu data?
-  if (channels == 2)
-    MixinMsuAudioData(audioBuffer, g_samples_per_block);
-
   for (int i = 0; i < 10; i++) {
     if (SDL_GetQueuedAudioSize(device) <= g_samples_per_block * channels * sizeof(int16) * 6) {
       // don't queue audio if buffer is still filled
@@ -584,9 +546,7 @@
       SDL_ShowCursor(g_cursor);
       break;
     case kKeys_Reset:
-      snes_reset(g_snes, true);
-      ZeldaReadSram(g_snes);
-      CopyStateAfterSnapshotRestore(true);
+      ZeldaReset(true);
       break;
     case kKeys_Pause: g_paused = !g_paused; break;
     case kKeys_PauseDimmed: 
@@ -651,7 +611,8 @@
   // Determine the quadrant offset
   float q = (float)((~ux_s & uy_s) >> 29 | ux_s >> 30);
   // Calculate the arctangent in the first quadrant
-  float bxy_a = fabs(b * x * y);
+  float bxy_a = b * x * y;
+  if (bxy_a < 0.0f) bxy_a = -bxy_a;  // avoid fabs
   float num = bxy_a + y * y;
   float atan_1q = num / (x * x + bxy_a + num + 0.000001f);
   // Translate it to the proper quadrant
@@ -692,14 +653,11 @@
   }
 }
 
-static bool LoadRom(const char *name, Snes *snes) {
+static bool LoadRom(const char *filename) {
   size_t length = 0;
-  uint8 *file = ReadFile(name, &length);
+  uint8 *file = ReadFile(filename, &length);
   if(!file) Die("Failed to read file");
-
-  PatchRom(file);
-
-  bool result = snes_loadRom(snes, file, (int)length);
+  bool result = EmuInitialize(file, length);
   free(file);
   return result;
 }
--- a/snes/ppu.c
+++ b/snes/ppu.c
@@ -6,7 +6,6 @@
 #include <stddef.h>
 #include <assert.h>
 #include "ppu.h"
-#include "snes.h"
 #include "../types.h"
 
 static const uint8 kSpriteSizes[8][2] = {
@@ -24,9 +23,9 @@
 static uint16_t ppu_getVramRemap(Ppu* ppu);
 static void PpuDrawWholeLine(Ppu *ppu, uint y);
 
-Ppu* ppu_init(Snes* snes) {
+Ppu* ppu_init() {
   Ppu* ppu = (Ppu * )malloc(sizeof(Ppu));
-  ppu->snes = snes;
+  ppu->extraLeftRight = kPpuExtraLeftRight;
   return ppu;
 }
 
@@ -40,7 +39,6 @@
   ppu->lastMosaicModulo = 0xff;
   ppu->extraLeftCur = 0;
   ppu->extraRightCur = 0;
-  ppu->extraLeftRight = kPpuExtraLeftRight;
   ppu->extraBottomCur = 0;
   ppu->vramPointer = 0;
   ppu->vramIncrementOnHigh = false;
@@ -1403,13 +1401,10 @@
     }
     case 0x37: {
       // TODO: only when ppulatch is set
-      ppu->hCount = ppu->snes->hPos / 4;
-      ppu->vCount = ppu->snes->vPos;
+      ppu->hCount = 0;
+      ppu->vCount = 0;
       ppu->countersLatched = true;
-      if (ppu->snes->disableHpos)
-        ppu->vCount = 192;
-
-      return ppu->snes->openBus;
+      return 0xff;
     }
     case 0x38: {
       uint8_t ret = 0;
@@ -1498,7 +1493,7 @@
       return val;
     }
     default: {
-      return ppu->snes->openBus;
+      return 0xff;
     }
   }
 }
--- a/snes/ppu.h
+++ b/snes/ppu.h
@@ -1,6 +1,6 @@
 
-#ifndef PPU_H
-#define PPU_H
+#ifndef ZELDA3_SNES_PPU_H_
+#define ZELDA3_SNES_PPU_H_
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -7,10 +7,9 @@
 #include <string.h>
 #include <stdint.h>
 #include <stdbool.h>
-
+#include "saveload.h"
 typedef struct Ppu Ppu;
 
-#include "snes.h"
 #include "../types.h"
 
 typedef struct BgLayer {
@@ -66,8 +65,6 @@
   uint8_t *renderBuffer;
   uint8_t extraLeftCur, extraRightCur, extraLeftRight, extraBottomCur;
   float mode7PerspectiveLow, mode7PerspectiveHigh;
-
-  Snes* snes;
   // store 31 extra entries to remove the need for clamp
   uint8_t brightnessMult[32 + 31]; 
   uint8_t brightnessMultHalf[32 * 2];
@@ -162,7 +159,7 @@
   uint32_t colorMapRgb[256];
 };
 
-Ppu* ppu_init(Snes* snes);
+Ppu* ppu_init();
 void ppu_free(Ppu* ppu);
 void ppu_reset(Ppu* ppu);
 void ppu_handleVblank(Ppu* ppu);
@@ -175,4 +172,4 @@
 void PpuSetMode7PerspectiveCorrection(Ppu *ppu, int low, int high);
 void PpuSetExtraSideSpace(Ppu *ppu, int left, int right, int bottom);
 
-#endif
+#endif  // ZELDA3_SNES_PPU_H_
--- a/snes/snes.c
+++ b/snes/snes.c
@@ -15,7 +15,7 @@
 #include "ppu.h"
 #include "cart.h"
 #include "input.h"
-#include "../tracing.h"
+#include "tracing.h"
 #include "snes_regs.h"
 
 static const double apuCyclesPerMaster = (32040 * 32) / (1364 * 262 * 60.0);
@@ -26,7 +26,6 @@
 static void snes_writeReg(Snes* snes, uint16_t adr, uint8_t val);
 static uint8_t snes_rread(Snes* snes, uint32_t adr); // wrapped by read, to set open bus
 static int snes_getAccessTime(Snes* snes, uint32_t adr);
-void zelda_apu_runcycles();
 
 Snes* snes_init(uint8_t *ram) {
   Snes* snes = (Snes * )malloc(sizeof(Snes));
@@ -141,7 +140,6 @@
   int catchupCycles = (int) snes->apuCatchupCycles;
   for(int i = 0; i < catchupCycles; i++) {
     apu_cycle(snes->apu);
-    zelda_apu_runcycles();
   }
   snes->apuCatchupCycles -= (double) catchupCycles;
 }
@@ -170,7 +168,7 @@
     return ppu_read(snes->ppu, adr);
   }
   if(adr < 0x80) {
-    apu_cycle(snes->apu);//spc_runOpcode(snes->apu->spc);
+    //apu_cycle(snes->apu);//spc_runOpcode(snes->apu->spc);
     return snes->apu->outPorts[adr & 0x3];
   }
   if(adr == 0x80) {
--- a/snes/snes.h
+++ b/snes/snes.h
@@ -85,12 +85,6 @@
 bool snes_loadRom(Snes* snes, uint8_t* data, int length);
 void snes_saveload(Snes *snes, SaveLoadFunc *func, void *ctx);
 
-enum {
-  kSaveLoad_Save = 0,
-  kSaveLoad_Load = 1,
-  kSaveLoad_Replay = 2,
-};
-
 
 #endif
 
--- /dev/null
+++ b/snes/tracing.c
@@ -1,0 +1,208 @@
+
+#define _CRT_SECURE_NO_WARNINGS 1
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "tracing.h"
+#include "snes.h"
+#include "apu.h"
+
+// name for each opcode, to be filled in with sprintf (length = 14 (13+\0))
+static const char* opcodeNames[256] = {
+  "brk          ", "ora ($%02x,x)  ", "cop #$%02x     ", "ora $%02x,s    ", "tsb $%02x      ", "ora $%02x      ", "asl $%02x      ", "ora [$%02x]    ", "php          ", "ora #$%04x   ", "asl          ", "phd          ", "tsb $%04x    ", "ora $%04x    ", "asl $%04x    ", "ora $%06x  ",
+  "bpl $%04x    ", "ora ($%02x),y  ", "ora ($%02x)    ", "ora ($%02x,s),y", "trb $%02x      ", "ora $%02x,x    ", "asl $%02x,x    ", "ora [$%02x],y  ", "clc          ", "ora $%04x,y  ", "inc          ", "tcs          ", "trb $%04x    ", "ora $%04x,x  ", "asl $%04x,x  ", "ora $%06x,x",
+  "jsr $%04x    ", "and ($%02x,x)  ", "jsl $%06x  ", "and $%02x,s    ", "bit $%02x      ", "and $%02x      ", "rol $%02x      ", "and [$%02x]    ", "plp          ", "and #$%04x   ", "rol          ", "pld          ", "bit $%04x    ", "and $%04x    ", "rol $%04x    ", "and $%06x  ",
+  "bmi $%04x    ", "and ($%02x),y  ", "and ($%02x)    ", "and ($%02x,s),y", "bit $%02x,x    ", "and $%02x,x    ", "rol $%02x,x    ", "and [$%02x],y  ", "sec          ", "and $%04x,y  ", "dec          ", "tsc          ", "bit $%04x,x  ", "and $%04x,x  ", "rol $%04x,x  ", "and $%06x,x",
+  "rti          ", "eor ($%02x,x)  ", "wdm #$%02x     ", "eor $%02x,s    ", "mvp $%02x, $%02x ", "eor $%02x      ", "lsr $%02x      ", "eor [$%02x]    ", "pha          ", "eor #$%04x   ", "lsr          ", "phk          ", "jmp $%04x    ", "eor $%04x    ", "lsr $%04x    ", "eor $%06x  ",
+  "bvc $%04x    ", "eor ($%02x),y  ", "eor ($%02x)    ", "eor ($%02x,s),y", "mvn $%02x, $%02x ", "eor $%02x,x    ", "lsr $%02x,x    ", "eor [$%02x],y  ", "cli          ", "eor $%04x,y  ", "phy          ", "tcd          ", "jml $%06x  ", "eor $%04x,x  ", "lsr $%04x,x  ", "eor $%06x,x",
+  "rts          ", "adc ($%02x,x)  ", "per $%04x    ", "adc $%02x,s    ", "stz $%02x      ", "adc $%02x      ", "ror $%02x      ", "adc [$%02x]    ", "pla          ", "adc #$%04x   ", "ror          ", "rtl          ", "jmp ($%04x)  ", "adc $%04x    ", "ror $%04x    ", "adc $%06x  ",
+  "bvs $%04x    ", "adc ($%02x),y  ", "adc ($%02x)    ", "adc ($%02x,s),y", "stz $%02x,x    ", "adc $%02x,x    ", "ror $%02x,x    ", "adc [$%02x],y  ", "sei          ", "adc $%04x,y  ", "ply          ", "tdc          ", "jmp ($%04x,x)", "adc $%04x,x  ", "ror $%04x,x  ", "adc $%06x,x",
+  "bra $%04x    ", "sta ($%02x,x)  ", "brl $%04x    ", "sta $%02x,s    ", "sty $%02x      ", "sta $%02x      ", "stx $%02x      ", "sta [$%02x]    ", "dey          ", "bit #$%04x   ", "txa          ", "phb          ", "sty $%04x    ", "sta $%04x    ", "stx $%04x    ", "sta $%06x  ",
+  "bcc $%04x    ", "sta ($%02x),y  ", "sta ($%02x)    ", "sta ($%02x,s),y", "sty $%02x,x    ", "sta $%02x,x    ", "stx $%02x,y    ", "sta [$%02x],y  ", "tya          ", "sta $%04x,y  ", "txs          ", "txy          ", "stz $%04x    ", "sta $%04x,x  ", "stz $%04x,x  ", "sta $%06x,x",
+  "ldy #$%04x   ", "lda ($%02x,x)  ", "ldx #$%04x   ", "lda $%02x,s    ", "ldy $%02x      ", "lda $%02x      ", "ldx $%02x      ", "lda [$%02x]    ", "tay          ", "lda #$%04x   ", "tax          ", "plb          ", "ldy $%04x    ", "lda $%04x    ", "ldx $%04x    ", "lda $%06x  ",
+  "bcs $%04x    ", "lda ($%02x),y  ", "lda ($%02x)    ", "lda ($%02x,s),y", "ldy $%02x,x    ", "lda $%02x,x    ", "ldx $%02x,y    ", "lda [$%02x],y  ", "clv          ", "lda $%04x,y  ", "tsx          ", "tyx          ", "ldy $%04x,x  ", "lda $%04x,x  ", "ldx $%04x,y  ", "lda $%06x,x",
+  "cpy #$%04x   ", "cmp ($%02x,x)  ", "rep #$%02x     ", "cmp $%02x,s    ", "cpy $%02x      ", "cmp $%02x      ", "dec $%02x      ", "cmp [$%02x]    ", "iny          ", "cmp #$%04x   ", "dex          ", "wai          ", "cpy $%04x    ", "cmp $%04x    ", "dec $%04x    ", "cmp $%06x  ",
+  "bne $%04x    ", "cmp ($%02x),y  ", "cmp ($%02x)    ", "cmp ($%02x,s),y", "pei $%02x      ", "cmp $%02x,x    ", "dec $%02x,x    ", "cmp [$%02x],y  ", "cld          ", "cmp $%04x,y  ", "phx          ", "stp          ", "jml [$%04x]  ", "cmp $%04x,x  ", "dec $%04x,x  ", "cmp $%06x,x",
+  "cpx #$%04x   ", "sbc ($%02x,x)  ", "sep #$%02x     ", "sbc $%02x,s    ", "cpx $%02x      ", "sbc $%02x      ", "inc $%02x      ", "sbc [$%02x]    ", "inx          ", "sbc #$%04x   ", "nop          ", "xba          ", "cpx $%04x    ", "sbc $%04x    ", "inc $%04x    ", "sbc $%06x  ",
+  "beq $%04x    ", "sbc ($%02x),y  ", "sbc ($%02x)    ", "sbc ($%02x,s),y", "pea #$%04x   ", "sbc $%02x,x    ", "inc $%02x,x    ", "sbc [$%02x],y  ", "sed          ", "sbc $%04x,y  ", "plx          ", "xce          ", "jsr ($%04x,x)", "sbc $%04x,x  ", "inc $%04x,x  ", "sbc $%06x,x"
+};
+
+// for 8/16 bit immediates
+// TODO: probably a better way to do this...
+static const char* opcodeNamesSp[256] = {
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "ora #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "and #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "eor #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "adc #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "bit #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+  "ldy #$%02x     ", NULL, "ldx #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL, "lda #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+  "cpy #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "cmp #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+  "cpx #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "sbc #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
+  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+
+// address types for each opcode
+static const int opcodeType[256] = {
+  0, 1, 1, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
+  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
+  2, 1, 3, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
+  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
+  0, 1, 1, 1, 8, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
+  6, 1, 1, 1, 8, 1, 1, 1, 0, 2, 0, 0, 3, 2, 2, 3,
+  0, 1, 7, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
+  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
+  6, 1, 7, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
+  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
+  5, 1, 5, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
+  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
+  5, 1, 1, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
+  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
+  5, 1, 1, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
+  6, 1, 1, 1, 2, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3
+};
+
+// name for each opcode, for spc
+static const char* opcodeNamesSpc[256] = {
+  "nop              ", "tcall 0          ", "set1 $%02x.0       ", "bbs $%02x.0, $%04x ", "or a, $%02x        ", "or a, $%04x      ", "or a, [X]        ", "or a, [$%02x+x]    ", "or a, #$%02x       ", "or $%02x, $%02x      ", "or1 c, $%04x.%01x   ", "asl $%02x          ", "asl $%04x        ", "push p           ", "tset $%04x       ", "brk              ",
+  "bpl $%04x        ", "tcall 1          ", "clr1 $%02x.0       ", "bbc $%02x.0, $%04x ", "or a, $%02x+x      ", "or a, $%04x+x    ", "or a, $%04x+y    ", "or a, [$%02x]+y    ", "or $%02x, #$%02x     ", "or [X], [Y]      ", "decw $%02x         ", "asl $%02x+x        ", "asl a            ", "dec x            ", "cmp x, $%04x     ", "jmp [$%04x+x]    ",
+  "clrp             ", "tcall 2          ", "set1 $%02x.1       ", "bbs $%02x.1, $%04x ", "and a, $%02x       ", "and a, $%04x     ", "and a, [X]       ", "and a, [$%02x+x]   ", "and a, #$%02x      ", "and $%02x, $%02x     ", "or1 c, /$%04x.%01x  ", "rol $%02x          ", "rol $%04x        ", "push a           ", "cbne $%02x, $%04x  ", "bra $%04x        ",
+  "bmi $%04x        ", "tcall 3          ", "clr1 $%02x.1       ", "bbc $%02x.1, $%04x ", "and a, $%02x+x     ", "and a, $%04x+x   ", "and a, $%04x+y   ", "and a, [$%02x]+y   ", "and $%02x, #$%02x    ", "and [X], [Y]     ", "incw $%02x         ", "rol $%02x+x        ", "rol a            ", "inc x            ", "cmp x, $%02x       ", "call $%04x       ",
+  "setp             ", "tcall 4          ", "set1 $%02x.2       ", "bbs $%02x.2, $%04x ", "eor a, $%02x       ", "eor a, $%04x     ", "eor a, [X]       ", "eor a, [$%02x+x]   ", "eor a, #$%02x      ", "eor $%02x, $%02x     ", "and1 c, $%04x.%01x  ", "lsr $%02x          ", "lsr $%04x        ", "push x           ", "tclr $%04x       ", "pcall $%02x        ",
+  "bvc $%04x        ", "tcall 5          ", "clr1 $%02x.2       ", "bbc $%02x.2, $%04x ", "eor a, $%02x+x     ", "eor a, $%04x+x   ", "eor a, $%04x+y   ", "eor a, [$%02x]+y   ", "eor $%02x, #$%02x    ", "eor [X], [Y]     ", "cmpw ya, $%02x     ", "lsr $%02x+x        ", "lsr a            ", "mov x, a         ", "cmp y, $%04x     ", "jmp $%04x        ",
+  "clrc             ", "tcall 6          ", "set1 $%02x.3       ", "bbs $%02x.3, $%04x ", "cmp a, $%02x       ", "cmp a, $%04x     ", "cmp a, [X]       ", "cmp a, [$%02x+x]   ", "cmp a, #$%02x      ", "cmp $%02x, $%02x     ", "and1 c, /$%04x.%01x ", "ror $%02x          ", "ror $%04x        ", "push y           ", "dbnz $%02x, $%04x  ", "ret              ",
+  "bvs $%04x        ", "tcall 7          ", "clr1 $%02x.3       ", "bbc $%02x.3, $%04x ", "cmp a, $%02x+x     ", "cmp a, $%04x+x   ", "cmp a, $%04x+y   ", "cmp a, [$%02x]+y   ", "cmp $%02x, #$%02x    ", "cmp [X], [Y]     ", "addw ya, $%02x     ", "ror $%02x+x        ", "ror a            ", "mov a, x         ", "cmp y, $%02x       ", "reti             ",
+  "setc             ", "tcall 8          ", "set1 $%02x.4       ", "bbs $%02x.4, $%04x ", "adc a, $%02x       ", "adc a, $%04x     ", "adc a, [X]       ", "adc a, [$%02x+x]   ", "adc a, #$%02x      ", "adc $%02x, $%02x     ", "eor1 c, $%04x.%01x  ", "dec $%02x          ", "dec $%04x        ", "mov y, #$%02x      ", "pop p            ", "mov $%02x, #$%02x    ",
+  "bcc $%04x        ", "tcall 9          ", "clr1 $%02x.4       ", "bbc $%02x.4, $%04x ", "adc a, $%02x+x     ", "adc a, $%04x+x   ", "adc a, $%04x+y   ", "adc a, [$%02x]+y   ", "adc $%02x, #$%02x    ", "adc [X], [Y]     ", "subw ya, $%02x     ", "dec $%02x+x        ", "dec a            ", "mov x, sp        ", "div ya, x        ", "xcn a            ",
+  "ei               ", "tcall 10         ", "set1 $%02x.5       ", "bbs $%02x.5, $%04x ", "sbc a, $%02x       ", "sbc a, $%04x     ", "sbc a, [X]       ", "sbc a, [$%02x+x]   ", "sbc a, #$%02x      ", "sbc $%02x, $%02x     ", "mov1 c, $%04x.%01x  ", "inc $%02x          ", "inc $%04x        ", "cmp y, #$%02x      ", "pop a            ", "mov [x+], a      ",
+  "bcs $%04x        ", "tcall 11         ", "clr1 $%02x.5       ", "bbc $%02x.5, $%04x ", "sbc a, $%02x+x     ", "sbc a, $%04x+x   ", "sbc a, $%04x+y   ", "sbc a, [$%02x]+y   ", "sbc $%02x, #$%02x    ", "sbc [X], [Y]     ", "movw ya, $%02x     ", "inc $%02x+x        ", "inc a            ", "mov sp, x        ", "das a            ", "mov a, [x+]      ",
+  "di               ", "tcall 12         ", "set1 $%02x.6       ", "bbs $%02x.6, $%04x ", "mov $%02x, a       ", "mov $%04x, a     ", "mov [X], a       ", "mov [$%02x+x], a   ", "cmp x, #$%02x      ", "mov $%04x, x     ", "mov1 $%04x.%01x, c  ", "mov $%02x, y       ", "mov $%04x, y     ", "mov x, #$%02x      ", "pop x            ", "mul ya           ",
+  "bne $%04x        ", "tcall 13         ", "clr1 $%02x.6       ", "bbc $%02x.6, $%04x ", "mov $%02x+x, a     ", "mov $%04x+x, a   ", "mov $%04x+y, a   ", "mov [$%02x]+y, a   ", "mov $%02x, x       ", "mov $%02x+y, x     ", "movw $%02x, ya     ", "mov $%02x+x, y     ", "dec y            ", "mov a, y         ", "cbne $%02x+x, $%04x", "daa a            ",
+  "clrv             ", "tcall 14         ", "set1 $%02x.7       ", "bbs $%02x.7, $%04x ", "mov a, $%02x       ", "mov a, $%04x     ", "mov a, [X]       ", "mov a, [$%02x+x]   ", "mov a, #$%02x      ", "mov x, $%04x     ", "not1 $%04x.%01x     ", "mov y, $%02x       ", "mov y, $%04x     ", "notc             ", "pop y            ", "sleep            ",
+  "beq $%04x        ", "tcall 15         ", "clr1 $%02x.7       ", "bbc $%02x.7, $%04x ", "mov a, $%02x+x     ", "mov a, $%04x+x   ", "mov a, $%04x+y   ", "mov a, [$%02x]+y   ", "mov x, $%02x       ", "mov x, $%02x+y     ", "mov $%02x, $%02x     ", "mov y, $%02x+x     ", "inc y            ", "mov y, a         ", "dbnz y, $%04x    ", "stop             "
+};
+
+// address types for each opcode, for spc
+static const int opcodeTypeSpc[256] = {
+  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 0, 2, 0,
+  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 2, 2,
+  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 0, 5, 3,
+  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 1, 2,
+  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 0, 2, 1,
+  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 2, 2,
+  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 0, 5, 0,
+  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 1, 0,
+  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 1, 0, 4,
+  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 0, 0,
+  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 1, 0, 0,
+  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 0, 0,
+  0, 0, 1, 5, 1, 2, 0, 1, 1, 2, 6, 1, 2, 1, 0, 0,
+  3, 0, 1, 5, 1, 2, 2, 1, 1, 1, 1, 1, 0, 0, 5, 0,
+  0, 0, 1, 5, 1, 2, 0, 1, 1, 2, 6, 1, 2, 0, 0, 0,
+  3, 0, 1, 5, 1, 2, 2, 1, 1, 1, 4, 1, 0, 0, 3, 0
+};
+
+static void getDisassemblyCpu(Snes* snes, char* line);
+static void getDisassemblySpc(Apu *apu, char* line);
+
+void getProcessorStateCpu(Snes* snes, char* line) {
+  // 0        1         2         3         4         5         6         7         8
+  // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
+  // CPU 12:3456 1234567890123 A:1234 X:1234 Y:1234 SP:1234 DP:1234 DP:12 e nvmxdizc
+  char disLine[14] = "             ";
+  getDisassemblyCpu(snes, disLine);
+  sprintf(
+    line, "CPU %02x:%04x %s A:%04x X:%04x Y:%04x SP:%04x DP:%04x DB:%02x %c %c%c%c%c%c%c%c%c",
+    snes->cpu->k, snes->cpu->pc, disLine, snes->cpu->a, snes->cpu->x, snes->cpu->y,
+    snes->cpu->sp, snes->cpu->dp, snes->cpu->db, snes->cpu->e ? 'E' : 'e',
+    snes->cpu->n ? 'N' : 'n', snes->cpu->v ? 'V' : 'v', snes->cpu->mf ? 'M' : 'm', snes->cpu->xf ? 'X' : 'x',
+    snes->cpu->d ? 'D' : 'd', snes->cpu->i ? 'I' : 'i', snes->cpu->z ? 'Z' : 'z', snes->cpu->c ? 'C' : 'c'
+  );
+}
+
+void getProcessorStateSpc(Apu *apu, char* line) {
+  // 0        1         2         3         4         5         6         7         8
+  // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
+  // SPC 3456 12345678901234567 A:12 X:12 Y:12 SP:12 nvpbhizc
+  char disLine[18] = "                 ";
+  getDisassemblySpc(apu, disLine);
+  sprintf(
+    line, "SPC %04x %s A:%02x X:%02x Y:%02x SP:%02x %c%c%c%c%c%c%c%c",
+    apu->spc->pc, disLine, apu->spc->a, apu->spc->x, apu->spc->y, apu->spc->sp,
+    apu->spc->n ? 'N' : 'n', apu->spc->v ? 'V' : 'v', apu->spc->p ? 'P' : 'p', apu->spc->b ? 'B' : 'b',
+    apu->spc->h ? 'H' : 'h', apu->spc->i ? 'I' : 'i', apu->spc->z ? 'Z' : 'z', apu->spc->c ? 'C' : 'c'
+  );
+}
+
+static void getDisassemblyCpu(Snes* snes, char* line) {
+  uint32_t adr = snes->cpu->pc | (snes->cpu->k << 16);
+  // read 4 bytes
+  // TODO: this can have side effects, implement and use peaking
+  uint8_t opcode = snes_read(snes, adr);
+  uint8_t byte = snes_read(snes, (adr + 1) & 0xffffff);
+  uint8_t byte2 = snes_read(snes, (adr + 2) & 0xffffff);
+  uint16_t word = (byte2 << 8) | byte;
+  uint32_t longv = (snes_read(snes, (adr + 3) & 0xffffff) << 16) | word;
+  uint16_t rel = snes->cpu->pc + 2 + (int8_t) byte;
+  uint16_t rell = snes->cpu->pc + 3 + (int16_t) word;
+  // switch on type
+  switch(opcodeType[opcode]) {
+    case 0: sprintf(line, "%s", opcodeNames[opcode]); break;
+    case 1: sprintf(line, opcodeNames[opcode], byte); break;
+    case 2: sprintf(line, opcodeNames[opcode], word); break;
+    case 3: sprintf(line, opcodeNames[opcode], longv); break;
+    case 4: {
+      if(snes->cpu->mf) {
+        sprintf(line, opcodeNamesSp[opcode], byte);
+      } else {
+        sprintf(line, opcodeNames[opcode], word);
+      }
+      break;
+    }
+    case 5: {
+      if(snes->cpu->xf) {
+        sprintf(line, opcodeNamesSp[opcode], byte);
+      } else {
+        sprintf(line, opcodeNames[opcode], word);
+      }
+      break;
+    }
+    case 6: sprintf(line, opcodeNames[opcode], rel); break;
+    case 7: sprintf(line, opcodeNames[opcode], rell); break;
+    case 8: sprintf(line, opcodeNames[opcode], byte2, byte); break;
+  }
+}
+
+void getDisassemblySpc(Apu *apu, char* line) {
+  uint16_t adr = apu->spc->pc;
+  // read 3 bytes
+  // TODO: this can have side effects, implement and use peaking
+  uint8_t opcode = apu_cpuRead(apu, adr);
+  uint8_t byte = apu_cpuRead(apu, (adr + 1) & 0xffff);
+  uint8_t byte2 = apu_cpuRead(apu, (adr + 2) & 0xffff);
+  uint16_t word = (byte2 << 8) | byte;
+  uint16_t rel = apu->spc->pc + 2 + (int8_t) byte;
+  uint16_t rel2 = apu->spc->pc + 2 + (int8_t) byte2;
+  uint16_t wordb = word & 0x1fff;
+  uint8_t bit = word >> 13;
+  // switch on type
+  switch(opcodeTypeSpc[opcode]) {
+    case 0: sprintf(line, "%s", opcodeNamesSpc[opcode]); break;
+    case 1: sprintf(line, opcodeNamesSpc[opcode], byte); break;
+    case 2: sprintf(line, opcodeNamesSpc[opcode], word); break;
+    case 3: sprintf(line, opcodeNamesSpc[opcode], rel); break;
+    case 4: sprintf(line, opcodeNamesSpc[opcode], byte2, byte); break;
+    case 5: sprintf(line, opcodeNamesSpc[opcode], byte, rel2); break;
+    case 6: sprintf(line, opcodeNamesSpc[opcode], wordb, bit); break;
+  }
+}
--- /dev/null
+++ b/snes/tracing.h
@@ -1,0 +1,17 @@
+
+#ifndef TRACING_H
+#define TRACING_H
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "snes.h"
+
+void getProcessorStateCpu(Snes* snes, char* line);
+void getProcessorStateSpc(Apu* apu, char* line);
+
+#endif
--- a/spc_player.c
+++ b/spc_player.c
@@ -1,13 +1,11 @@
 #include <stdint.h>
 #include <string.h>
 #include <stdlib.h>
-#include <SDL.h>
 #include <assert.h>
 #include "types.h"
 
 #include "snes/spc.h"
 #include "snes/dsp_regs.h"
-#include "tracing.h"
 
 #include "spc_player.h"
 
@@ -1274,7 +1272,12 @@
 }
 
 // =======================================
+#define WITH_SPC_PLAYER_DEBUGGING 0
 
+#if WITH_SPC_PLAYER_DEBUGGING
+
+#include <SDL.h>
+
 static DspRegWriteHistory my_write_hist;
 static SpcPlayer my_spc, my_spc_snapshot;
 static int loop_ctr;
@@ -1442,4 +1445,5 @@
       }
     }
   }
-}
\ No newline at end of file
+}
+#endif  // WITH_SPC_PLAYER_DEBUGGING
\ No newline at end of file
--- a/tracing.c
+++ /dev/null
@@ -1,208 +1,0 @@
-
-#define _CRT_SECURE_NO_WARNINGS 1
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdint.h>
-#include <stdbool.h>
-
-#include "tracing.h"
-#include "snes/snes.h"
-#include "snes/apu.h"
-
-// name for each opcode, to be filled in with sprintf (length = 14 (13+\0))
-static const char* opcodeNames[256] = {
-  "brk          ", "ora ($%02x,x)  ", "cop #$%02x     ", "ora $%02x,s    ", "tsb $%02x      ", "ora $%02x      ", "asl $%02x      ", "ora [$%02x]    ", "php          ", "ora #$%04x   ", "asl          ", "phd          ", "tsb $%04x    ", "ora $%04x    ", "asl $%04x    ", "ora $%06x  ",
-  "bpl $%04x    ", "ora ($%02x),y  ", "ora ($%02x)    ", "ora ($%02x,s),y", "trb $%02x      ", "ora $%02x,x    ", "asl $%02x,x    ", "ora [$%02x],y  ", "clc          ", "ora $%04x,y  ", "inc          ", "tcs          ", "trb $%04x    ", "ora $%04x,x  ", "asl $%04x,x  ", "ora $%06x,x",
-  "jsr $%04x    ", "and ($%02x,x)  ", "jsl $%06x  ", "and $%02x,s    ", "bit $%02x      ", "and $%02x      ", "rol $%02x      ", "and [$%02x]    ", "plp          ", "and #$%04x   ", "rol          ", "pld          ", "bit $%04x    ", "and $%04x    ", "rol $%04x    ", "and $%06x  ",
-  "bmi $%04x    ", "and ($%02x),y  ", "and ($%02x)    ", "and ($%02x,s),y", "bit $%02x,x    ", "and $%02x,x    ", "rol $%02x,x    ", "and [$%02x],y  ", "sec          ", "and $%04x,y  ", "dec          ", "tsc          ", "bit $%04x,x  ", "and $%04x,x  ", "rol $%04x,x  ", "and $%06x,x",
-  "rti          ", "eor ($%02x,x)  ", "wdm #$%02x     ", "eor $%02x,s    ", "mvp $%02x, $%02x ", "eor $%02x      ", "lsr $%02x      ", "eor [$%02x]    ", "pha          ", "eor #$%04x   ", "lsr          ", "phk          ", "jmp $%04x    ", "eor $%04x    ", "lsr $%04x    ", "eor $%06x  ",
-  "bvc $%04x    ", "eor ($%02x),y  ", "eor ($%02x)    ", "eor ($%02x,s),y", "mvn $%02x, $%02x ", "eor $%02x,x    ", "lsr $%02x,x    ", "eor [$%02x],y  ", "cli          ", "eor $%04x,y  ", "phy          ", "tcd          ", "jml $%06x  ", "eor $%04x,x  ", "lsr $%04x,x  ", "eor $%06x,x",
-  "rts          ", "adc ($%02x,x)  ", "per $%04x    ", "adc $%02x,s    ", "stz $%02x      ", "adc $%02x      ", "ror $%02x      ", "adc [$%02x]    ", "pla          ", "adc #$%04x   ", "ror          ", "rtl          ", "jmp ($%04x)  ", "adc $%04x    ", "ror $%04x    ", "adc $%06x  ",
-  "bvs $%04x    ", "adc ($%02x),y  ", "adc ($%02x)    ", "adc ($%02x,s),y", "stz $%02x,x    ", "adc $%02x,x    ", "ror $%02x,x    ", "adc [$%02x],y  ", "sei          ", "adc $%04x,y  ", "ply          ", "tdc          ", "jmp ($%04x,x)", "adc $%04x,x  ", "ror $%04x,x  ", "adc $%06x,x",
-  "bra $%04x    ", "sta ($%02x,x)  ", "brl $%04x    ", "sta $%02x,s    ", "sty $%02x      ", "sta $%02x      ", "stx $%02x      ", "sta [$%02x]    ", "dey          ", "bit #$%04x   ", "txa          ", "phb          ", "sty $%04x    ", "sta $%04x    ", "stx $%04x    ", "sta $%06x  ",
-  "bcc $%04x    ", "sta ($%02x),y  ", "sta ($%02x)    ", "sta ($%02x,s),y", "sty $%02x,x    ", "sta $%02x,x    ", "stx $%02x,y    ", "sta [$%02x],y  ", "tya          ", "sta $%04x,y  ", "txs          ", "txy          ", "stz $%04x    ", "sta $%04x,x  ", "stz $%04x,x  ", "sta $%06x,x",
-  "ldy #$%04x   ", "lda ($%02x,x)  ", "ldx #$%04x   ", "lda $%02x,s    ", "ldy $%02x      ", "lda $%02x      ", "ldx $%02x      ", "lda [$%02x]    ", "tay          ", "lda #$%04x   ", "tax          ", "plb          ", "ldy $%04x    ", "lda $%04x    ", "ldx $%04x    ", "lda $%06x  ",
-  "bcs $%04x    ", "lda ($%02x),y  ", "lda ($%02x)    ", "lda ($%02x,s),y", "ldy $%02x,x    ", "lda $%02x,x    ", "ldx $%02x,y    ", "lda [$%02x],y  ", "clv          ", "lda $%04x,y  ", "tsx          ", "tyx          ", "ldy $%04x,x  ", "lda $%04x,x  ", "ldx $%04x,y  ", "lda $%06x,x",
-  "cpy #$%04x   ", "cmp ($%02x,x)  ", "rep #$%02x     ", "cmp $%02x,s    ", "cpy $%02x      ", "cmp $%02x      ", "dec $%02x      ", "cmp [$%02x]    ", "iny          ", "cmp #$%04x   ", "dex          ", "wai          ", "cpy $%04x    ", "cmp $%04x    ", "dec $%04x    ", "cmp $%06x  ",
-  "bne $%04x    ", "cmp ($%02x),y  ", "cmp ($%02x)    ", "cmp ($%02x,s),y", "pei $%02x      ", "cmp $%02x,x    ", "dec $%02x,x    ", "cmp [$%02x],y  ", "cld          ", "cmp $%04x,y  ", "phx          ", "stp          ", "jml [$%04x]  ", "cmp $%04x,x  ", "dec $%04x,x  ", "cmp $%06x,x",
-  "cpx #$%04x   ", "sbc ($%02x,x)  ", "sep #$%02x     ", "sbc $%02x,s    ", "cpx $%02x      ", "sbc $%02x      ", "inc $%02x      ", "sbc [$%02x]    ", "inx          ", "sbc #$%04x   ", "nop          ", "xba          ", "cpx $%04x    ", "sbc $%04x    ", "inc $%04x    ", "sbc $%06x  ",
-  "beq $%04x    ", "sbc ($%02x),y  ", "sbc ($%02x)    ", "sbc ($%02x,s),y", "pea #$%04x   ", "sbc $%02x,x    ", "inc $%02x,x    ", "sbc [$%02x],y  ", "sed          ", "sbc $%04x,y  ", "plx          ", "xce          ", "jsr ($%04x,x)", "sbc $%04x,x  ", "inc $%04x,x  ", "sbc $%06x,x"
-};
-
-// for 8/16 bit immediates
-// TODO: probably a better way to do this...
-static const char* opcodeNamesSp[256] = {
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "ora #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "and #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "eor #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "adc #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "bit #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-  "ldy #$%02x     ", NULL, "ldx #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL, "lda #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-  "cpy #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "cmp #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-  "cpx #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "sbc #$%02x     ", NULL, NULL, NULL, NULL, NULL, NULL,
-  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
-};
-
-// address types for each opcode
-static const int opcodeType[256] = {
-  0, 1, 1, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
-  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
-  2, 1, 3, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
-  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
-  0, 1, 1, 1, 8, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
-  6, 1, 1, 1, 8, 1, 1, 1, 0, 2, 0, 0, 3, 2, 2, 3,
-  0, 1, 7, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
-  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
-  6, 1, 7, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
-  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
-  5, 1, 5, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
-  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
-  5, 1, 1, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
-  6, 1, 1, 1, 1, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3,
-  5, 1, 1, 1, 1, 1, 1, 1, 0, 4, 0, 0, 2, 2, 2, 3,
-  6, 1, 1, 1, 2, 1, 1, 1, 0, 2, 0, 0, 2, 2, 2, 3
-};
-
-// name for each opcode, for spc
-static const char* opcodeNamesSpc[256] = {
-  "nop              ", "tcall 0          ", "set1 $%02x.0       ", "bbs $%02x.0, $%04x ", "or a, $%02x        ", "or a, $%04x      ", "or a, [X]        ", "or a, [$%02x+x]    ", "or a, #$%02x       ", "or $%02x, $%02x      ", "or1 c, $%04x.%01x   ", "asl $%02x          ", "asl $%04x        ", "push p           ", "tset $%04x       ", "brk              ",
-  "bpl $%04x        ", "tcall 1          ", "clr1 $%02x.0       ", "bbc $%02x.0, $%04x ", "or a, $%02x+x      ", "or a, $%04x+x    ", "or a, $%04x+y    ", "or a, [$%02x]+y    ", "or $%02x, #$%02x     ", "or [X], [Y]      ", "decw $%02x         ", "asl $%02x+x        ", "asl a            ", "dec x            ", "cmp x, $%04x     ", "jmp [$%04x+x]    ",
-  "clrp             ", "tcall 2          ", "set1 $%02x.1       ", "bbs $%02x.1, $%04x ", "and a, $%02x       ", "and a, $%04x     ", "and a, [X]       ", "and a, [$%02x+x]   ", "and a, #$%02x      ", "and $%02x, $%02x     ", "or1 c, /$%04x.%01x  ", "rol $%02x          ", "rol $%04x        ", "push a           ", "cbne $%02x, $%04x  ", "bra $%04x        ",
-  "bmi $%04x        ", "tcall 3          ", "clr1 $%02x.1       ", "bbc $%02x.1, $%04x ", "and a, $%02x+x     ", "and a, $%04x+x   ", "and a, $%04x+y   ", "and a, [$%02x]+y   ", "and $%02x, #$%02x    ", "and [X], [Y]     ", "incw $%02x         ", "rol $%02x+x        ", "rol a            ", "inc x            ", "cmp x, $%02x       ", "call $%04x       ",
-  "setp             ", "tcall 4          ", "set1 $%02x.2       ", "bbs $%02x.2, $%04x ", "eor a, $%02x       ", "eor a, $%04x     ", "eor a, [X]       ", "eor a, [$%02x+x]   ", "eor a, #$%02x      ", "eor $%02x, $%02x     ", "and1 c, $%04x.%01x  ", "lsr $%02x          ", "lsr $%04x        ", "push x           ", "tclr $%04x       ", "pcall $%02x        ",
-  "bvc $%04x        ", "tcall 5          ", "clr1 $%02x.2       ", "bbc $%02x.2, $%04x ", "eor a, $%02x+x     ", "eor a, $%04x+x   ", "eor a, $%04x+y   ", "eor a, [$%02x]+y   ", "eor $%02x, #$%02x    ", "eor [X], [Y]     ", "cmpw ya, $%02x     ", "lsr $%02x+x        ", "lsr a            ", "mov x, a         ", "cmp y, $%04x     ", "jmp $%04x        ",
-  "clrc             ", "tcall 6          ", "set1 $%02x.3       ", "bbs $%02x.3, $%04x ", "cmp a, $%02x       ", "cmp a, $%04x     ", "cmp a, [X]       ", "cmp a, [$%02x+x]   ", "cmp a, #$%02x      ", "cmp $%02x, $%02x     ", "and1 c, /$%04x.%01x ", "ror $%02x          ", "ror $%04x        ", "push y           ", "dbnz $%02x, $%04x  ", "ret              ",
-  "bvs $%04x        ", "tcall 7          ", "clr1 $%02x.3       ", "bbc $%02x.3, $%04x ", "cmp a, $%02x+x     ", "cmp a, $%04x+x   ", "cmp a, $%04x+y   ", "cmp a, [$%02x]+y   ", "cmp $%02x, #$%02x    ", "cmp [X], [Y]     ", "addw ya, $%02x     ", "ror $%02x+x        ", "ror a            ", "mov a, x         ", "cmp y, $%02x       ", "reti             ",
-  "setc             ", "tcall 8          ", "set1 $%02x.4       ", "bbs $%02x.4, $%04x ", "adc a, $%02x       ", "adc a, $%04x     ", "adc a, [X]       ", "adc a, [$%02x+x]   ", "adc a, #$%02x      ", "adc $%02x, $%02x     ", "eor1 c, $%04x.%01x  ", "dec $%02x          ", "dec $%04x        ", "mov y, #$%02x      ", "pop p            ", "mov $%02x, #$%02x    ",
-  "bcc $%04x        ", "tcall 9          ", "clr1 $%02x.4       ", "bbc $%02x.4, $%04x ", "adc a, $%02x+x     ", "adc a, $%04x+x   ", "adc a, $%04x+y   ", "adc a, [$%02x]+y   ", "adc $%02x, #$%02x    ", "adc [X], [Y]     ", "subw ya, $%02x     ", "dec $%02x+x        ", "dec a            ", "mov x, sp        ", "div ya, x        ", "xcn a            ",
-  "ei               ", "tcall 10         ", "set1 $%02x.5       ", "bbs $%02x.5, $%04x ", "sbc a, $%02x       ", "sbc a, $%04x     ", "sbc a, [X]       ", "sbc a, [$%02x+x]   ", "sbc a, #$%02x      ", "sbc $%02x, $%02x     ", "mov1 c, $%04x.%01x  ", "inc $%02x          ", "inc $%04x        ", "cmp y, #$%02x      ", "pop a            ", "mov [x+], a      ",
-  "bcs $%04x        ", "tcall 11         ", "clr1 $%02x.5       ", "bbc $%02x.5, $%04x ", "sbc a, $%02x+x     ", "sbc a, $%04x+x   ", "sbc a, $%04x+y   ", "sbc a, [$%02x]+y   ", "sbc $%02x, #$%02x    ", "sbc [X], [Y]     ", "movw ya, $%02x     ", "inc $%02x+x        ", "inc a            ", "mov sp, x        ", "das a            ", "mov a, [x+]      ",
-  "di               ", "tcall 12         ", "set1 $%02x.6       ", "bbs $%02x.6, $%04x ", "mov $%02x, a       ", "mov $%04x, a     ", "mov [X], a       ", "mov [$%02x+x], a   ", "cmp x, #$%02x      ", "mov $%04x, x     ", "mov1 $%04x.%01x, c  ", "mov $%02x, y       ", "mov $%04x, y     ", "mov x, #$%02x      ", "pop x            ", "mul ya           ",
-  "bne $%04x        ", "tcall 13         ", "clr1 $%02x.6       ", "bbc $%02x.6, $%04x ", "mov $%02x+x, a     ", "mov $%04x+x, a   ", "mov $%04x+y, a   ", "mov [$%02x]+y, a   ", "mov $%02x, x       ", "mov $%02x+y, x     ", "movw $%02x, ya     ", "mov $%02x+x, y     ", "dec y            ", "mov a, y         ", "cbne $%02x+x, $%04x", "daa a            ",
-  "clrv             ", "tcall 14         ", "set1 $%02x.7       ", "bbs $%02x.7, $%04x ", "mov a, $%02x       ", "mov a, $%04x     ", "mov a, [X]       ", "mov a, [$%02x+x]   ", "mov a, #$%02x      ", "mov x, $%04x     ", "not1 $%04x.%01x     ", "mov y, $%02x       ", "mov y, $%04x     ", "notc             ", "pop y            ", "sleep            ",
-  "beq $%04x        ", "tcall 15         ", "clr1 $%02x.7       ", "bbc $%02x.7, $%04x ", "mov a, $%02x+x     ", "mov a, $%04x+x   ", "mov a, $%04x+y   ", "mov a, [$%02x]+y   ", "mov x, $%02x       ", "mov x, $%02x+y     ", "mov $%02x, $%02x     ", "mov y, $%02x+x     ", "inc y            ", "mov y, a         ", "dbnz y, $%04x    ", "stop             "
-};
-
-// address types for each opcode, for spc
-static const int opcodeTypeSpc[256] = {
-  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 0, 2, 0,
-  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 2, 2,
-  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 0, 5, 3,
-  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 1, 2,
-  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 0, 2, 1,
-  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 2, 2,
-  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 0, 5, 0,
-  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 1, 0,
-  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 1, 0, 4,
-  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 0, 0,
-  0, 0, 1, 5, 1, 2, 0, 1, 1, 4, 6, 1, 2, 1, 0, 0,
-  3, 0, 1, 5, 1, 2, 2, 1, 4, 0, 1, 1, 0, 0, 0, 0,
-  0, 0, 1, 5, 1, 2, 0, 1, 1, 2, 6, 1, 2, 1, 0, 0,
-  3, 0, 1, 5, 1, 2, 2, 1, 1, 1, 1, 1, 0, 0, 5, 0,
-  0, 0, 1, 5, 1, 2, 0, 1, 1, 2, 6, 1, 2, 0, 0, 0,
-  3, 0, 1, 5, 1, 2, 2, 1, 1, 1, 4, 1, 0, 0, 3, 0
-};
-
-static void getDisassemblyCpu(Snes* snes, char* line);
-static void getDisassemblySpc(Apu *apu, char* line);
-
-void getProcessorStateCpu(Snes* snes, char* line) {
-  // 0        1         2         3         4         5         6         7         8
-  // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
-  // CPU 12:3456 1234567890123 A:1234 X:1234 Y:1234 SP:1234 DP:1234 DP:12 e nvmxdizc
-  char disLine[14] = "             ";
-  getDisassemblyCpu(snes, disLine);
-  sprintf(
-    line, "CPU %02x:%04x %s A:%04x X:%04x Y:%04x SP:%04x DP:%04x DB:%02x %c %c%c%c%c%c%c%c%c",
-    snes->cpu->k, snes->cpu->pc, disLine, snes->cpu->a, snes->cpu->x, snes->cpu->y,
-    snes->cpu->sp, snes->cpu->dp, snes->cpu->db, snes->cpu->e ? 'E' : 'e',
-    snes->cpu->n ? 'N' : 'n', snes->cpu->v ? 'V' : 'v', snes->cpu->mf ? 'M' : 'm', snes->cpu->xf ? 'X' : 'x',
-    snes->cpu->d ? 'D' : 'd', snes->cpu->i ? 'I' : 'i', snes->cpu->z ? 'Z' : 'z', snes->cpu->c ? 'C' : 'c'
-  );
-}
-
-void getProcessorStateSpc(Apu *apu, char* line) {
-  // 0        1         2         3         4         5         6         7         8
-  // 12345678901234567890123456789012345678901234567890123456789012345678901234567890
-  // SPC 3456 12345678901234567 A:12 X:12 Y:12 SP:12 nvpbhizc
-  char disLine[18] = "                 ";
-  getDisassemblySpc(apu, disLine);
-  sprintf(
-    line, "SPC %04x %s A:%02x X:%02x Y:%02x SP:%02x %c%c%c%c%c%c%c%c",
-    apu->spc->pc, disLine, apu->spc->a, apu->spc->x, apu->spc->y, apu->spc->sp,
-    apu->spc->n ? 'N' : 'n', apu->spc->v ? 'V' : 'v', apu->spc->p ? 'P' : 'p', apu->spc->b ? 'B' : 'b',
-    apu->spc->h ? 'H' : 'h', apu->spc->i ? 'I' : 'i', apu->spc->z ? 'Z' : 'z', apu->spc->c ? 'C' : 'c'
-  );
-}
-
-static void getDisassemblyCpu(Snes* snes, char* line) {
-  uint32_t adr = snes->cpu->pc | (snes->cpu->k << 16);
-  // read 4 bytes
-  // TODO: this can have side effects, implement and use peaking
-  uint8_t opcode = snes_read(snes, adr);
-  uint8_t byte = snes_read(snes, (adr + 1) & 0xffffff);
-  uint8_t byte2 = snes_read(snes, (adr + 2) & 0xffffff);
-  uint16_t word = (byte2 << 8) | byte;
-  uint32_t longv = (snes_read(snes, (adr + 3) & 0xffffff) << 16) | word;
-  uint16_t rel = snes->cpu->pc + 2 + (int8_t) byte;
-  uint16_t rell = snes->cpu->pc + 3 + (int16_t) word;
-  // switch on type
-  switch(opcodeType[opcode]) {
-    case 0: sprintf(line, "%s", opcodeNames[opcode]); break;
-    case 1: sprintf(line, opcodeNames[opcode], byte); break;
-    case 2: sprintf(line, opcodeNames[opcode], word); break;
-    case 3: sprintf(line, opcodeNames[opcode], longv); break;
-    case 4: {
-      if(snes->cpu->mf) {
-        sprintf(line, opcodeNamesSp[opcode], byte);
-      } else {
-        sprintf(line, opcodeNames[opcode], word);
-      }
-      break;
-    }
-    case 5: {
-      if(snes->cpu->xf) {
-        sprintf(line, opcodeNamesSp[opcode], byte);
-      } else {
-        sprintf(line, opcodeNames[opcode], word);
-      }
-      break;
-    }
-    case 6: sprintf(line, opcodeNames[opcode], rel); break;
-    case 7: sprintf(line, opcodeNames[opcode], rell); break;
-    case 8: sprintf(line, opcodeNames[opcode], byte2, byte); break;
-  }
-}
-
-void getDisassemblySpc(Apu *apu, char* line) {
-  uint16_t adr = apu->spc->pc;
-  // read 3 bytes
-  // TODO: this can have side effects, implement and use peaking
-  uint8_t opcode = apu_cpuRead(apu, adr);
-  uint8_t byte = apu_cpuRead(apu, (adr + 1) & 0xffff);
-  uint8_t byte2 = apu_cpuRead(apu, (adr + 2) & 0xffff);
-  uint16_t word = (byte2 << 8) | byte;
-  uint16_t rel = apu->spc->pc + 2 + (int8_t) byte;
-  uint16_t rel2 = apu->spc->pc + 2 + (int8_t) byte2;
-  uint16_t wordb = word & 0x1fff;
-  uint8_t bit = word >> 13;
-  // switch on type
-  switch(opcodeTypeSpc[opcode]) {
-    case 0: sprintf(line, "%s", opcodeNamesSpc[opcode]); break;
-    case 1: sprintf(line, opcodeNamesSpc[opcode], byte); break;
-    case 2: sprintf(line, opcodeNamesSpc[opcode], word); break;
-    case 3: sprintf(line, opcodeNamesSpc[opcode], rel); break;
-    case 4: sprintf(line, opcodeNamesSpc[opcode], byte2, byte); break;
-    case 5: sprintf(line, opcodeNamesSpc[opcode], byte, rel2); break;
-    case 6: sprintf(line, opcodeNamesSpc[opcode], wordb, bit); break;
-  }
-}
--- a/tracing.h
+++ /dev/null
@@ -1,17 +1,0 @@
-
-#ifndef TRACING_H
-#define TRACING_H
-
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdint.h>
-#include <stdbool.h>
-
-#include "snes/snes.h"
-
-void getProcessorStateCpu(Snes* snes, char* line);
-void getProcessorStateSpc(Apu* apu, char* line);
-
-#endif
--- a/types.h
+++ b/types.h
@@ -55,9 +55,11 @@
 typedef struct Point16U {
   uint16 x, y;
 } Point16U;
+
 typedef struct PointU8 {
   uint8 x, y;
 } PointU8;
+
 typedef struct Pair16U {
   uint16 a, b;
 } Pair16U;
@@ -74,24 +76,6 @@
 typedef struct OamEnt {
   uint8 x, y, charnum, flags;
 } OamEnt;
-
-typedef struct UploadVram_Row {
-  uint16 col[32];
-} UploadVram_Row;
-
-typedef struct UploadVram_32x32 {
-  UploadVram_Row row[32];
-} UploadVram_32x32;
-
-typedef struct UploadVram_3 {
-  uint8 pad[256];
-  uint16 data[4];
-} UploadVram_3;
-
-#define uvram (*(UploadVram_3*)(&g_ram[0x1000]))
-
-typedef void PlayerHandlerFunc();
-typedef void HandlerFuncK(int k);
 
 void NORETURN Die(const char *error);
 
--- a/variables.h
+++ b/variables.h
@@ -1,4 +1,5 @@
-
+#ifndef ZELDA3_VARIABLES_H_
+#define ZELDA3_VARIABLES_H_
 #define main_module_index (*(uint8*)(g_ram+0x10))
 #define submodule_index (*(uint8*)(g_ram+0x11))
 #define nmi_boolean (*(uint8*)(g_ram+0x12))
@@ -1398,7 +1399,6 @@
 
 
 #define turn_on_off_water_ctr (*(uint8*)(g_ram+0x424))
-#define mirror_vars (*(MirrorHdmaVars*)(g_ram+0x6A0))
 #define sprite_N_word ((uint16*)(g_ram+0xBC0))
 #define sprite_where_in_overworld ((uint8*)(g_ram+0x1DF80))
 #define alt_sprite_B ((uint8*)(g_ram+0x1FA5C))
@@ -1427,3 +1427,97 @@
 // Relocated the hdma table so it can fit 240 rows
 #define hdma_table_dynamic_orig_pos ((uint16*)(g_ram+0x1B00))
 #define hdma_table_dynamic ((uint16*)(g_ram+0x1DBA0))
+
+
+
+
+typedef struct MovableBlockData {
+  uint16 room;
+  uint16 tilemap;
+} MovableBlockData;
+
+typedef struct OamEntSigned {
+  int8 x, y;
+  uint8 charnum, flags;
+} OamEntSigned;
+
+
+
+#define movable_block_datas ((MovableBlockData*)(g_ram+0xf940))
+#define oam_buf ((OamEnt*)(g_ram+0x800))
+
+
+typedef struct RoomBounds {
+  union {
+    struct { uint16 a0, b0, a1, b1; };
+    uint16 v[4];
+  };
+} RoomBounds;
+#define room_bounds_y (*(RoomBounds*)(g_ram+0x600))
+#define room_bounds_x (*(RoomBounds*)(g_ram+0x608))
+
+
+typedef struct OwScrollVars {
+  uint16 ystart, yend, xstart, xend;
+} OwScrollVars;
+
+
+#define ow_scroll_vars0 (*(OwScrollVars*)(g_ram+0x600))
+#define ow_scroll_vars1 (*(OwScrollVars*)(g_ram+0x608))
+
+#define ow_scroll_vars0_exit (*(OwScrollVars*)(g_ram+0xC154))
+
+extern const uint8 kLayoutQuadrantFlags[];
+extern const uint8 kVariousPacks[16];
+extern const uint8 kMaxBombsForLevel[];
+extern const uint8 kMaxArrowsForLevel[];
+extern const uint8 kReceiveItem_Tab1[76];
+extern const uint8 kHealthAfterDeath[21];
+extern const uint8 kReceiveItemGfx[76];
+extern const uint16 kOverworld_OffsetBaseY[64];
+extern const uint16 kOverworld_OffsetBaseX[64];
+
+// forwards
+
+
+typedef struct MirrorHdmaVars {
+  uint16 var0;
+  uint16 var1[2];
+  uint16 var3[2];
+  uint16 var5;
+  uint16 var6;
+  uint16 var7;
+  uint16 var8;
+  uint16 var9;
+  uint16 var10;
+  uint16 var11;
+  uint16 pad;
+  uint8 ctr2, ctr;
+} MirrorHdmaVars;
+#define mirror_vars (*(MirrorHdmaVars*)(g_ram+0x6A0))
+
+
+typedef struct UploadVram_Row {
+  uint16 col[32];
+} UploadVram_Row;
+
+typedef struct UploadVram_32x32 {
+  UploadVram_Row row[32];
+} UploadVram_32x32;
+
+typedef struct UploadVram_3 {
+  uint8 pad[256];
+  uint16 data[4];
+} UploadVram_3;
+
+#define uvram (*(UploadVram_3*)(&g_ram[0x1000]))
+
+extern uint8 g_ram[131072];
+extern const uint16 kUpperBitmasks[];
+extern const uint8 kLitTorchesColorPlus[];
+extern const uint8 kDungeonCrystalPendantBit[];
+extern const int8 kGetBestActionToPerformOnTile_x[];
+extern const int8 kGetBestActionToPerformOnTile_y[];
+
+
+#endif  // ZELDA3_VARIABLES_H_
\ No newline at end of file
--- a/zelda3.vcxproj
+++ b/zelda3.vcxproj
@@ -272,6 +272,7 @@
       <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Disabled</Optimization>
       <InlineFunctionExpansion Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Disabled</InlineFunctionExpansion>
     </ClCompile>
+    <ClCompile Include="snes\tracing.c" />
     <ClCompile Include="spc_player.c" />
     <ClCompile Include="sprite.c" />
     <ClCompile Include="sprite_main.c">
@@ -280,10 +281,6 @@
     </ClCompile>
     <ClCompile Include="tagalong.c" />
     <ClCompile Include="tile_detect.c" />
-    <ClCompile Include="tracing.c">
-      <Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Disabled</Optimization>
-      <InlineFunctionExpansion Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Disabled</InlineFunctionExpansion>
-    </ClCompile>
     <ClCompile Include="zelda_cpu_infra.c" />
     <ClCompile Include="zelda_rtl.c" />
   </ItemGroup>
@@ -294,6 +291,7 @@
     <ClInclude Include="config.h" />
     <ClInclude Include="dungeon.h" />
     <ClInclude Include="ending.h" />
+    <ClInclude Include="features.h" />
     <ClInclude Include="hud.h" />
     <ClInclude Include="load_gfx.h" />
     <ClInclude Include="messaging.h" />
@@ -319,12 +317,12 @@
     <ClInclude Include="snes\snes.h" />
     <ClInclude Include="snes\snes_regs.h" />
     <ClInclude Include="snes\spc.h" />
+    <ClInclude Include="snes\tracing.h" />
     <ClInclude Include="spc_player.h" />
     <ClInclude Include="sprite.h" />
     <ClInclude Include="sprite_main.h" />
     <ClInclude Include="tagalong.h" />
     <ClInclude Include="tile_detect.h" />
-    <ClInclude Include="tracing.h" />
     <ClInclude Include="types.h" />
     <ClInclude Include="variables.h" />
     <ClInclude Include="zelda_cpu_infra.h" />
--- a/zelda3.vcxproj.filters
+++ b/zelda3.vcxproj.filters
@@ -55,9 +55,6 @@
     <ClCompile Include="snes\spc.c">
       <Filter>Snes</Filter>
     </ClCompile>
-    <ClCompile Include="tracing.c">
-      <Filter>Snes</Filter>
-    </ClCompile>
     <ClCompile Include="zelda_rtl.c">
       <Filter>Zelda</Filter>
     </ClCompile>
@@ -133,6 +130,9 @@
     <ClCompile Include="main.c">
       <Filter>Zelda</Filter>
     </ClCompile>
+    <ClCompile Include="snes\tracing.c">
+      <Filter>Snes</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="snes\apu.h">
@@ -165,9 +165,6 @@
     <ClInclude Include="snes\spc.h">
       <Filter>Snes</Filter>
     </ClInclude>
-    <ClInclude Include="tracing.h">
-      <Filter>Snes</Filter>
-    </ClInclude>
     <ClInclude Include="zelda_rtl.h">
       <Filter>Zelda</Filter>
     </ClInclude>
@@ -258,6 +255,12 @@
     <ClInclude Include="assets.h">
       <Filter>Zelda</Filter>
     </ClInclude>
+    <ClInclude Include="features.h">
+      <Filter>Zelda</Filter>
+    </ClInclude>
+    <ClInclude Include="snes\tracing.h">
+      <Filter>Snes</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="packages.config" />
@@ -334,4 +337,4 @@
       <Filter>Deploy\saverefs</Filter>
     </None>
   </ItemGroup>
-</Project>
+</Project>
\ No newline at end of file
--- a/zelda_cpu_infra.c
+++ b/zelda_cpu_infra.c
@@ -1,25 +1,21 @@
 // This file handles running zelda through the emulated cpu.
-
+// It defines the runtime environment for the emulated side-by-side state.
+// It should be possible to build and run the game without this file
 #include "zelda_cpu_infra.h"
 #include "zelda_rtl.h"
 #include "variables.h"
-#include "misc.h"
-#include "nmi.h"
-#include "poly.h"
-#include "attract.h"
 #include "spc_player.h"
-#include "snes/snes_regs.h"
 #include "snes/snes.h"
+#include "snes/snes_regs.h"
 #include "snes/cpu.h"
 #include "snes/cart.h"
-#include "tracing.h"
+#include "snes/tracing.h"
 
 Snes *g_snes;
 Cpu *g_cpu;
 uint8 g_emulated_ram[0x20000];
-uint32 g_wanted_zelda_features;
 
-void SaveLoadSlot(int cmd, int which);
+static void PatchRom(uint8 *rom);
 
 uint8 *GetPtr(uint32 addr) {
   Cart *cart = g_snes->cart;
@@ -173,11 +169,6 @@
   return &cart->rom[(((addr >> 16) << 15) | (addr & 0x7fff)) & (cart->romSize - 1)];
 }
 
-void SetSnes(Snes *snes) {
-  g_snes = snes;
-  g_cpu = snes->cpu;
-}
-
 bool g_calling_asm_from_c;
 
 void HookedFunctionRts(int is_long) {
@@ -270,7 +261,7 @@
   }
 }
 
-void RunEmulatedSnesFrame(Snes *snes, int run_what) {
+static void RunEmulatedSnesFrame(Snes *snes, int run_what) {
   // First call runs until init
   if (snes->cpu->pc == 0x8000 && snes->cpu->k == 0) {
     RunOrigAsmCodeOneLoop(snes);
@@ -317,441 +308,21 @@
   RunOrigAsmCodeOneLoop(snes);
 }
 
-Dsp *GetDspForRendering() {
-  SpcPlayer_GenerateSamples(g_zenv.player);
-  return g_zenv.player->dsp;
-}
 
-typedef struct ByteArray {
-  uint8 *data;
-  size_t size, capacity;
-} ByteArray;
-
-void ByteArray_Resize(ByteArray *arr, size_t new_size) {
-  arr->size = new_size;
-  if (new_size > arr->capacity) {
-    size_t minsize = arr->capacity + (arr->capacity >> 1) + 8;
-    arr->capacity = new_size < minsize ? minsize : new_size;
-    void *data = realloc(arr->data, arr->capacity);
-    if (!data) Die("memory allocation failed");
-    arr->data = data;
-  }
-}
-
-void ByteArray_Destroy(ByteArray *arr) {
-  free(arr->data);
-  arr->data = NULL;
-}
-
-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;
-
-void loadFunc(void *ctx, void *data, size_t data_size) {
-  LoadFuncState *st = (LoadFuncState *)ctx;
-  assert(st->pend - st->p >= data_size);
-  memcpy(data, st->p, data_size);
-  st->p += data_size;
-}
-
-void CopyStateAfterSnapshotRestore(bool is_reset) {
-  memcpy(g_snes->ram + 0x1DBA0, g_snes->ram + 0x1b00, 224 * 2); // hdma table was moved
-
-  memcpy(g_zenv.ram, g_snes->ram, 0x20000);
-  memcpy(g_zenv.sram, g_snes->cart->ram, g_snes->cart->ramSize);
-  memcpy(g_zenv.ppu->vram, &g_snes->ppu->vram, offsetof(Ppu, ppu2openBus) + 1 - offsetof(Ppu, vram));
-  memcpy(g_zenv.player->ram, g_snes->apu->ram, sizeof(g_snes->apu->ram));
-
-  if (!is_reset) {
-    memcpy(g_zenv.player->dsp->ram, g_snes->apu->dsp->ram, sizeof(Dsp) - offsetof(Dsp, ram));
-    SpcPlayer_CopyVariablesFromRam(g_zenv.player);
-  }
-
-  memcpy(g_zenv.dma->channel, g_snes->dma->channel, sizeof(Dma) - offsetof(Dma, channel));
-  
-  
-
-  g_zenv.player->timer_cycles = 0;
-
-  ZeldaOpenMsuFile();
-}
-
-void SaveSnesState(ByteArray *ctx) {
-  MakeSnapshot(&g_snapshot_before);
-
-  memcpy(g_zenv.ram + 0x1b00, g_zenv.ram + 0x1DBA0, 224 * 2); // hdma table was moved
-
-  // Copy from my state into the emulator
+// Copy state into the emulator, we can skip dsp/apu because 
+// we're not emulating that.
+static void EmuSynchronizeWholeState() {
   memcpy(&g_snes->ppu->vram, g_zenv.ppu->vram, offsetof(Ppu, ppu2openBus) + 1 - offsetof(Ppu, vram));
   memcpy(g_snes->ram, g_zenv.ram, 0x20000);
   memcpy(g_snes->cart->ram, g_zenv.sram, 0x2000);
-  SpcPlayer_CopyVariablesToRam(g_zenv.player);
-  memcpy(g_snes->apu->ram, g_zenv.player->ram, 0x10000);
-  memcpy(g_snes->apu->dsp->ram, g_zenv.player->dsp->ram, sizeof(Dsp) - offsetof(Dsp, ram));
   memcpy(g_snes->dma->channel, g_zenv.dma->channel, sizeof(Dma) - offsetof(Dma, channel));
 
-  snes_saveload(g_snes, &saveFunc, ctx);
-
-  RestoreSnapshot(&g_snapshot_before);
+  // todo: this is hacky
+  if (animated_tile_data_src == 0)
+    cpu_reset(g_snes->cpu);
 }
 
-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;
-  uint8 replay_cmd;
-  bool replay_mode;
-
-  ByteArray log;
-  ByteArray base_snapshot;
-} StateRecorder;
-
-static StateRecorder state_recorder;
-
-void StateRecorder_Init(StateRecorder *sr) {
-  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 StateRecorder_Load(StateRecorder *sr, FILE *f, bool replay_mode) {
-  // todo: fix robustness on invalid data.
-  uint32 hdr[8] = { 0 };
-  fread(hdr, 1, sizeof(hdr), f);
-
-  assert(hdr[0] == 1);
-
-  sr->total_frames = hdr[1];
-  ByteArray_Resize(&sr->log, hdr[2]);
-  fread(sr->log.data, 1, sr->log.size, f);
-  sr->last_inputs = hdr[3];
-  sr->frames_since_last = hdr[4];
-
-  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->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) {
-      LoadFuncState state = { sr->base_snapshot.data, sr->base_snapshot.data + sr->base_snapshot.size };
-      snes_saveload(g_snes, &loadFunc, &state);
-      assert(state.p == state.pend);
-    } else {
-      snes_reset(g_snes, true);
-      SpcPlayer_Initialize(g_zenv.player);
-      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);
-    LoadFuncState state = { arr.data, arr.data + arr.size };
-    snes_saveload(g_snes, &loadFunc, &state);
-    ByteArray_Destroy(&arr);
-    assert(state.p == state.pend);
-  }
-  CopyStateAfterSnapshotRestore(is_reset);
-}
-
-void StateRecorder_Save(StateRecorder *sr, FILE *f) {
-  uint32 hdr[8] = { 0 };
-  ByteArray arr = {0};
-  SaveSnesState(&arr);
-  assert(sr->base_snapshot.size == 0 || sr->base_snapshot.size == arr.size);
-
-  hdr[0] = 1;
-  hdr[1] = sr->total_frames;
-  hdr[2] = (uint32)sr->log.size;
-  hdr[3] = sr->last_inputs;
-  hdr[4] = sr->frames_since_last;
-  hdr[5] = sr->base_snapshot.size ? 1 : 0;
-  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[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);
-
-  ByteArray_Destroy(&arr);
-}
-
-void StateRecorder_ClearKeyLog(StateRecorder *sr) {
-  printf("Clearing key log!\n");
-  sr->base_snapshot.size = 0;
-  SaveSnesState(&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_emulated_ram[addr & 0x1ffff] = g_ram[addr & 0x1ffff] = sr->log.data[replay_pos++];
-        } 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;
-}
-
-static int frame_ctr;
-
-int IncrementCrystalCountdown(uint8 *a, int v) {
-  int t = *a + v;
-  *a = t;
-  return t >> 8;
-}
-
-#ifdef _DEBUG
-// This can be used to read inputs from a text file for easier debugging
-int InputStateReadFromFile() {
-  static FILE *f;
-  static uint32 next_ts, next_keys, cur_keys;
-  char buf[64];
-  char keys[64];
-
-  while (state_recorder.total_frames == next_ts) {
-    cur_keys = next_keys;
-    if (!f)
-      f = fopen("boss_bug.txt", "r");
-    if (fgets(buf, sizeof(buf), f)) {
-      if (sscanf(buf, "%d: %s", &next_ts, keys) == 1) keys[0] = 0;
-      int i = 0;
-      for (const char *s = keys; *s; s++) {
-        static const char kKeys[] = "AXsSUDLRBY";
-        const char *t = strchr(kKeys, *s);
-        assert(t);
-        i |= 1 << (t - kKeys);
-      }
-      next_keys = i;
-    } else {
-      next_ts = 0xffffffff;
-    }
-  }
-
-  return cur_keys;
-}
-#endif
-
-bool RunOneFrame(Snes *snes, int input_state) {
-  bool is_replay = state_recorder.replay_mode;
-  frame_ctr++;
-
-  // Either copy state or apply state
-  if (is_replay) {
-    input_state = StateRecorder_ReadNextReplayState(&state_recorder);
-  } else {
-    //    input_state = InputStateReadFromFile();
-    StateRecorder_Record(&state_recorder, input_state);
-
-    // This is whether APUI00 is true or false, this is used by the ancilla code.
-    uint8 apui00 = ZeldaIsMusicPlaying();
-    if (apui00 != g_ram[kRam_APUI00]) {
-      g_emulated_ram[kRam_APUI00] = g_ram[kRam_APUI00] = apui00;
-      StateRecorder_RecordPatchByte(&state_recorder, 0x648, &apui00, 1);
-    }
-
-    if (animated_tile_data_src != 0) {
-      // 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) {
-        g_emulated_ram[kRam_BugsFixed] = g_ram[kRam_BugsFixed] = kBugFix_Latest;
-        StateRecorder_RecordPatchByte(&state_recorder, kRam_BugsFixed, &g_ram[kRam_BugsFixed], 1);
-      }
-
-      if (enhanced_features0 != g_wanted_zelda_features) {
-        *(uint32*)&g_emulated_ram[kRam_Features0] = enhanced_features0 = g_wanted_zelda_features;
-        StateRecorder_RecordPatchByte(&state_recorder, kRam_Features0, (uint8*)&enhanced_features0, 4);
-      }
-    }
-  }
-
-  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 || enhanced_features0 != 0) {
-    // can't compare against real impl when running with extra features.
-    ZeldaRunFrame(input_state, run_what);
-    return is_replay;
-  }
-
-  if (g_fail)
-    return false;
-
+static void EmuRunFrameWithCompare(uint16 input_state, int run_what) {
   MakeSnapshot(&g_snapshot_before);
   MakeMySnapshot(&g_snapshot_mine);
   MakeSnapshot(&g_snapshot_theirs);
@@ -760,27 +331,27 @@
   VerifySnapshotsEq(&g_snapshot_mine, &g_snapshot_theirs, &g_snapshot_before);
   if (g_fail) {
     printf("early fail\n");
-    //assert(0);
+    assert(0);
     //return turbo;
   }
 
   // Run orig version then snapshot
 again_theirs:
-  snes->input1->currentState = input_state;
-  RunEmulatedSnesFrame(snes, run_what);
+  g_snes->input1->currentState = input_state;
+  RunEmulatedSnesFrame(g_snes, run_what);
   MakeSnapshot(&g_snapshot_theirs);
 
   // Run my version and snapshot
 again_mine:
-  ZeldaRunFrame(input_state, run_what);
-  
+  ZeldaRunFrameInternal(input_state, run_what);
+
   MakeMySnapshot(&g_snapshot_mine);
 
   // Compare both snapshots
   VerifySnapshotsEq(&g_snapshot_mine, &g_snapshot_theirs, &g_snapshot_before);
-  
+
   if (g_fail) {
-//    g_fail = false;
+    //    g_fail = false;
     if (1) {
       RestoreMySnapshot(&g_snapshot_before);
       //SaveLoadSlot(kSaveLoad_Save, 0);
@@ -791,29 +362,28 @@
     }
     if (1) {
       MakeSnapshot(&g_snapshot_theirs);
-      RestoreMySnapshot(&g_snapshot_theirs);      
+      RestoreMySnapshot(&g_snapshot_theirs);
     }
   }
-
-  return is_replay;
 }
 
-void PatchRomBP(uint8_t *rom, uint32_t addr) {
+
+static void PatchRomBP(uint8_t *rom, uint32_t addr) {
   rom[(addr >> 16) << 15 | (addr & 0x7fff)] = 0;
 }
 
-void PatchRomByte(uint8_t *rom, uint32_t addr, uint8 old_value, uint8 value) {
+static void PatchRomByte(uint8_t *rom, uint32_t addr, uint8 old_value, uint8 value) {
   assert(rom[(addr >> 16) << 15 | (addr & 0x7fff)] == old_value);
   rom[(addr >> 16) << 15 | (addr & 0x7fff)] = value;
 }
 
-void PatchRomWord(uint8_t *rom, uint32_t addr, uint16 old_value, uint16 value) {
+static void PatchRomWord(uint8_t *rom, uint32_t addr, uint16 old_value, uint16 value) {
   assert(WORD(rom[(addr >> 16) << 15 | (addr & 0x7fff)]) == old_value);
   WORD(rom[(addr >> 16) << 15 | (addr & 0x7fff)]) = value;
 }
 
 
-void PatchRom(uint8_t *rom) {
+static void PatchRom(uint8_t *rom) {
   //  fix a bug with unitialized memory
   {
     uint8_t *p = rom + 0x36434;
@@ -979,111 +549,12 @@
 }
 
 
-static const char *const kReferenceSaves[] = {
-  "Chapter 1 - Zelda's Rescue.sav",
-  "Chapter 2 - After Eastern Palace.sav",
-  "Chapter 3 - After Desert Palace.sav",
-  "Chapter 4 - After Tower of Hera.sav",
-  "Chapter 5 - After Hyrule Castle Tower.sav",
-  "Chapter 6 - After Dark Palace.sav",
-  "Chapter 7 - After Swamp Palace.sav",
-  "Chapter 8 - After Skull Woods.sav",
-  "Chapter 9 - After Gargoyle's Domain.sav",
-  "Chapter 10 - After Ice Palace.sav",
-  "Chapter 11 - After Misery Mire.sav",
-  "Chapter 12 - After Turtle Rock.sav",
-  "Chapter 13 - After Ganon's Tower.sav",
-};
 
-void SaveLoadSlot(int cmd, int which) {
-  char name[128];
-  if (which & 256) {
-    if (cmd == kSaveLoad_Save)
-      return;
-    sprintf(name, "saves/ref/%s", kReferenceSaves[which - 256]);
-  } else {
-    sprintf(name, "saves/save%d.sav", which);
-  }
-  FILE *f = fopen(name, cmd != kSaveLoad_Save ? "rb" : "wb");
-  if (f) {
-    printf("*** %s slot %d\n", 
-      cmd==kSaveLoad_Save ? "Saving" : cmd==kSaveLoad_Load ? "Loading" : "Replaying", which);
+bool EmuInitialize(uint8 *data, size_t size) {
+  PatchRom(data);
+  g_snes = snes_init(g_emulated_ram);
+  g_cpu = g_snes->cpu;
 
-    if (cmd != kSaveLoad_Save)
-      StateRecorder_Load(&state_recorder, f, cmd == kSaveLoad_Replay);
-    else
-      StateRecorder_Save(&state_recorder, f);
-
-    fclose(f);
-  }
-}
-
-void ZeldaReadSram(Snes *snes) {
-  FILE *f = fopen("saves/sram.dat", "rb");
-  if (f) {
-    fread(g_zenv.sram, 1, 8192, f);
-    memcpy(snes->cart->ram, g_zenv.sram, 8192);
-    fclose(f);
-  }
-}
-
-void ZeldaWriteSram() {
-  rename("saves/sram.dat", "saves/sram.bak");
-  FILE *f = fopen("saves/sram.dat", "wb");
-  if (f) {
-    fwrite(g_zenv.sram, 1, 8192, f);
-    fclose(f);
-  } else {
-    fprintf(stderr, "Unable to write saves/sram.dat\n");
-  }
-}
-
-typedef struct StateRecoderMultiPatch {
-  uint32 count;
-  uint32 addr;
-  uint8 vals[256];
-} StateRecoderMultiPatch;
-
-
-void StateRecoderMultiPatch_Init(StateRecoderMultiPatch *mp) {
-  mp->count = mp->addr = 0;
-}
-
-void StateRecoderMultiPatch_Commit(StateRecoderMultiPatch *mp) {
-  if (mp->count)
-    StateRecorder_RecordPatchByte(&state_recorder, mp->addr, mp->vals, mp->count);
-}
-
-void StateRecoderMultiPatch_Patch(StateRecoderMultiPatch *mp, uint32 addr, uint8 value) {
-  if (mp->count >= 256 || addr != mp->addr + mp->count) {
-    StateRecoderMultiPatch_Commit(mp);
-    mp->addr = addr;
-    mp->count = 0;
-  }
-  mp->vals[mp->count++] = value;
-  g_emulated_ram[addr] = g_ram[addr] = value;
-}
-
-void PatchCommand(char c) {
-  StateRecoderMultiPatch mp;
-
-  StateRecoderMultiPatch_Init(&mp);
-  if (c == 'w') {
-    StateRecoderMultiPatch_Patch(&mp, 0xf372, 80);  // health filler
-    StateRecoderMultiPatch_Patch(&mp, 0xf373, 80);  // magic filler
-    //    b.Patch(0x1FE01, 25);
-  } else if (c == 'W') {
-    StateRecoderMultiPatch_Patch(&mp, 0xf375, 10);  // link_bomb_filler
-    StateRecoderMultiPatch_Patch(&mp, 0xf376, 10);  // link_arrow_filler
-    uint16 rupees = link_rupees_goal + 100;
-    StateRecoderMultiPatch_Patch(&mp, 0xf360, rupees);  // link_rupees_goal
-    StateRecoderMultiPatch_Patch(&mp, 0xf361, rupees >> 8);  // link_rupees_goal
-  } else if (c == 'k') {
-    StateRecorder_ClearKeyLog(&state_recorder);
-  } else if (c == 'o') {
-    StateRecoderMultiPatch_Patch(&mp, 0xf36f, 1);
-  } else if (c == 'l') {
-    StateRecorder_StopReplay(&state_recorder);
-  }
-  StateRecoderMultiPatch_Commit(&mp);
+  ZeldaSetupEmuCallbacks(g_emulated_ram, &EmuRunFrameWithCompare, &EmuSynchronizeWholeState);
+  return snes_loadRom(g_snes, data, (int)size);
 }
--- a/zelda_cpu_infra.h
+++ b/zelda_cpu_infra.h
@@ -1,8 +1,14 @@
-#pragma once
+#ifndef ZELDA3_ZELDA_CPU_INFRA_H_
+#define ZELDA3_ZELDA_CPU_INFRA_H_
 #include "types.h"
 
+extern uint8 g_emulated_ram[0x20000];
+
 uint8 *GetPtr(uint32 addr);
 
 void RunEmulatedFuncSilent(uint32 pc, uint16 a, uint16 x, uint16 y, bool mf, bool xf, int b, int whatflags);
 void RunEmulatedFunc(uint32 pc, uint16 a, uint16 x, uint16 y, bool mf, bool xf, int b, int whatflags);
 
+bool EmuInitialize(uint8 *data, size_t size);
+
+#endif  // ZELDA3_ZELDA_CPU_INFRA_H_
--- a/zelda_rtl.c
+++ b/zelda_rtl.c
@@ -6,11 +6,16 @@
 #include "attract.h"
 #include "snes/ppu.h"
 #include "snes/snes_regs.h"
+#include "snes/dma.h"
 #include "spc_player.h"
 
 ZeldaEnv g_zenv;
-// These point to the rewritten instance of the emu.
 uint8 g_ram[131072];
+
+uint32 g_wanted_zelda_features;
+
+static void Startup_InitializeMemory();
+
 typedef struct SimpleHdma {
   const uint8 *table;
   const uint8 *indir_ptr;
@@ -19,8 +24,8 @@
   uint8 ppu_addr;
   uint8 indir_bank;
 } SimpleHdma;
-void SimpleHdma_Init(SimpleHdma *c, DmaChannel *dc);
-void SimpleHdma_DoLine(SimpleHdma *c);
+static void SimpleHdma_Init(SimpleHdma *c, DmaChannel *dc);
+static void SimpleHdma_DoLine(SimpleHdma *c);
 
 static const uint8 bAdrOffsets[8][4] = {
   {0, 0, 0, 0},
@@ -57,11 +62,6 @@
   g_zenv.player->input_ports[adr & 0x3] = val;
 }
 
-void zelda_apu_write_word(uint32_t adr, uint16_t val) {
-  zelda_apu_write(adr, val);
-  zelda_apu_write(adr + 1, val >> 8);
-}
-
 uint8_t zelda_read_apui00() {
   // This needs to be here because the ancilla code reads
   // from the apu and we don't want to make the core code
@@ -74,12 +74,6 @@
   return g_zenv.player->port_to_snes[adr & 0x3];
 }
 
-uint16_t zelda_apu_read_word(uint32_t adr) {
-  uint16_t rv = zelda_apu_read(adr);
-  rv |= zelda_apu_read(adr + 1) << 8;
-  return rv;
-}
-
 void zelda_ppu_write(uint32_t adr, uint8_t val) {
   assert(adr >= INIDISP && adr <= STAT78);
   ppu_write(g_zenv.ppu, (uint8)adr, val);
@@ -90,11 +84,7 @@
   zelda_ppu_write(adr + 1, val >> 8);
 }
 
-void zelda_apu_runcycles() {
-//  apu_cycle(g_zenv.apu);
-}
-
-const uint8 *SimpleHdma_GetPtr(uint32 p) {
+static const uint8 *SimpleHdma_GetPtr(uint32 p) {
   switch (p) {
 
   case 0xCFA87: return kAttractDmaTable0;
@@ -124,7 +114,7 @@
   }
 }
 
-void SimpleHdma_Init(SimpleHdma *c, DmaChannel *dc) {
+static void SimpleHdma_Init(SimpleHdma *c, DmaChannel *dc) {
   if (!dc->hdmaActive) {
     c->table = 0;
     return;
@@ -136,7 +126,7 @@
   c->indir_bank = dc->indBank;
 }
 
-void SimpleHdma_DoLine(SimpleHdma *c) {
+static void SimpleHdma_DoLine(SimpleHdma *c) {
   if (c->table == NULL)
     return;
   bool do_transfer = false;
@@ -161,7 +151,7 @@
   c->rep_count--;
 }
 
-void ConfigurePpuSideSpace() {
+static void ConfigurePpuSideSpace() {
   // Let PPU impl know about the maximum allowed extra space on the sides and bottom
   int extra_right = 0, extra_left = 0, extra_bottom = 0;
 //  printf("main %d, sub %d  (%d, %d, %d)\n", main_module_index, submodule_index, BG2HOFS_copy2, room_bounds_x.v[2 | (quadrant_fullsize_x >> 1)], quadrant_fullsize_x >> 1);
@@ -254,7 +244,7 @@
   dma_write(dma, DAS70, indirect_bank);
 }
 
-void ZeldaInitializationCode() {
+static void ZeldaInitializationCode() {
   zelda_snes_dummy_write(NMITIMEN, 0);
   zelda_snes_dummy_write(HDMAEN, 0);
   zelda_snes_dummy_write(MDMAEN, 0);
@@ -273,7 +263,12 @@
   zelda_snes_dummy_write(NMITIMEN, 0x81);
 }
 
-void ZeldaRunGameLoop() {
+static void ClearOamBuffer() {  // 80841e
+  for (int i = 0; i < 128; i++)
+    oam_buf[i].y = 0xf0;
+}
+
+static void ZeldaRunGameLoop() {
   frame_counter++;
   ClearOamBuffer();
   Module_MainRouting();
@@ -293,7 +288,7 @@
   ppu_reset(g_zenv.ppu);
 }
 
-void ZeldaRunPolyLoop() {
+static void ZeldaRunPolyLoop() {
   if (intro_did_run_step && !nmi_flag_update_polyhedral) {
     Poly_RunFrame();
     intro_did_run_step = 0;
@@ -301,7 +296,7 @@
   }
 }
 
-void ZeldaRunFrame(uint16 input, int run_what) {
+void ZeldaRunFrameInternal(uint16 input, int run_what) {
   if (animated_tile_data_src == 0)
     ZeldaInitializationCode();
 
@@ -312,12 +307,40 @@
   Interrupt_NMI(input);
 }
 
-void ClearOamBuffer() {  // 80841e
-  for (int i = 0; i < 128; i++)
-    oam_buf[i].y = 0xf0;
+
+static int IncrementCrystalCountdown(uint8 *a, int v) {
+  int t = *a + v;
+  *a = t;
+  return t >> 8;
 }
 
-void Startup_InitializeMemory() {  // 8087c0
+int frame_ctr_dbg;
+static uint8 *g_emu_memory_ptr;
+static ZeldaRunFrameFunc *g_emu_runframe;
+static ZeldaSyncAllFunc *g_emu_syncall;
+
+void ZeldaSetupEmuCallbacks(uint8 *emu_ram, ZeldaRunFrameFunc *func, ZeldaSyncAllFunc *sync_all) {
+  g_emu_memory_ptr = emu_ram;
+  g_emu_runframe = func;
+  g_emu_syncall = sync_all;
+}
+
+static void EmuSynchronizeWholeState() {
+  if (g_emu_syncall)
+    g_emu_syncall();
+}
+
+// |ptr| must be a pointer into g_ram, will synchronize the RAM memory with the
+// emulator.
+static void EmuSyncMemoryRegion(void *ptr, size_t n) {
+  uint8 *data = (uint8 *)ptr;
+  assert(data >= g_ram && data < g_ram + 0x20000);
+  if (g_emu_memory_ptr)
+    memcpy(g_emu_memory_ptr + (data - g_ram), data, n);
+}
+
+
+static void Startup_InitializeMemory() {  // 8087c0
   memset(g_ram + 0x0, 0, 0x2000);
   main_palette_buffer[0] = 0;
   srm_var1 = 0;
@@ -333,11 +356,546 @@
   flag_update_cgram_in_nmi++;
 }
 
+
+typedef struct ByteArray {
+  uint8 *data;
+  size_t size, capacity;
+} ByteArray;
+
+void ByteArray_Resize(ByteArray *arr, size_t new_size) {
+  arr->size = new_size;
+  if (new_size > arr->capacity) {
+    size_t minsize = arr->capacity + (arr->capacity >> 1) + 8;
+    arr->capacity = new_size < minsize ? minsize : new_size;
+    void *data = realloc(arr->data, arr->capacity);
+    if (!data) Die("memory allocation failed");
+    arr->data = data;
+  }
+}
+
+void ByteArray_Destroy(ByteArray *arr) {
+  free(arr->data);
+  arr->data = NULL;
+}
+
+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;
+
+void loadFunc(void *ctx, void *data, size_t data_size) {
+  LoadFuncState *st = (LoadFuncState *)ctx;
+  assert(st->pend - st->p >= data_size);
+  memcpy(data, st->p, data_size);
+  st->p += data_size;
+}
+
+static void InternalSaveLoad(SaveLoadFunc *func, void *ctx) {
+  uint8 junk[58] = { 0 };
+  func(ctx, junk, 27);
+  func(ctx, g_zenv.player->ram, 0x10000);  // apu ram
+  func(ctx, junk, 40); // junk
+  dsp_saveload(g_zenv.player->dsp, func, ctx); // 3024 bytes of dsp
+  func(ctx, junk, 15); // spc junk
+  dma_saveload(g_zenv.dma, func, ctx); // 192 bytes of dma state
+  ppu_saveload(g_zenv.ppu, func, ctx); // 66619 + 512 + 174
+  func(ctx, g_zenv.sram, 0x2000);  // 8192 bytes of sram
+  func(ctx, junk, 58); // snes junk
+  func(ctx, g_zenv.ram, 0x20000);  // 0x20000 bytes of ram
+  func(ctx, junk, 4); // snes junk
+}
+
+void ZeldaReset(bool preserve_sram) {
+  frame_ctr_dbg = 0;
+  dma_reset(g_zenv.dma);
+  ppu_reset(g_zenv.ppu);
+  memset(g_zenv.ram, 0, 0x20000);
+  if (!preserve_sram)
+    memset(g_zenv.sram, 0, 0x2000);
+
+  SpcPlayer_Initialize(g_zenv.player);
+  EmuSynchronizeWholeState();
+}
+
+static void LoadSnesState(SaveLoadFunc *func, void *ctx) {
+  // Do the actual loading
+  InternalSaveLoad(func, ctx);
+  memcpy(g_zenv.ram + 0x1DBA0, g_zenv.ram + 0x1b00, 224 * 2); // hdma table was moved
+
+  // Restore spc variables from the ram dump.
+  SpcPlayer_CopyVariablesFromRam(g_zenv.player);
+  // This is not stored in the snapshot
+  g_zenv.player->timer_cycles = 0;
+  
+  // Ensure emulator has the up-to-date state too
+  EmuSynchronizeWholeState();
+
+  // Ensure we load any msu files
+  ZeldaOpenMsuFile();
+}
+
+static void SaveSnesState(SaveLoadFunc *func, void *ctx) {
+  memcpy(g_zenv.ram + 0x1b00, g_zenv.ram + 0x1DBA0, 224 * 2); // hdma table was moved
+  SpcPlayer_CopyVariablesToRam(g_zenv.player);
+  InternalSaveLoad(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;
+  uint8 replay_cmd;
+  bool replay_mode;
+
+  ByteArray log;
+  ByteArray base_snapshot;
+} StateRecorder;
+
+static StateRecorder state_recorder;
+
+void StateRecorder_Init(StateRecorder *sr) {
+  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 StateRecorder_Load(StateRecorder *sr, FILE *f, bool replay_mode) {
+  // todo: fix robustness on invalid data.
+  uint32 hdr[8] = { 0 };
+  fread(hdr, 1, sizeof(hdr), f);
+
+  assert(hdr[0] == 1);
+
+  sr->total_frames = hdr[1];
+  ByteArray_Resize(&sr->log, hdr[2]);
+  fread(sr->log.data, 1, sr->log.size, f);
+  sr->last_inputs = hdr[3];
+  sr->frames_since_last = hdr[4];
+
+  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->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) {
+      LoadFuncState state = { sr->base_snapshot.data, sr->base_snapshot.data + sr->base_snapshot.size };
+      LoadSnesState(&loadFunc, &state);
+      assert(state.p == state.pend);
+    } else {
+      ZeldaReset(false);
+      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);
+    LoadFuncState state = { arr.data, arr.data + arr.size };
+    LoadSnesState(&loadFunc, &state);
+    ByteArray_Destroy(&arr);
+    assert(state.p == state.pend);
+  }
+}
+
+void StateRecorder_Save(StateRecorder *sr, FILE *f) {
+  uint32 hdr[8] = { 0 };
+  ByteArray arr = { 0 };
+  SaveSnesState(&saveFunc, &arr);
+  assert(sr->base_snapshot.size == 0 || sr->base_snapshot.size == arr.size);
+
+  hdr[0] = 1;
+  hdr[1] = sr->total_frames;
+  hdr[2] = (uint32)sr->log.size;
+  hdr[3] = sr->last_inputs;
+  hdr[4] = sr->frames_since_last;
+  hdr[5] = sr->base_snapshot.size ? 1 : 0;
+  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[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);
+
+  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++];
+          EmuSyncMemoryRegion(&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;
+}
+
+#ifdef _DEBUG
+// This can be used to read inputs from a text file for easier debugging
+int InputStateReadFromFile() {
+  static FILE *f;
+  static uint32 next_ts, next_keys, cur_keys;
+  char buf[64];
+  char keys[64];
+
+  while (state_recorder.total_frames == next_ts) {
+    cur_keys = next_keys;
+    if (!f)
+      f = fopen("boss_bug.txt", "r");
+    if (fgets(buf, sizeof(buf), f)) {
+      if (sscanf(buf, "%d: %s", &next_ts, keys) == 1) keys[0] = 0;
+      int i = 0;
+      for (const char *s = keys; *s; s++) {
+        static const char kKeys[] = "AXsSUDLRBY";
+        const char *t = strchr(kKeys, *s);
+        assert(t);
+        i |= 1 << (t - kKeys);
+      }
+      next_keys = i;
+    } else {
+      next_ts = 0xffffffff;
+    }
+  }
+
+  return cur_keys;
+}
+#endif
+
+bool ZeldaRunFrame(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;
+
+  frame_ctr_dbg++;
+
+  bool is_replay = state_recorder.replay_mode;
+
+  // Either copy state or apply state
+  if (is_replay) {
+    inputs = StateRecorder_ReadNextReplayState(&state_recorder);
+  } else {
+    //    input_state = InputStateReadFromFile();
+    StateRecorder_Record(&state_recorder, inputs);
+
+    // This is whether APUI00 is true or false, this is used by the ancilla code.
+    uint8 apui00 = ZeldaIsMusicPlaying();
+    if (apui00 != g_ram[kRam_APUI00]) {
+      g_ram[kRam_APUI00] = apui00;
+      EmuSyncMemoryRegion(&g_ram[kRam_APUI00], 1);
+      StateRecorder_RecordPatchByte(&state_recorder, 0x648, &apui00, 1);
+    }
+
+    if (animated_tile_data_src != 0) {
+      // 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) {
+        g_ram[kRam_BugsFixed] = kBugFix_Latest;
+        EmuSyncMemoryRegion(&g_ram[kRam_BugsFixed], 1);
+        StateRecorder_RecordPatchByte(&state_recorder, kRam_BugsFixed, &g_ram[kRam_BugsFixed], 1);
+      }
+
+      if (enhanced_features0 != g_wanted_zelda_features) {
+        enhanced_features0 = g_wanted_zelda_features;
+        EmuSyncMemoryRegion(&enhanced_features0, sizeof(enhanced_features0));
+        StateRecorder_RecordPatchByte(&state_recorder, kRam_Features0, (uint8 *)&enhanced_features0, 4);
+      }
+    }
+  }
+
+  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;
+    EmuSyncMemoryRegion(&g_ram[kRam_CrystalRotateCounter], 1);
+  }
+
+  if (g_emu_runframe == NULL || enhanced_features0 != 0) {
+    // can't compare against real impl when running with extra features.
+    ZeldaRunFrameInternal(inputs, run_what);
+  } else {
+    g_emu_runframe(inputs, run_what);
+  }
+  return is_replay;
+}
+
+
+
+
+static const char *const kReferenceSaves[] = {
+  "Chapter 1 - Zelda's Rescue.sav",
+  "Chapter 2 - After Eastern Palace.sav",
+  "Chapter 3 - After Desert Palace.sav",
+  "Chapter 4 - After Tower of Hera.sav",
+  "Chapter 5 - After Hyrule Castle Tower.sav",
+  "Chapter 6 - After Dark Palace.sav",
+  "Chapter 7 - After Swamp Palace.sav",
+  "Chapter 8 - After Skull Woods.sav",
+  "Chapter 9 - After Gargoyle's Domain.sav",
+  "Chapter 10 - After Ice Palace.sav",
+  "Chapter 11 - After Misery Mire.sav",
+  "Chapter 12 - After Turtle Rock.sav",
+  "Chapter 13 - After Ganon's Tower.sav",
+};
+
+void SaveLoadSlot(int cmd, int which) {
+  char name[128];
+  if (which & 256) {
+    if (cmd == kSaveLoad_Save)
+      return;
+    sprintf(name, "saves/ref/%s", kReferenceSaves[which - 256]);
+  } else {
+    sprintf(name, "saves/save%d.sav", which);
+  }
+  FILE *f = fopen(name, cmd != kSaveLoad_Save ? "rb" : "wb");
+  if (f) {
+    printf("*** %s slot %d\n",
+      cmd == kSaveLoad_Save ? "Saving" : cmd == kSaveLoad_Load ? "Loading" : "Replaying", which);
+
+    if (cmd != kSaveLoad_Save)
+      StateRecorder_Load(&state_recorder, f, cmd == kSaveLoad_Replay);
+    else
+      StateRecorder_Save(&state_recorder, f);
+
+    fclose(f);
+  }
+}
+
+
+
+typedef struct StateRecoderMultiPatch {
+  uint32 count;
+  uint32 addr;
+  uint8 vals[256];
+} StateRecoderMultiPatch;
+
+
+void StateRecoderMultiPatch_Init(StateRecoderMultiPatch *mp) {
+  mp->count = mp->addr = 0;
+}
+
+void StateRecoderMultiPatch_Commit(StateRecoderMultiPatch *mp) {
+  if (mp->count)
+    StateRecorder_RecordPatchByte(&state_recorder, mp->addr, mp->vals, mp->count);
+}
+
+void StateRecoderMultiPatch_Patch(StateRecoderMultiPatch *mp, uint32 addr, uint8 value) {
+  if (mp->count >= 256 || addr != mp->addr + mp->count) {
+    StateRecoderMultiPatch_Commit(mp);
+    mp->addr = addr;
+    mp->count = 0;
+  }
+  mp->vals[mp->count++] = value;
+  g_ram[addr] = value;
+  EmuSyncMemoryRegion(&g_ram[addr], 1);
+}
+
+void PatchCommand(char c) {
+  StateRecoderMultiPatch mp;
+
+  StateRecoderMultiPatch_Init(&mp);
+  if (c == 'w') {
+    StateRecoderMultiPatch_Patch(&mp, 0xf372, 80);  // health filler
+    StateRecoderMultiPatch_Patch(&mp, 0xf373, 80);  // magic filler
+    //    b.Patch(0x1FE01, 25);
+  } else if (c == 'W') {
+    StateRecoderMultiPatch_Patch(&mp, 0xf375, 10);  // link_bomb_filler
+    StateRecoderMultiPatch_Patch(&mp, 0xf376, 10);  // link_arrow_filler
+    uint16 rupees = link_rupees_goal + 100;
+    StateRecoderMultiPatch_Patch(&mp, 0xf360, rupees);  // link_rupees_goal
+    StateRecoderMultiPatch_Patch(&mp, 0xf361, rupees >> 8);  // link_rupees_goal
+  } else if (c == 'k') {
+    StateRecorder_ClearKeyLog(&state_recorder);
+  } else if (c == 'o') {
+    StateRecoderMultiPatch_Patch(&mp, 0xf36f, 1);
+  } else if (c == 'l') {
+    StateRecorder_StopReplay(&state_recorder);
+  }
+  StateRecoderMultiPatch_Commit(&mp);
+}
+
+
+
 void LoadSongBank(const uint8 *p) {  // 808888
   SpcPlayer_Upload(g_zenv.player, p);
 }
 
-
 bool msu_enabled;
 static FILE *msu_file;
 static uint32 msu_loop_start;
@@ -395,7 +953,7 @@
   zelda_apu_write(APUI00, 0xf1);  // pause spc player
 }
 
-void MixinMsuAudioData(int16 *audio_buffer, int audio_samples) {  
+void MixinMsuAudioData(int16 *audio_buffer, int audio_samples) {
   if (msu_file == NULL)
     return;  // msu inactive
   // handle volume fade
@@ -450,5 +1008,34 @@
     }
     fseek(msu_file, msu_loop_start * 4 + 8, SEEK_SET);
     msu_curr_sample = msu_loop_start;
+  }
+}
+
+
+void ZeldaRenderAudio(int16 *audio_buffer, int samples, int channels) {
+  SpcPlayer_GenerateSamples(g_zenv.player);
+  dsp_getSamples(g_zenv.player->dsp, audio_buffer, samples, channels);
+  if (channels == 2)
+    MixinMsuAudioData(audio_buffer, samples);
+}
+
+
+void ZeldaReadSram() {
+  FILE *f = fopen("saves/sram.dat", "rb");
+  if (f) {
+    fread(g_zenv.sram, 1, 8192, f);
+    fclose(f);
+    EmuSynchronizeWholeState();
+  }
+}
+
+void ZeldaWriteSram() {
+  rename("saves/sram.dat", "saves/sram.bak");
+  FILE *f = fopen("saves/sram.dat", "wb");
+  if (f) {
+    fwrite(g_zenv.sram, 1, 8192, f);
+    fclose(f);
+  } else {
+    fprintf(stderr, "Unable to write saves/sram.dat\n");
   }
 }
--- a/zelda_rtl.h
+++ b/zelda_rtl.h
@@ -1,16 +1,18 @@
-#ifndef ZELDA_RTL_H
-#define ZELDA_RTL_H
-#pragma once
+// This file defines various things related to the runtime environment
+// of the code
+#ifndef ZELDA3_ZELDA_RTL_H_
+#define ZELDA3_ZELDA_RTL_H_
 
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 #include <stdbool.h>
 #include <assert.h>
 
 #include "types.h"
+#include "features.h"
 
 struct Snes;
+struct Dsp;
 
 typedef struct ZeldaEnv {
   uint8 *ram;
@@ -21,147 +23,33 @@
   struct Dma *dma;
 } ZeldaEnv;
 extern ZeldaEnv g_zenv;
+extern int frame_ctr_dbg;
 
-// it's here so that the variables.h can access it
-extern uint8 g_ram[131072];
-extern const uint16 kUpperBitmasks[];
-extern const uint8 kLitTorchesColorPlus[];
-extern const uint8 kDungeonCrystalPendantBit[];
-extern const int8 kGetBestActionToPerformOnTile_x[];
-extern const int8 kGetBestActionToPerformOnTile_y[];
+typedef void PlayerHandlerFunc();
+typedef void HandlerFuncK(int k);
 
 static inline void zelda_snes_dummy_write(uint32 adr, uint8 val) {}
 
-typedef struct MovableBlockData {
-  uint16 room;
-  uint16 tilemap;
-} MovableBlockData;
-
-typedef struct OamEntSigned {
-  int8 x, y;
-  uint8 charnum, flags;
-} OamEntSigned;
-
-
-
-#define movable_block_datas ((MovableBlockData*)(g_ram+0xf940))
-#define oam_buf ((OamEnt*)(g_ram+0x800))
-
-
-typedef struct RoomBounds {
-  union {
-    struct { uint16 a0, b0, a1, b1; };
-    uint16 v[4];
-  };
-} RoomBounds;
-#define room_bounds_y (*(RoomBounds*)(g_ram+0x600))
-#define room_bounds_x (*(RoomBounds*)(g_ram+0x608))
-
-
-typedef struct OwScrollVars {
-  uint16 ystart, yend, xstart, xend;
-} OwScrollVars;
-
-
-#define ow_scroll_vars0 (*(OwScrollVars*)(g_ram+0x600))
-#define ow_scroll_vars1 (*(OwScrollVars*)(g_ram+0x608))
-
-#define ow_scroll_vars0_exit (*(OwScrollVars*)(g_ram+0xC154))
-
-extern const uint8 kLayoutQuadrantFlags[];
-extern const uint8 kVariousPacks[16];
-extern const uint8 kMaxBombsForLevel[];
-extern const uint8 kMaxArrowsForLevel[];
-extern const uint8 kReceiveItem_Tab1[76];
-extern const uint8 kHealthAfterDeath[21];
-extern const uint8 kReceiveItemGfx[76];
-extern const uint16 kOverworld_OffsetBaseY[64];
-extern const uint16 kOverworld_OffsetBaseX[64];
-
-// forwards
-
-
-typedef struct MirrorHdmaVars {
-  uint16 var0;
-  uint16 var1[2];
-  uint16 var3[2];
-  uint16 var5;
-  uint16 var6;
-  uint16 var7;
-  uint16 var8;
-  uint16 var9;
-  uint16 var10;
-  uint16 var11;
-  uint16 pad;
-  uint8 ctr2, ctr;
-} MirrorHdmaVars;
-
-
-// Special RAM locations that are unused but I use for compat things.
-enum {
-  kRam_APUI00 = 0x648,
-  kRam_CrystalRotateCounter = 0x649,
-  kRam_BugsFixed = 0x64a,
-  kRam_Features0 = 0x64c,
-};
-
-enum {
-  // Poly rendered uses correct speed
-  kBugFix_PolyRenderer = 1,
-  kBugFix_AncillaOverwrites = 1,
-  kBugFix_Latest = 1,
-};
-
-// Enum values for kRam_Features0
-enum {
-  kFeatures0_ExtendScreen64 = 1,
-  kFeatures0_SwitchLR = 2,
-  kFeatures0_TurnWhileDashing = 4,
-  kFeatures0_MirrorToDarkworld = 8,
-  kFeatures0_CollectItemsWithSword = 16,
-  kFeatures0_BreakPotsWithSword = 32,
-  kFeatures0_DisableLowHealthBeep = 64,
-  kFeatures0_SkipIntroOnKeypress = 128,
-  kFeatures0_ShowMaxItemsInYellow = 256,
-  kFeatures0_MoreActiveBombs = 512,
-
-  // This is set for visual fixes that don't affect game behavior but will affect ram compare.
-  kFeatures0_WidescreenVisualFixes = 1024,
-};
-
-#define enhanced_features0 (*(uint32*)(g_ram+0x64c))
-#define msu_curr_sample (*(uint32*)(g_ram+0x650))
-#define msu_volume (*(uint8*)(g_ram+0x654))
-#define msu_track (*(uint8*)(g_ram+0x655))
-#define hud_cur_item_x (*(uint8*)(g_ram+0x656))
-#define hud_inventory_order ((uint8*)(g_ram + 0x225)) // 4x6 bytes
-
-extern uint32 g_wanted_zelda_features;
-extern bool msu_enabled;
-
 void zelda_apu_write(uint32_t adr, uint8_t val);
-void zelda_apu_write_word(uint32_t adr, uint16_t val);
 uint8_t zelda_read_apui00();
 uint8_t zelda_apu_read(uint32_t adr);
-uint16_t zelda_apu_read_word(uint32_t adr);
 void zelda_ppu_write(uint32_t adr, uint8_t val);
 void zelda_ppu_write_word(uint32_t adr, uint16_t val);
-void zelda_apu_runcycles();
-const uint8 *SimpleHdma_GetPtr(uint32 p);
 
+
 // 512x480 32-bit pixels. Returns true if we instead draw 1024x960
-bool ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags);
 void HdmaSetup(uint32 addr6, uint32 addr7, uint8 transfer_unit, uint8 reg6, uint8 reg7, uint8 indirect_bank);
-void ZeldaInitializationCode();
-void ZeldaRunGameLoop();
+
 void ZeldaInitialize();
-void ZeldaRunFrame(uint16 input, int run_what);
-void ClearOamBuffer();
-void Startup_InitializeMemory();
+void ZeldaReset(bool preserve_sram);
+bool ZeldaDrawPpuFrame(uint8 *pixel_buffer, size_t pitch, uint32 render_flags);
+void ZeldaRunFrameInternal(uint16 input, int run_what);
+bool ZeldaRunFrame(int input_state);
 void LoadSongBank(const uint8 *p);
-void ZeldaWriteSram();
-void ZeldaReadSram(struct Snes *snes);
 
+void PatchCommand(char cmd);
+
+// Things for msu
 void ZeldaPlayMsuAudioTrack();
 void MixinMsuAudioData(int16 *audio_buffer, int audio_samples);
 void ZeldaOpenMsuFile();
@@ -168,4 +56,23 @@
 bool ZeldaIsMusicPlaying();
 
 
-#endif  // ZELDA_RTL_H
+// Things for state management
+
+enum {
+  kSaveLoad_Save = 0,
+  kSaveLoad_Load = 1,
+  kSaveLoad_Replay = 2,
+};
+
+void SaveLoadSlot(int cmd, int which);
+void ZeldaWriteSram();
+void ZeldaReadSram();
+
+void ZeldaRenderAudio(int16 *audio_buffer, int samples, int channels);
+
+typedef void ZeldaRunFrameFunc(uint16 input, int run_what);
+typedef void ZeldaSyncAllFunc();
+
+void ZeldaSetupEmuCallbacks(uint8 *emu_ram, ZeldaRunFrameFunc *func, ZeldaSyncAllFunc *sync_all);
+
+#endif  // ZELDA3_ZELDA_RTL_H_