mirror of
https://github.com/snesrev/zelda3.git
synced 2025-12-19 18:05:55 -05:00
Clean up and refactor the emulator vs own state
This commit is contained in:
50
features.h
Normal file
50
features.h
Normal 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
82
main.c
@@ -11,31 +11,21 @@
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <math.h>
|
||||
#include "snes/snes.h"
|
||||
#include "tracing.h"
|
||||
|
||||
#include "snes/ppu.h"
|
||||
|
||||
#include "types.h"
|
||||
#include "variables.h"
|
||||
|
||||
#include "zelda_rtl.h"
|
||||
#include "zelda_cpu_infra.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "assets.h"
|
||||
|
||||
extern Dsp *GetDspForRendering();
|
||||
extern Snes *g_snes;
|
||||
extern uint8 g_emulated_ram[0x20000];
|
||||
|
||||
void PatchRom(uint8 *rom);
|
||||
void SetSnes(Snes *snes);
|
||||
void RunAudioPlayer();
|
||||
void CopyStateAfterSnapshotRestore(bool is_reset);
|
||||
void SaveLoadSlot(int cmd, int which);
|
||||
void PatchCommand(char cmd);
|
||||
bool RunOneFrame(Snes *snes, int input_state);
|
||||
|
||||
static bool LoadRom(const char *name, Snes *snes);
|
||||
static void PlayAudio(Snes *snes, SDL_AudioDeviceID device, int channels, int16 *audioBuffer);
|
||||
// Forwards
|
||||
static bool LoadRom(const char *name);
|
||||
static void PlayAudio(SDL_AudioDeviceID device, int channels, int16 *audioBuffer);
|
||||
static void RenderScreen(SDL_Window *window, SDL_Renderer *renderer, SDL_Texture *texture, bool fullscreen);
|
||||
static void HandleInput(int keyCode, int modCode, bool pressed);
|
||||
static void HandleGamepadInput(int button, bool pressed);
|
||||
@@ -285,18 +275,8 @@ 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;
|
||||
}
|
||||
|
||||
17
snes/ppu.c
17
snes/ppu.c
@@ -6,7 +6,6 @@
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include "ppu.h"
|
||||
#include "snes.h"
|
||||
#include "../types.h"
|
||||
|
||||
static const uint8 kSpriteSizes[8][2] = {
|
||||
@@ -24,9 +23,9 @@ static 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
snes/ppu.h
13
snes/ppu.h
@@ -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_
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
10
spc_player.c
10
spc_player.c
@@ -1,13 +1,11 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <SDL.h>
|
||||
#include <assert.h>
|
||||
#include "types.h"
|
||||
|
||||
#include "snes/spc.h"
|
||||
#include "snes/dsp_regs.h"
|
||||
#include "tracing.h"
|
||||
|
||||
#include "spc_player.h"
|
||||
|
||||
@@ -1274,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
20
types.h
@@ -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_
|
||||
|
||||
98
variables.h
98
variables.h
@@ -1,4 +1,5 @@
|
||||
|
||||
#ifndef ZELDA3_VARIABLES_H_
|
||||
#define ZELDA3_VARIABLES_H_
|
||||
#define main_module_index (*(uint8*)(g_ram+0x10))
|
||||
#define submodule_index (*(uint8*)(g_ram+0x11))
|
||||
#define nmi_boolean (*(uint8*)(g_ram+0x12))
|
||||
@@ -1398,7 +1399,6 @@
|
||||
|
||||
|
||||
#define turn_on_off_water_ctr (*(uint8*)(g_ram+0x424))
|
||||
#define mirror_vars (*(MirrorHdmaVars*)(g_ram+0x6A0))
|
||||
#define sprite_N_word ((uint16*)(g_ram+0xBC0))
|
||||
#define sprite_where_in_overworld ((uint8*)(g_ram+0x1DF80))
|
||||
#define alt_sprite_B ((uint8*)(g_ram+0x1FA5C))
|
||||
@@ -1427,3 +1427,97 @@
|
||||
// Relocated the hdma table so it can fit 240 rows
|
||||
#define hdma_table_dynamic_orig_pos ((uint16*)(g_ram+0x1B00))
|
||||
#define hdma_table_dynamic ((uint16*)(g_ram+0x1DBA0))
|
||||
|
||||
|
||||
|
||||
|
||||
typedef struct MovableBlockData {
|
||||
uint16 room;
|
||||
uint16 tilemap;
|
||||
} MovableBlockData;
|
||||
|
||||
typedef struct OamEntSigned {
|
||||
int8 x, y;
|
||||
uint8 charnum, flags;
|
||||
} OamEntSigned;
|
||||
|
||||
|
||||
|
||||
#define movable_block_datas ((MovableBlockData*)(g_ram+0xf940))
|
||||
#define oam_buf ((OamEnt*)(g_ram+0x800))
|
||||
|
||||
|
||||
typedef struct RoomBounds {
|
||||
union {
|
||||
struct { uint16 a0, b0, a1, b1; };
|
||||
uint16 v[4];
|
||||
};
|
||||
} RoomBounds;
|
||||
#define room_bounds_y (*(RoomBounds*)(g_ram+0x600))
|
||||
#define room_bounds_x (*(RoomBounds*)(g_ram+0x608))
|
||||
|
||||
|
||||
typedef struct OwScrollVars {
|
||||
uint16 ystart, yend, xstart, xend;
|
||||
} OwScrollVars;
|
||||
|
||||
|
||||
#define ow_scroll_vars0 (*(OwScrollVars*)(g_ram+0x600))
|
||||
#define ow_scroll_vars1 (*(OwScrollVars*)(g_ram+0x608))
|
||||
|
||||
#define ow_scroll_vars0_exit (*(OwScrollVars*)(g_ram+0xC154))
|
||||
|
||||
extern const uint8 kLayoutQuadrantFlags[];
|
||||
extern const uint8 kVariousPacks[16];
|
||||
extern const uint8 kMaxBombsForLevel[];
|
||||
extern const uint8 kMaxArrowsForLevel[];
|
||||
extern const uint8 kReceiveItem_Tab1[76];
|
||||
extern const uint8 kHealthAfterDeath[21];
|
||||
extern const uint8 kReceiveItemGfx[76];
|
||||
extern const uint16 kOverworld_OffsetBaseY[64];
|
||||
extern const uint16 kOverworld_OffsetBaseX[64];
|
||||
|
||||
// forwards
|
||||
|
||||
|
||||
typedef struct MirrorHdmaVars {
|
||||
uint16 var0;
|
||||
uint16 var1[2];
|
||||
uint16 var3[2];
|
||||
uint16 var5;
|
||||
uint16 var6;
|
||||
uint16 var7;
|
||||
uint16 var8;
|
||||
uint16 var9;
|
||||
uint16 var10;
|
||||
uint16 var11;
|
||||
uint16 pad;
|
||||
uint8 ctr2, ctr;
|
||||
} MirrorHdmaVars;
|
||||
#define mirror_vars (*(MirrorHdmaVars*)(g_ram+0x6A0))
|
||||
|
||||
|
||||
typedef struct UploadVram_Row {
|
||||
uint16 col[32];
|
||||
} UploadVram_Row;
|
||||
|
||||
typedef struct UploadVram_32x32 {
|
||||
UploadVram_Row row[32];
|
||||
} UploadVram_32x32;
|
||||
|
||||
typedef struct UploadVram_3 {
|
||||
uint8 pad[256];
|
||||
uint16 data[4];
|
||||
} UploadVram_3;
|
||||
|
||||
#define uvram (*(UploadVram_3*)(&g_ram[0x1000]))
|
||||
|
||||
extern uint8 g_ram[131072];
|
||||
extern const uint16 kUpperBitmasks[];
|
||||
extern const uint8 kLitTorchesColorPlus[];
|
||||
extern const uint8 kDungeonCrystalPendantBit[];
|
||||
extern const int8 kGetBestActionToPerformOnTile_x[];
|
||||
extern const int8 kGetBestActionToPerformOnTile_y[];
|
||||
|
||||
|
||||
#endif // ZELDA3_VARIABLES_H_
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
|
||||
651
zelda_rtl.c
651
zelda_rtl.c
@@ -6,11 +6,16 @@
|
||||
#include "attract.h"
|
||||
#include "snes/ppu.h"
|
||||
#include "snes/snes_regs.h"
|
||||
#include "snes/dma.h"
|
||||
#include "spc_player.h"
|
||||
|
||||
ZeldaEnv g_zenv;
|
||||
// These point to the rewritten instance of the emu.
|
||||
uint8 g_ram[131072];
|
||||
|
||||
uint32 g_wanted_zelda_features;
|
||||
|
||||
static void Startup_InitializeMemory();
|
||||
|
||||
typedef struct SimpleHdma {
|
||||
const uint8 *table;
|
||||
const uint8 *indir_ptr;
|
||||
@@ -19,8 +24,8 @@ 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");
|
||||
}
|
||||
}
|
||||
|
||||
173
zelda_rtl.h
173
zelda_rtl.h
@@ -1,16 +1,18 @@
|
||||
#ifndef ZELDA_RTL_H
|
||||
#define ZELDA_RTL_H
|
||||
#pragma once
|
||||
// This file defines various things related to the runtime environment
|
||||
// of the code
|
||||
#ifndef ZELDA3_ZELDA_RTL_H_
|
||||
#define ZELDA3_ZELDA_RTL_H_
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "types.h"
|
||||
#include "features.h"
|
||||
|
||||
struct Snes;
|
||||
struct Dsp;
|
||||
|
||||
typedef struct ZeldaEnv {
|
||||
uint8 *ram;
|
||||
@@ -21,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_
|
||||
|
||||
Reference in New Issue
Block a user