ref: 5d1360efa9bd226a8078aeb3e12e2e9b10699558
parent: e6b5294e8775120fa652e1cb1a636c9a034a5bf7
author: Snesrev <[email protected]>
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_