Files
sm-vita/src/sm_cpu_infra.c
2023-04-04 04:11:07 +02:00

998 lines
33 KiB
C

#include "sm_cpu_infra.h"
#include "types.h"
#include "snes/cpu.h"
#include "snes/snes.h"
#include "tracing.h"
#include "ida_types.h"
#include "variables.h"
#include "funcs.h"
#include "sm_rtl.h"
#include "util.h"
#include <time.h>
void RtlRunFrameCompare(uint16 input, int run_what);
enum RunMode { RM_BOTH, RM_MINE, RM_THEIRS };
uint8 g_runmode = RM_BOTH;
extern int g_got_mismatch_count;
Snes *g_snes;
Cpu *g_cpu;
bool g_calling_asm_from_c;
int g_calling_asm_from_c_ret;
bool g_fail;
bool g_use_my_apu_code = true;
extern bool g_other_image;
typedef struct Snapshot {
uint16 a, x, y, sp, dp, pc;
uint8 k, db, flags;
uint16_t vTimer;
uint8 ram[0x20000];
uint16 vram[0x8000];
uint8 sram[0x2000];
uint16 oam[0x120];
} Snapshot;
static Snapshot g_snapshot_mine, g_snapshot_theirs, g_snapshot_before;
static uint32 hookmode, hookcnt, hookadr;
static uint32 hooked_func_pc;
static uint8 hook_orgbyte[1024];
static uint8 hook_fixbug_orgbyte[1024];
static void VerifySnapshotsEq(Snapshot *b, Snapshot *a, Snapshot *prev);
static void MakeSnapshot(Snapshot *s);
static void RestoreSnapshot(Snapshot *s);
void Call(uint32 addr) {
assert(addr & 0x8000);
RunAsmCode(addr, 0, 0, 0, 0);
}
uint8_t *SnesRomPtr(uint32 v) {
return (uint8*)RomPtr(v);
}
bool ProcessHook(uint32 v) {
uint8_t *rombyte = SnesRomPtr(v);
switch (hookmode) {
case 0: // remove hooks
*rombyte = hook_orgbyte[hookcnt++];
return false;
case 1: // install hooks
hook_orgbyte[hookcnt++] = *rombyte;
*rombyte = 0;
return false;
case 2: // run hook
if (v == hookadr) {
hookmode = 3;
return true;
}
return false;
}
return false;
}
bool FixBugHook(uint32 addr) {
switch (hookmode) {
case 1: { // install hooks
uint8_t *rombyte = SnesRomPtr(addr);
hook_fixbug_orgbyte[hookcnt++] = *rombyte;
*rombyte = 0;
return false;
}
case 2: // run hook
if (addr == hookadr) {
hookmode = 3;
return true;
}
hookcnt++;
return false;
}
return false;
}
static const uint32 kPatchedCarrys[] = {
0xa7ac33,
0xa7ac36,
0xa7ac39,
0xa7ac42,
0xa7ac45,
// Ridley_Func_107
0xa6d6d1,
0xa6d6d3,
0xa6d6d5,
0xa6d700,
0xa6d702,
0xa6d704,
// Ridley_Func_106
0xa6d665,
0xa6d667,
0xa6d669,
0xa6d694,
0xa6d696,
0xa6d698,
// DrawSpritemapWithBaseTile2
0x818b65,
0x818b6B,
// DrawSpritemapWithBaseTileOffscreen
0x818ba7,
0x818bd9,
0x818bdf,
// EprojInit_BombTorizoLowHealthInitialDrool
0x86a671,
0x86a680,
0x86a6a9,
0x86a6ba,
// HandleEarthquakeSoundEffect
0x88B245,
// Ridley_Func_104
0xA6D565,
0xA6D567,
0xA6D599,
0xA6D59B,
// Ridley_Func_105
0xA6D5DB,
0xA6D5DD,
0xA6D60F,
0xA6D611,
// Ridley_Func_86
0xA6CEFF,
// Shitroid_GraduallyAccelerateTowardsPt
0xa9f4a5,
0xa9f4a7,
0xa9f4d6,
0xa9f4d8,
// Shitroid_GraduallyAccelerateHoriz
0xa9f519,
0xa9f51f,
0xa9f521,
0xa9f554,
0xa9f55a,
0xa9f55c,
// Shitroid_Func_16
0xA9F24D,
// Lots of ADC
0x80AA6A,
0x80A592,
0x80A720,
0x80A7D6,
0x80A99B,
0x818AA7,
0x94B176,
0x94B156,
// MotherBrain
0xA99413,
// room_width_in_blocks etc
0x80ab5d,
0x84865c,
0x848d90,
0x84ab60,
0x84b567,
0x84b588,
0x84b606,
0x84b615,
0x84b624,
0x84b9d3,
0x84b9e2,
0x84ba07,
0x84ba1e,
0x84ba35,
0x84d6ae,
0x84d6bf,
0x84d7f4,
0x84d803,
0x84d812,
0x84daae,
0x84dbaa,
0x84dbe1,
0x84dc20,
0x84dc52,
0x84dc89,
0x84dcc8,
0x84deae,
0x84dedd,
0x84df0a,
0x84df39,
0x86893a,
0x9483a7,
0x948405,
0x949592,
0x94a13f,
0x94a2b2,
0x94a3d8,
0xa0bc33,
0xa0bdac,
0xa0bf45,
0xa0c725,
0x88B486,
0x88C578,
0xA292E8,
0x86F18E,
0x888CB6,
0x888FAA,
0x88A483,
0x91CC35,
0x91CBFF,
0xA09541,
0xA09552,
0xA49AE8,
0xA6C297,
0xA6C3AD,
0xA9C5EC,
0xA9D500,
0xA9D537,
0xA9DCDB,
0xA0A31B,
0x91D064,
0x91D07A,
0x90C719,
0xA6A80E,
0xA6A816,
0xA4906E,
0xA49071,
0x90BC75,
0x90BC93,
};
static uint8 kPatchedCarrysOrg[arraysize(kPatchedCarrys)];
uint32 PatchBugs(uint32 mode, uint32 addr) {
hookmode = mode, hookadr = addr, hookcnt = 0;
if (FixBugHook(0x86EF35)) {
g_cpu->x = g_cpu->y;
} else if (FixBugHook(0x86EF45)) {
g_cpu->z = (g_cpu->a == 0); // EprojInit_F337 doesn't compare A and assumes Z is in it.
} else if (FixBugHook(0x818ab8)) {
if (g_cpu->y == 0)
g_cpu->pc = 0x8b1f;
} else if (FixBugHook(0xa794ba)) { // Kraid_Arm_Shot - y undefined
g_cpu->y = g_cpu->x;
} else if (FixBugHook(0xa7b968)) { // KraidEnemy_ProcessInstrEnemyTimer - X is undefined
g_cpu->x = cur_enemy_index;
} else if (FixBugHook(0xa7b963)) { // KraidFoot_FirstPhase_Thinking - X is undefined
g_cpu->x = cur_enemy_index;
} else if (FixBugHook(0xA496C8)) { // Crocomire_Func_67 assumes A is zero
g_cpu->a = 0;
} else if (FixBugHook(0x9085AA)) { // Samus_HandleSpeedBoosterAnimDelay doesn't preserve A
g_cpu->a = speed_boost_counter;
} else if (FixBugHook(0xA29044) || FixBugHook(0xA2905D)) { // MaridiaBeybladeTurtle_Func8 thinks INC sets carry
g_cpu->c = (g_cpu->a == 0);
} else if (FixBugHook(0xa29051)) { /// MaridiaBeybladeTurtle_Func8 does an INC too much
g_cpu->a--;
} else if (FixBugHook(0xA5931C)) { // Draygon_Func_35 needs cur_enemy_index in X
g_cpu->x = cur_enemy_index;
} else if (FixBugHook(0x80ADA4)) { // DoorTransitionScrollingSetup_Down
g_cpu->a = layer2_y_pos;
} else if (FixBugHook(0x80ADD9)) { // DoorTransitionScrollingSetup_Up
g_cpu->a = layer2_y_pos;
} else if (FixBugHook(0x80AD4d)) { // DoorTransitionScrollingSetup_Right
g_cpu->a = layer2_x_pos;
} else if (FixBugHook(0x80AD77)) { // DoorTransitionScrollingSetup_Left
g_cpu->a = layer2_x_pos;
} else if (FixBugHook(0x9381db)) { // ProjectileInsts_GetValue reading from invalid memory for newly started ones
int k = g_cpu->x;
int ip = projectile_bomb_instruction_ptr[k >> 1];
int delta = (projectile_bomb_instruction_timers[k >> 1] == 1 && !sign16(get_ProjectileInstr(ip)->timer)) ? 0 : -8;
g_cpu->a += 8 + delta;
} else if (FixBugHook(0x86b701)) { // EprojPreInstr_EyeDoorProjectile using destroyed X
g_cpu->x = g_cpu->y;
} else if (FixBugHook(0x8FC1B0)) { // RoomCode_GenRandomExplodes X is garbage
g_cpu->x = g_cpu->a;
} else if (FixBugHook(0x80804F)) {
//for(int i = 0; i < 5000; i++)
// snes_readBBusOrg(g_snes, (uint8_t)APUI00);
} else if (FixBugHook(0x829325)) {
// forgot to change bank
g_cpu->db = 0x82;
} else if (FixBugHook(0x848ACD)) {
// PlmInstr_IncrementArgumentAndJGE A is not zeroed
g_cpu->a = 0;
} else if (FixBugHook(0xA7CEB2)) {
// Phantoon_Main forgots to reload x
g_cpu->x = cur_enemy_index;
} else if (FixBugHook(0x91CD44)) {
// Xray_SetupStage4_Func2 passes a bad value to Xray_GetXrayedBlock
if (g_cpu->x == 0)
g_cpu->pc = 0xCD52;
// Fix VAR BEAM etc.
// Prevent EquipmentScreenCategory_ButtonResponse from getting called when category changed
} else if (FixBugHook(0x82AFD3)) {
if ((uint8)pausemenu_equipment_category_item != 1)
return 0x82AFD9;
} else if (FixBugHook(0x82B0CD)) {
if ((uint8)pausemenu_equipment_category_item != 2)
return 0x82AFD9;
} else if (FixBugHook(0x82B15B)) {
if ((uint8)pausemenu_equipment_category_item != 3)
return 0x82AFD9;
} else if (FixBugHook(0xA2D38C)) {
// MaridiaLargeSnail_Touch uses uninitialized X
g_cpu->x = cur_enemy_index;
} else if (FixBugHook(0xA4970F)) {
// Crocomire_Func_67 does weird things
g_cpu->a &= 0xff;
g_cpu->y = g_cpu->x & 0x7;
} else if (FixBugHook(0xA496E0)) {
if (g_cpu->x > 48) {
croco_cur_vline_idx = g_cpu->x;
g_cpu->mf = 0;
return 0xA497CE;
}
} else if (FixBugHook(0x91DA89)) {
// Samus_HandleScrewAttackSpeedBoostingPals reads OOB
if (special_samus_palette_frame > 6)
special_samus_palette_frame = 6;
} else if (FixBugHook(0x828D56)) {
WriteReg(VMAIN, 0x80); // BackupBG2TilemapForPauseMenu lacks this
} else if (FixBugHook(0x88AFCF)) {
if (g_cpu->a & 0x8000) // RoomMainAsm_ScrollingSky reads oob
g_cpu->a = 0;
} else if (FixBugHook(0x88AFF2)) {
if (g_cpu->a < 256) // RoomMainAsm_ScrollingSky reads oob
g_cpu->a = 256;
} else if (FixBugHook(0x8189bd)) {
if (g_cpu->y == 0) // DrawSamusSpritemap reads invalid ptr
return 0x818A35;
} else if (FixBugHook(0xA29BC1)) {
g_cpu->a = 1; // ThinHoppingBlobs_Func8 reads from R1 instead of #1
} else if (FixBugHook(0x82E910)) {
WORD(g_ram[22]) = 0; // SpawnDoorClosingPLM doesn't zero R22
} else if (FixBugHook(0x90A4C8)) {
WORD(g_ram[18]) = 0; // Samus_InitJump overwrites R18 in Samus_Movement_03_SpinJumping
} else if (FixBugHook(0xA99F60)) {
WORD(g_ram[22]) = 1; // MotherBrain_Instr_SpawnLaserEproj doesn't set R22
} else if (FixBugHook(0x94A85B)) {
memset(g_ram + 0xd82, 0, 8); // grapple_beam_tmpD82 not cleared in BlockCollGrappleBeam
} else if (FixBugHook(0xA0A35C)) {
// ProcessEnemyPowerBombInteraction - R18 may get overwritten by the enemy death routine
REMOVED_R18 = HIBYTE(power_bomb_explosion_radius);
REMOVED_R20 = (REMOVED_R18 + (REMOVED_R18 >> 1)) >> 1;
} else if (FixBugHook(0xA7B049)) {
// Kraid_Shot_Mouth: The real game doesn't preserve R18, R20 so they're junk at this point.
// Force getting out of the loop.
g_cpu->x = 0;
} else if (FixBugHook(0xa5a018)) {
// Draygon_Func_42 uses undefined varE24 value
REMOVED_varE24 = 0;
}
return 0;
}
int RunPatchBugHook(uint32 addr) {
uint32 new_pc = PatchBugs(2, addr);
if (hookmode == 3) {
if (new_pc == 0) {
return hook_fixbug_orgbyte[hookcnt];
} else {
g_cpu->k = new_pc >> 16;
g_cpu->pc = (new_pc & 0xffff) + 1;
return *SnesRomPtr(new_pc);
}
}
return -1;
}
int CpuOpcodeHook(uint32 addr) {
for (size_t i = 0; i != arraysize(kPatchedCarrys); i++) {
if (addr == kPatchedCarrys[i])
return kPatchedCarrysOrg[i];
}
{
int i = RunPatchBugHook(addr);
if (i >= 0) return i;
}
assert(0);
return 0;
}
bool HookedFunctionRts(int is_long) {
if (g_calling_asm_from_c) {
g_calling_asm_from_c_ret = is_long;
g_calling_asm_from_c = false;
return false;
}
assert(0);
return false;
}
static void VerifySnapshotsEq(Snapshot *b, Snapshot *a, Snapshot *prev) {
memcpy(&b->ram[0x0], &a->ram[0x0], 0x34); // r18, r20, R22 etc
memcpy(&b->ram[0x1f5b], &a->ram[0x1f5b], 0x100 - 0x5b); // stacck
memcpy(&a->ram[0x3c], &b->ram[0x3c], 2); // nmicopy1_var_d
memcpy(&b->ram[0x44], &a->ram[0x44], 13); // decompress temp
memcpy(&b->ram[0xad], &a->ram[0xad], 2); // ptr_to_retaddr_parameters
memcpy(&b->ram[0x5e7], &a->ram[0x5e7], 14); // bitmask, mult_tmp, mult_product_lo etc
memcpy(&a->ram[0x60B], &b->ram[0x60B], 6); // enemy_projectile_init_param_2, remaining_enemy_hitbox_entries, REMOVED_num_projectiles_to_check_enemy_coll
memcpy(&a->ram[0x611], &b->ram[0x611], 6); // coroutine_state (copy from mine to theirs)
memcpy(&b->ram[0x641], &a->ram[0x641], 2); // apu_attempts_countdown
memcpy(&a->ram[0x77e], &b->ram[0x77e], 5); // my counter
memcpy(&b->ram[0xA82], &a->ram[0xA82], 2); // xray_angle
memcpy(&a->ram[0xd1e], &b->ram[0xd1e], 2); // grapple_beam_unkD1E
memcpy(&a->ram[0xd82], &b->ram[0xd82], 8); // grapple_beam_tmpD82
memcpy(&a->ram[0xd9c], &b->ram[0xd9c], 2); // grapple_beam_tmpD82
memcpy(&a->ram[0xdd2], &b->ram[0xdd2], 6); // temp_collision_DD2 etc
memcpy(&a->ram[0xd8a], &b->ram[0xd8a], 6); // grapple_beam_tmpD8A
memcpy(&a->ram[0xe20], &b->ram[0xe20], 0xe46 - 0xe20); // temp vars
memcpy(&a->ram[0xe54], &b->ram[0xe54], 2); // cur_enemy_index
memcpy(&a->ram[0xe02], &b->ram[0xe02], 2); // samus_bottom_boundary_position
memcpy(&a->ram[0xe4a], &b->ram[0xe4a], 2); // new_enemy_index
memcpy(&a->ram[0xe56], &b->ram[0xe56], 4); // REMOVED_cur_enemy_index_backup etc
memcpy(&a->ram[0x1784], &b->ram[0x1784], 8); // enemy_ai_pointer etc
memcpy(&a->ram[0x1790], &b->ram[0x1790], 4); // set_to_rtl_when_loading_enemies_unused etc
memcpy(&a->ram[0x17a8], &b->ram[0x17a8], 4); // interactive_enemy_indexes_index
memcpy(&a->ram[0x1834], &b->ram[0x1834], 8); // distance_to_enemy_colliding_dirs
memcpy(&a->ram[0x184A], &b->ram[0x184A], 18); // samus_x_pos_colliding_solid etc
memcpy(&a->ram[0x186E], &b->ram[0x186E], 16+8); // REMOVED_enemy_spritemap_entry_pointer etc
memcpy(&a->ram[0x18A6], &b->ram[0x18A6], 2); // collision_detection_index
memcpy(&a->ram[0x189A], &b->ram[0x189A], 12); // samus_target_x_pos etc
memcpy(&b->ram[0x1993], &a->ram[0x1993], 2); // enemy_projectile_init_param
memcpy(&b->ram[0x19b3], &a->ram[0x19b3], 2); // mode7_spawn_param
memcpy(&b->ram[0x1a93], &a->ram[0x1a93], 2); // cinematic_spawn_param
memcpy(&b->ram[0x1B9D], &a->ram[0x1B9D], 2); // cinematic_spawn_param
memcpy(&a->ram[0x1E77], &b->ram[0x1E77], 2); // current_slope_bts
memcpy(&a->ram[0x9100], &b->ram[0x9100], 0x1cc + 2); // XrayHdmaFunc has some bug that i couldn't fix in asm
memcpy(&a->ram[0x9800], &b->ram[0x9800], 0x1cc+2); // XrayHdmaFunc has some bug that i couldn't fix in asm
memcpy(&a->ram[0x99cc], &b->ram[0x99cc], 2); // XrayHdmaFunc_BeamAimedL writes outside
memcpy(&a->ram[0xEF74], &b->ram[0xEF74], 4); // next_enemy_tiles_index
memcpy(&a->ram[0xF37A], &b->ram[0xF37A], 6); // word_7EF37A etc
if (memcmp(b->ram, a->ram, 0x20000)) {
fprintf(stderr, "@%d: Memory compare failed (mine != theirs, prev):\n", snes_frame_counter);
int j = 0;
for (size_t i = 0; i < 0x20000; i++) {
if (a->ram[i] != b->ram[i]) {
if (++j < 256) {
if (((i & 1) == 0 || i < 0x10000) && a->ram[i + 1] != b->ram[i + 1]) {
fprintf(stderr, "0x%.6X: %.4X != %.4X (%.4X)\n", (int)i,
WORD(b->ram[i]), WORD(a->ram[i]), WORD(prev->ram[i]));
i++, j++;
} else {
fprintf(stderr, "0x%.6X: %.2X != %.2X (%.2X)\n", (int)i, b->ram[i], a->ram[i], prev->ram[i]);
}
}
}
}
if (j)
g_fail = true;
fprintf(stderr, " total of %d failed bytes\n", (int)j);
}
if (memcmp(b->sram, a->sram, 0x2000)) {
fprintf(stderr, "@%d: SRAM compare failed (mine != theirs, prev):\n", snes_frame_counter);
int j = 0;
for (size_t i = 0; i < 0x2000; i++) {
if (a->sram[i] != b->sram[i]) {
if (++j < 128) {
if ((i & 1) == 0 && a->sram[i + 1] != b->sram[i + 1]) {
fprintf(stderr, "0x%.6X: %.4X != %.4X (%.4X)\n", (int)i,
WORD(b->sram[i]), WORD(a->sram[i]), WORD(prev->sram[i]));
i++, j++;
} else {
fprintf(stderr, "0x%.6X: %.2X != %.2X (%.2X)\n", (int)i, b->sram[i], a->sram[i], prev->sram[i]);
}
}
}
}
if (j)
g_fail = true;
fprintf(stderr, " total of %d failed bytes\n", (int)j);
}
#if 1
if (memcmp(b->vram, a->vram, sizeof(uint16) * 0x8000)) {
fprintf(stderr, "@%d: VRAM compare failed (mine != theirs, prev):\n", snes_frame_counter);
for (size_t i = 0, j = 0; i < 0x8000; i++) {
if (a->vram[i] != b->vram[i]) {
fprintf(stderr, "0x%.6X: %.4X != %.4X (%.4X)\n", (int)i, b->vram[i], a->vram[i], prev->vram[i]);
g_fail = true;
if (++j >= 32)
break;
}
}
}
if (memcmp(b->oam, a->oam, sizeof(uint16) * 0x120)) {
fprintf(stderr, "@%d: VRAM OAM compare failed (mine != theirs, prev):\n", snes_frame_counter);
for (size_t i = 0, j = 0; i < 0x120; i++) {
if (a->oam[i] != b->oam[i]) {
fprintf(stderr, "0x%.6X: %.4X != %.4X (%.4X)\n", (int)i, b->oam[i], a->oam[i], prev->oam[i]);
g_fail = true;
if (++j >= 16)
break;
}
}
}
#endif
}
static void MakeSnapshot(Snapshot *s) {
Cpu *c = g_cpu;
s->a = c->a, s->x = c->x, s->y = c->y;
s->sp = c->sp, s->dp = c->dp, s->db = c->db;
s->pc = c->pc, s->k = c->k;
s->flags = cpu_getFlags(c);
s->vTimer = g_snes->vTimer;
memcpy(s->ram, g_snes->ram, 0x20000);
memcpy(s->sram, g_snes->cart->ram, g_snes->cart->ramSize);
memcpy(s->vram, g_snes->ppu->vram, sizeof(uint16) * 0x8000);
memcpy(s->oam, g_snes->ppu->oam, sizeof(uint16) * 0x120);
}
static void MakeMySnapshot(Snapshot *s) {
memcpy(s->ram, g_snes->ram, 0x20000);
memcpy(s->sram, g_snes->cart->ram, g_snes->cart->ramSize);
memcpy(s->vram, g_snes->ppu->vram, sizeof(uint16) * 0x8000);
memcpy(s->oam, g_snes->ppu->oam, sizeof(uint16) * 0x120);
}
static void RestoreSnapshot(Snapshot *s) {
Cpu *c = g_cpu;
c->a = s->a, c->x = s->x, c->y = s->y;
c->sp = s->sp, c->dp = s->dp, c->db = s->db;
c->pc = s->pc, c->k = s->k;
g_snes->vTimer = s->vTimer;
cpu_setFlags(c, s->flags);
memcpy(g_snes->ram, s->ram, 0x20000);
memcpy(g_snes->cart->ram, s->sram, 0x2000);
memcpy(g_snes->ppu->vram, s->vram, sizeof(uint16) * 0x8000);
memcpy(g_snes->ppu->oam, s->oam, sizeof(uint16) * 0x120);
}
int RunAsmCode(uint32 pc, uint16 a, uint16 x, uint16 y, int flags) {
uint16 org_sp = g_cpu->sp;
uint16 org_pc = g_cpu->pc;
uint8 org_b = g_cpu->db;
uint16 org_dp = g_cpu->dp;
printf("RunAsmCode!\n");
g_ram[0x1ffff] = 1;
bool dc = g_snes->debug_cycles;
g_cpu->db = pc >> 16;
g_cpu->a = a;
g_cpu->x = x;
g_cpu->y = y;
g_cpu->spBreakpoint = g_cpu->sp;
g_cpu->k = (pc >> 16);
g_cpu->pc = (pc & 0xffff);
g_cpu->mf = (flags & 1);
g_cpu->xf = (flags & 2) >> 1;
g_calling_asm_from_c = true;
while (g_calling_asm_from_c) {
uint32 pc = g_snes->cpu->k << 16 | g_snes->cpu->pc;
if (g_snes->debug_cycles) {
char line[80];
getProcessorStateCpu(g_snes, line);
puts(line);
line[0] = 0;
}
cpu_runOpcode(g_cpu);
while (g_snes->dma->dmaBusy)
dma_doDma(g_snes->dma);
if (flags & 1) {
for(int i = 0; i < 10; i++)
apu_cycle(g_snes->apu);
}
}
g_cpu->dp = org_dp;
g_cpu->sp = org_sp;
g_cpu->db = org_b;
g_cpu->pc = org_pc;
g_snes->debug_cycles = dc;
return g_calling_asm_from_c_ret;
}
static bool loadRom(const char *name, Snes *snes) {
size_t length = 0;
uint8_t *file = NULL;
file = ReadWholeFile(name, &length);
if (file == NULL) {
puts("Failed to read file");
return false;
}
bool result = snes_loadRom(snes, file, (int)length);
free(file);
return result;
}
void PatchBytes(uint32 addr, const uint8 *value, size_t n) {
for(size_t i = 0; i != n; i++)
SnesRomPtr(addr)[i] = value[i];
}
// Patches add/sub to ignore carry
void FixupCarry(uint32 addr) {
*SnesRomPtr(addr) = 0;
}
void RtlUpdateSnesPatchForBugfix() {
// Patch HandleMessageBoxInteraction logic
{ uint8 t[] = { 0x20, 0x50, 0x96, 0x60 }; PatchBytes(0x8584A3, t, sizeof(t)); }
// while ((bug_fix_counter < 1 ? joypad1_newkeys : joypad1_lastkeys) == 0);
{ uint8 t[] = { 0x20, 0x36, 0x81, 0x22, 0x59, 0x94, 0x80, 0xc2, 0x30, 0xa5, (bug_fix_counter < 1) ? 0x8f : 0x8b, 0xf0, 0xf3, 0x60 }; PatchBytes(0x859650, t, sizeof(t)); }
{ uint8 t[] = { 0x18, 0x18 }; PatchBytes(0x8584CC, t, sizeof(t)); } // Don't wait 2 loops
}
Snes *SnesInit(const char *filename) {
g_snes = snes_init(g_ram);
g_cpu = g_snes->cpu;
bool loaded = loadRom(filename, g_snes);
if (!loaded) {
return NULL;
}
g_sram = g_snes->cart->ram;
g_rom = g_snes->cart->rom;
RtlSetupEmuCallbacks(NULL, &RtlRunFrameCompare, NULL);
// Ensure it will run reset first.
coroutine_state_0 = 1;
#if 1
{ uint8 t[3] = { 0x20, 0x0f, 0xf7 }; PatchBytes(0x82896b, t, 3); }
{ uint8 t[3] = { 0x7c, 0x81, 0x89 }; PatchBytes(0x82F70F, t, 3); }
// Some code called by GameState_37_CeresGoesBoomWithSamus_ forgets to clear the M flag
{ uint8 t[] = { 0x5f, 0xf7 }; PatchBytes(0x8BA362, t, sizeof(t)); }
{ uint8 t[] = { 0xc2, 0x20, 0x4c, 0x67, 0xa3 }; PatchBytes(0x8BF760, t, sizeof(t)); }
//{ uint8 t[3] = { 0x60 }; PatchBytes(0x808028, t, 1); } // Apu_UploadBank hangs
{ uint8 t[2] = { 0x0a, 0x0a }; PatchBytes(0x8584B2, t, 2); } // HandleMessageBoxInteraction has a loop
// LoadRoomPlmGfx passes bad value
{ uint8 t[] = { 0xc0, 0x00, 0x00, 0xf0, 0x03, 0x20, 0x64, 0x87, 0x60}; PatchBytes(0x84efd3, t, sizeof(t)); }
{ uint8 t[] = { 0xd3, 0xef }; PatchBytes(0x848243, t, sizeof(t)); }
// EprojColl_8676 doesn't initialize Y
{ uint8 t[] = { 0xac, 0x91, 0x19, 0x4c, 0x76, 0x86 }; PatchBytes(0x86f4a6, t, sizeof(t)); }
{ uint8 t[] = { 0xa6, 0xf4 }; PatchBytes(0x8685bd, t, sizeof(t)); }
// Fix so main code is in a function.
{ uint8 t[] = { 0xc2, 0x30, 0x22, 0x59, 0x94, 0x80, 0x20, 0x48, 0x89, 0x22, 0x38, 0x83, 0x80, 0x4C, 0x13, 0xF7 }; PatchBytes(0x82f713, t, sizeof(t)); }
{ uint8 t[] = { 0x58, 0x4c, 0x13, 0xf7 }; PatchBytes(0x828944, t, sizeof(t)); }
{ uint8 t[] = { 0x28, 0x60 }; PatchBytes(0x82897a, t, sizeof(t)); }
// Remove IO_HVBJOY loop in ReadJoypadInput
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x80945C, t, sizeof(t)); }
// Fix so NorfairLavaMan_Func_12 initializes Y
{ uint8 t[] = { 0xbc, 0xaa, 0x0f, 0xc9, 0x6c, 0x00, 0x10, 0x1a }; PatchBytes(0xa8b237, t, sizeof(t)); }
// MaridiaBeybladeTurtle_Func8 negate
{ uint8 t[] = { 0x49, 0xff, 0xff, 0x69, 0x00, 0x00 }; PatchBytes(0xa2904b, t, sizeof(t)); }
{ uint8 t[] = { 0x49, 0xff, 0xff, 0x69, 0x00, 0x00 }; PatchBytes(0xa29065, t, sizeof(t)); }
// Remove DebugLoadEnemySetData
{ uint8 t[] = { 0x6b }; PatchBytes(0xA0896F, t, sizeof(t)); }
// MotherBrainsTubesFalling_Falling wrong X value
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0xA98C12, t, sizeof(t)); }
{ uint8 t[] = { 0x60 }; PatchBytes(0x8085F6, t, sizeof(t)); }
// Remove 4 frames of delay in reset routine
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x80843C, t, sizeof(t)); }
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x808475, t, sizeof(t)); }
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x808525, t, sizeof(t)); }
// Remove WaitUntilEndOfVblank in WaitUntilEndOfVblankAndClearHdma - We run frame by frame.
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x8882A1, t, sizeof(t)); }
// Remove WaitForNMI in GameState_41_TransitionToDemo.
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x828533, t, sizeof(t)); }
// WaitForNMI in ScreenOfWaitNmi / ScreenOnWaitNMI
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x80837B, t, sizeof(t)); }
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x80838E, t, sizeof(t)); }
// WaitUntilEndOfVblankAndEnableIrq
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x82DF6C, t, sizeof(t)); }
// Remove loops based on door_transition_vram_update_enabled
// Replace with a call to Irq_DoorTransitionVramUpdate
{ uint8 t[] = { 0x20, 0x32, 0x96, 0x6b }; PatchBytes(0x80d000, t, sizeof(t)); }
{ uint8 t[] = { 0x22, 0x00, 0xd0, 0x80, 0x18 }; PatchBytes(0x82E02C, t, sizeof(t)); }
{ uint8 t[] = { 0x22, 0x00, 0xd0, 0x80, 0x18 }; PatchBytes(0x82E06B, t, sizeof(t)); }
{ uint8 t[] = { 0x22, 0x00, 0xd0, 0x80, 0x18 }; PatchBytes(0x82E50D, t, sizeof(t)); }
{ uint8 t[] = { 0x22, 0x00, 0xd0, 0x80, 0x18 }; PatchBytes(0x82E609, t, sizeof(t)); }
// Remove infinite loop polling door_transition_flag (AD 31 09 10 FB)
{ uint8 t[] = { 0x22, 0x04, 0xd0, 0x80, 0x18 }; PatchBytes(0x82E526, t, sizeof(t)); }
{ uint8 t[] = { 0x22, 0x38, 0x83, 0x80, 0xad, 0x31, 0x09, 0x10, 0xf7, 0x6b }; PatchBytes(0x80d004, t, sizeof(t)); }
// Remove WaitForNMI in DoorTransitionFunction_LoadMoreThings_Async
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x82E540, t, sizeof(t)); }
// Remove WaitForNMI in CinematicFunctionBlackoutFromCeres
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x8BC11E, t, sizeof(t)); }
// Remove WaitForNMI in CinematicFunctionEscapeFromCeres
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x8BD487, t, sizeof(t)); }
// Patch InitializePpuForMessageBoxes
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x858148, t, sizeof(t)); } // WaitForLagFrame
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x8581b2, t, sizeof(t)); } // WaitForLagFrame
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x8581EA, t, sizeof(t)); } // HandleMusicQueue etc
// Patch ClearMessageBoxBg3Tilemap
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x858203, t, sizeof(t)); } // WaitForLagFrame
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x858236, t, sizeof(t)); } // HandleMusicQueue etc
// Patch WriteMessageTilemap
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x8582B8, t, sizeof(t)); }
// Patch SetupPpuForActiveMessageBox
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x858321, t, sizeof(t)); } // WaitForLagFrame
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x85835A, t, sizeof(t)); } // InitializePpuForMessageBoxes
// Patch ToggleSaveConfirmationSelection
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x858532, t, sizeof(t)); } // WaitForNMI_NoUpdate
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x85856b, t, sizeof(t)); } // HandleMusicQueue etc.
// Patch DisplayMessageBox
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x858096, t, sizeof(t)); } // Remove MsgBoxDelayFrames_2
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x8580B4, t, sizeof(t)); } // Remove MsgBoxDelayFrames_2
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x8580DC, t, sizeof(t)); } // Remove MsgBoxDelayFrames_2
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x8580F2, t, sizeof(t)); } // Remove MsgBoxDelayFrames_2
// Patch RestorePpuForMessageBox
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x85861C, t, sizeof(t)); } // WaitForNMI_NoUpdate
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x858651, t, sizeof(t)); } // WaitForNMI_NoUpdate
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x858692, t, sizeof(t)); } // HdmaObjectHandler
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x858696, t, sizeof(t)); } // HandleSoundEffects
// Patch Fix_MsgBoxMakeHdmaTable_NoSleep
{ uint8 t[] = { 0x08, 0xc2, 0x30, 0x4c, 0xa9, 0x85 }; PatchBytes(0x859660, t, sizeof(t)); }
{ uint8 t[] = { 0x20, 0x60, 0x96 }; PatchBytes(0x8583BA, t, sizeof(t)); } // MsgBoxMakeHdmaTable
// Patch GunshipTop_13 to not block
{ uint8 t[] = { 0x22, 0x81, 0x96, 0x85, 0xc9, 0xff, 0xff, 0xd0, 0x04, 0x5c, 0x5f, 0xab, 0xa2, 0x5c, 0x26, 0xab, 0xa2 }; PatchBytes(0x859670, t, sizeof(t)); } // DisplayMessageBox_DoubleRet
{ uint8 t[] = { 0xcd, 0x1f, 0x1c, 0xd0, 0x08, 0x9c, 0x1f, 0x1c, 0xad, 0xf9, 0x05, 0x6b, 0xff, 0x8d, 0xc8, 0x0d, 0xa9, 0xff, 0xff, 0x6b }; PatchBytes(0x859681, t, sizeof(t)); } // DisplayMessageBox_Poll
{ uint8 t[] = { 0x5c, 0x70, 0x96, 0x85 }; PatchBytes(0xa2ab22, t, sizeof(t)); } // GunshipTop_13
// EnemyMain_WithCheckMsgBox
{ uint8 t[] = { 0x22, 0xd4, 0x8f, 0xa0, 0xad, 0xc8, 0x0d, 0xf0, 0x07, 0x22, 0x95, 0x96, 0x85, 0x9c, 0xc8, 0x0d, 0x6b }; PatchBytes(0x8596a0, t, sizeof(t)); }
{ uint8 t[] = { 0x22, 0xa0, 0x96, 0x85 }; PatchBytes(0x828b65, t, sizeof(t)); } // EnemyMain -> EnemyMain_WithCheckMsgBox
// CloseMessageBox_ResetMsgBoxIdx
{ uint8 t[] = { 0x20, 0x89, 0x85, 0xa9, 0x1c, 0x00, 0x8d, 0x1f, 0x1c, 0x60 }; PatchBytes(0x8596C0, t, sizeof(t)); }
{ uint8 t[] = { 0x20, 0xC0, 0x96 }; PatchBytes(0x8580E5, t, sizeof(t)); }
// ProcessPlm_CheckMessage
{ uint8 t[] = { 0xad, 0xc8, 0x0d, 0xf0, 0x11, 0x98, 0x9d, 0x27, 0x1d, 0xad, 0xc8, 0x0d, 0x22, 0x95, 0x96, 0x85, 0x9c, 0xc8, 0x0d, 0xbc, 0x27, 0x1d, 0x4c, 0xee, 0x85 }; PatchBytes(0x84EFDC, t, sizeof(t)); }
{ uint8 t[] = { 0xf4, 0xdb, 0xef }; PatchBytes(0x8485f7, t, sizeof(t)); }
// Hook DisplayMessageBox so it writes to queued_message_box_index instead
{ uint8 t[] = { 0x08, 0x8b, 0xda, 0x5a, 0x5c, 0x84, 0x80, 0x85 }; PatchBytes(0x859695, t, sizeof(t)); } // DisplayMessageBox_Org
{ uint8 t[] = { 0x8d, 0xc8, 0x0d, 0x6b }; PatchBytes(0x858080, t, sizeof(t)); } // Hook
// PlmInstr_ActivateSaveStationAndGotoIfNo_Fixed
{ uint8 t[] = { 0x22, 0x81, 0x96, 0x85, 0xc9, 0xff, 0xff, 0xf0, 0x04, 0x5c, 0xfa, 0x8c, 0x84, 0x7a, 0xfa, 0x88, 0x88, 0x60 }; PatchBytes(0x84f000, t, sizeof(t)); } // Restart if -1
{ uint8 t[] = { 0x5c, 0x00, 0xf0, 0x84 }; PatchBytes(0x848cf6, t, sizeof(t)); } // PlmInstr_ActivateSaveStationAndGotoIfNo
// SoftReset
{ uint8 t[] = { 0xa9, 0xff, 0xff, 0x8d, 0x98, 0x09, 0x60 }; PatchBytes(0x81F000, t, sizeof(t)); }
{ uint8 t[] = { 0x5c, 0x00, 0xf0, 0x81 }; PatchBytes(0x819027, t, sizeof(t)); }
{ uint8 t[] = { 0x5c, 0x00, 0xf0, 0x81 }; PatchBytes(0x819112, t, sizeof(t)); }
{ uint8 t[] = { 0x5c, 0x00, 0xf0, 0x81 }; PatchBytes(0x8194e9, t, sizeof(t)); }
// Remove ReadJoypadInputs from Vector_NMI
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x8095E1, t, sizeof(t)); } // callf ReadJoypadInputs
// Remove APU_UploadBank
if (g_use_my_apu_code)
{ uint8 t[] = { 0x60 }; PatchBytes(0x808028, t, sizeof(t)); }
// Remove reads from IO_APUI01 etc
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x80 }; PatchBytes(0x828A59, t, sizeof(t)); } // SfxHandlers_1_WaitForAck
{ uint8 t[] = { 0x18, 0x18, 0x18 }; PatchBytes(0x828A72, t, sizeof(t)); } // SfxHandlers_2_ClearRequest
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x80 }; PatchBytes(0x828A80, t, sizeof(t)); } // SfxHandlers_3_WaitForAck
{ uint8 t[] = { 0x06 }; PatchBytes(0x828A67, t, sizeof(t)); } // sfx_clear_delay
// LoadStdBG3andSpriteTilesClearTilemaps does DMA from RAM
{ uint8 t[] = { 0x00, 0x2E }; PatchBytes(0x82831E, t, sizeof(t)); }
{ uint8 t[] = { 0xa5, 0x25 }; PatchBytes(0x91C234, t, sizeof(t)); } // Bugfix in XrayHdmaFunc_BeamAimedUUL
// Remove call to InitializeMiniMapBroken
{ uint8 t[] = { 0x18, 0x18, 0x18, 0x18 }; PatchBytes(0x809AF3, t, sizeof(t)); } // callf InitializeMiniMapBroken
// NormalEnemyShotAiSkipDeathAnim_CurEnemy version that preserves R18 etc.
{ uint8 t[] = { 0xA5, 0x12, 0x48, 0xA5, 0x14, 0x48, 0xA5, 0x16, 0x48, 0x22, 0xA7, 0xA6, 0xA0, 0x68, 0x85, 0x16, 0x68, 0x85, 0x14, 0x68, 0x85, 0x12, 0x6B }; PatchBytes(0xA7FF82, t, sizeof(t)); }
{ uint8 t[] = { 0x22, 0x82, 0xff, 0xa7 }; PatchBytes(0xa7b03a, t, sizeof(t)); }
RtlUpdateSnesPatchForBugfix();
for (size_t i = 0; i != arraysize(kPatchedCarrys); i++) {
uint8 t = *SnesRomPtr(kPatchedCarrys[i]);
if (t) {
kPatchedCarrysOrg[i] = t;
FixupCarry(kPatchedCarrys[i]);
} else {
printf("0x%x double patched!\n", kPatchedCarrys[i]);
}
}
PatchBugs(1, 0);
#endif
return g_snes;
}
void DebugGameOverMenu(void) {
assert(0);
}
uint32 RunCpuUntilPC(uint32 pc1, uint32 pc2) {
for(;;) {
if (g_snes->debug_cycles) {
char line[80];
getProcessorStateCpu(g_snes, line);
puts(line);
}
cpu_runOpcode(g_cpu);
uint32 addr = g_snes->cpu->k << 16 | g_snes->cpu->pc;
if (addr == pc1 || addr == pc2)
return addr;
}
}
void RunOneFrameOfGame_Emulated(void) {
uint16 bug_fix_bak = bug_fix_counter;
// Execute until either WaitForNMI or WaitForLagFrame
RunCpuUntilPC(0x808343, 0x85813C);
// Trigger nmi, then run until WaitForNMI or WaitForLagFrame returns
g_snes->cpu->nmiWanted = true;
RunCpuUntilPC(0x80834A, 0x858142);
bug_fix_counter = bug_fix_bak;
}
void DrawFrameToPpu(void) {
g_snes->hPos = g_snes->vPos = 0;
while (!g_snes->cpu->nmiWanted) {
do {
snes_handle_pos_stuff(g_snes);
} while (g_snes->hPos != 0);
if (g_snes->vIrqEnabled && (g_snes->vPos - 1) == g_snes->vTimer) {
Vector_IRQ();
}
}
g_snes->cpu->nmiWanted = false;
}
void SaveBugSnapshot() {
if (!g_debug_flag && g_got_mismatch_count == 0) {
char buffer[64];
snprintf(buffer, sizeof(buffer), "saves/bug-%d.sav", (int)time(NULL));
RtlSaveSnapshot(buffer, true);
}
g_got_mismatch_count = 5 * 60;
}
void RunOneFrameOfGame_Both(void) {
g_snes->ppu = g_snes->snes_ppu;
MakeSnapshot(&g_snapshot_before);
// Run orig version then snapshot
again_theirs:
g_snes->runningWhichVersion = 1;
RunOneFrameOfGame_Emulated();
DrawFrameToPpu();
MakeSnapshot(&g_snapshot_theirs);
// Run my version and snapshot
//again_mine:
g_snes->ppu = g_snes->my_ppu;
RestoreSnapshot(&g_snapshot_before);
g_snes->runningWhichVersion = 2;
RunOneFrameOfGame();
DrawFrameToPpu();
MakeSnapshot(&g_snapshot_mine);
g_snes->runningWhichVersion = 0xff;
// Compare both snapshots
VerifySnapshotsEq(&g_snapshot_mine, &g_snapshot_theirs, &g_snapshot_before);
if (g_fail) {
g_fail = false;
printf("Verify failure!\n");
g_snes->ppu = g_snes->snes_ppu;
RestoreSnapshot(&g_snapshot_before);
if (g_debug_flag)
goto again_theirs;
SaveBugSnapshot();
RunOneFrameOfGame_Emulated();
goto getout;
}
g_snes->ppu = g_snes->snes_ppu;
RestoreSnapshot(&g_snapshot_theirs);
getout:
g_snes->ppu = g_other_image ? g_snes->my_ppu : g_snes->snes_ppu;
g_snes->runningWhichVersion = 0;
// Trigger soft reset?
if (game_state == 0xffff) {
g_snes->cpu->k = 0x80;
g_snes->cpu->pc = 0x8462;
coroutine_state_0 = 3;
}
if (menu_index & 0xff00) {
printf("MENU INDEX TOO BIG!\n");
SaveBugSnapshot();
menu_index &= 0xff;
}
if (g_got_mismatch_count)
g_got_mismatch_count--;
}
void RtlRunFrameCompare(uint16 input, int run_what) {
g_snes->input1->currentState = input;
if (g_runmode == RM_THEIRS) {
RunOneFrameOfGame_Emulated();
DrawFrameToPpu();
} else if (g_runmode == RM_MINE) {
g_use_my_apu_code = true;
g_snes->runningWhichVersion = 0xff;
RunOneFrameOfGame();
DrawFrameToPpu();
g_snes->runningWhichVersion = 0;
} else {
g_use_my_apu_code = true;
RunOneFrameOfGame_Both();
}
}