Clean up and refactor the emulator vs own state

This commit is contained in:
Snesrev
2022-09-28 18:06:00 +02:00
parent e6b5294e87
commit 5d1360efa9
17 changed files with 1124 additions and 1078 deletions

50
features.h Normal file
View File

@@ -0,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_

82
main.c
View File

@@ -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 @@ int main(int argc, char** argv) {
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 @@ int main(int argc, char** argv) {
mkdir("saves", 0755);
#endif
SetSnes(snes);
ZeldaReadSram(snes);
ZeldaReadSram();
for (int i = 0; i < SDL_NumJoysticks(); i++)
OpenOneGamepad(i);
@@ -371,11 +350,7 @@ int main(int argc, char** argv) {
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 = RunOneFrame(snes_run, inputs);
bool is_replay = ZeldaRunFrame(inputs);
if ((g_turbo ^ (is_replay & g_replay_turbo)) && (frameCtr++ & (g_turbo ? 0xf : 0x7f)) != 0)
continue;
@@ -383,7 +358,7 @@ int main(int argc, char** argv) {
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 @@ int main(int argc, char** argv) {
}
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,19 +411,8 @@ int main(int argc, char** argv) {
}
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;
}
dsp_getSamples(GetDspForRendering(), audioBuffer, g_samples_per_block, channels);
// Mixin the msu data?
if (channels == 2)
MixinMsuAudioData(audioBuffer, g_samples_per_block);
static void PlayAudio(SDL_AudioDeviceID device, int channels, int16 *audioBuffer) {
ZeldaRenderAudio(audioBuffer, g_samples_per_block, channels);
for (int i = 0; i < 10; i++) {
if (SDL_GetQueuedAudioSize(device) <= g_samples_per_block * channels * sizeof(int16) * 6) {
@@ -584,9 +546,7 @@ static void HandleCommand(uint32 j, bool pressed) {
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 @@ static float ApproximateAtan2(float y, float x) {
// 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 void HandleGamepadAxisInput(int gamepad_id, int axis, int value) {
}
}
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;
}

View File

@@ -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 bool ppu_evaluateSprites(Ppu* ppu, int line);
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 @@ void ppu_reset(Ppu* ppu) {
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 @@ uint8_t ppu_read(Ppu* ppu, uint8_t adr) {
}
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 @@ uint8_t ppu_read(Ppu* ppu, uint8_t adr) {
return val;
}
default: {
return ppu->snes->openBus;
return 0xff;
}
}
}

View File

@@ -1,16 +1,15 @@
#ifndef PPU_H
#define PPU_H
#ifndef ZELDA3_SNES_PPU_H_
#define ZELDA3_SNES_PPU_H_
#include <stdio.h>
#include <stdlib.h>
#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 @@ struct Ppu {
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 @@ struct Ppu {
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 @@ bool PpuBeginDrawing(Ppu *ppu, uint8_t *buffer, size_t pitch, uint32_t render_fl
void PpuSetMode7PerspectiveCorrection(Ppu *ppu, int low, int high);
void PpuSetExtraSideSpace(Ppu *ppu, int left, int right, int bottom);
#endif
#endif // ZELDA3_SNES_PPU_H_

View File

@@ -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 uint8_t snes_readReg(Snes* snes, uint16_t adr);
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 @@ static void snes_catchupApu(Snes* snes) {
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 @@ uint8_t snes_readBBus(Snes* snes, uint8_t adr) {
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) {

View File

@@ -85,12 +85,6 @@ void snes_doAutoJoypad(Snes *snes);
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

View File

@@ -1,208 +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/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;
}
}
#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;
}
}

View File

@@ -1,17 +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/snes.h"
void getProcessorStateCpu(Snes* snes, char* line);
void getProcessorStateSpc(Apu* apu, char* line);
#endif
#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

View File

@@ -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,6 +1272,11 @@ void SpcPlayer_Upload(SpcPlayer *p, const uint8_t *data) {
}
// =======================================
#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;
@@ -1442,4 +1445,5 @@ void RunAudioPlayer() {
}
}
}
}
}
#endif // WITH_SPC_PLAYER_DEBUGGING

20
types.h
View File

@@ -55,9 +55,11 @@ static inline uint16 swap16(uint16 v) { return (v << 8) | (v >> 8); }
typedef struct Point16U {
uint16 x, y;
} Point16U;
typedef struct PointU8 {
uint8 x, y;
} PointU8;
typedef struct Pair16U {
uint16 a, b;
} Pair16U;
@@ -75,24 +77,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);
#endif // ZELDA3_TYPES_H_

View File

@@ -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_

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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 @@ static uint8_t *RomByte(Cart *cart, uint32_t addr) {
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 RunOrigAsmCodeOneLoop(Snes *snes) {
}
}
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 @@ void RunEmulatedSnesFrame(Snes *snes, int run_what) {
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 @@ bool RunOneFrame(Snes *snes, int input_state) {
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 @@ again_mine:
}
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 @@ void PatchRom(uint8_t *rom) {
}
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);
}

View File

@@ -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_

View File

@@ -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 @@ typedef struct SimpleHdma {
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 @@ void zelda_apu_write(uint32_t adr, uint8_t val) {
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 @@ uint8_t zelda_apu_read(uint32_t adr) {
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 @@ void zelda_ppu_write_word(uint32_t adr, uint16_t val) {
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 @@ const uint8 *SimpleHdma_GetPtr(uint32 p) {
}
}
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 @@ void SimpleHdma_Init(SimpleHdma *c, DmaChannel *dc) {
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 @@ void SimpleHdma_DoLine(SimpleHdma *c) {
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 @@ void HdmaSetup(uint32 addr6, uint32 addr7, uint8 transfer_unit, uint8 reg6, uint
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 @@ void ZeldaInitializationCode() {
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 @@ void ZeldaInitialize() {
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 ZeldaRunPolyLoop() {
}
}
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 @@ void ZeldaRunFrame(uint16 input, int run_what) {
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 @@ void Startup_InitializeMemory() { // 8087c0
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 @@ void ZeldaPlayMsuAudioTrack() {
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
@@ -452,3 +1010,32 @@ void MixinMsuAudioData(int16 *audio_buffer, int audio_samples) {
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");
}
}

View File

@@ -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,151 +23,56 @@ typedef struct ZeldaEnv {
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 LoadSongBank(const uint8 *p);
void ZeldaWriteSram();
void ZeldaReadSram(struct Snes *snes);
void ZeldaInitialize();
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 PatchCommand(char cmd);
// Things for msu
void ZeldaPlayMsuAudioTrack();
void MixinMsuAudioData(int16 *audio_buffer, int audio_samples);
void ZeldaOpenMsuFile();
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_