#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 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); } bool ProcessHook(uint32 v) { uint8_t *rombyte = RomPtr(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 = RomPtr(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, // 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, }; 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_HandleXrayedBlock 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; } 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 *RomPtr(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[0x1f5b], &a->ram[0x1f5b], 0x100 - 0x5b); // stacck memcpy(&b->ram[0x44], &a->ram[0x44], 3); // decompress_dst_tmp memcpy(&b->ram[0x19b3], &a->ram[0x19b3], 1); // mode7_spawn_param memcpy(&b->ram[0x12], &a->ram[0x12], 6); // R18, R20, R22 memcpy(&b->ram[0x1993], &a->ram[0x1993], 2); // enemy_projectile_init_param memcpy(&b->ram[0x49], &a->ram[0x49], 1); // decompress_src.bank memcpy(&b->ram[0x1B9D], &a->ram[0x1B9D], 2); // cinematic_spawn_param memcpy(&b->ram[0x1a93], &a->ram[0x1a93], 2); // cinematic_spawn_param memcpy(&b->ram[0xA82], &a->ram[0xA82], 2); // xray_angle memcpy(&b->ram[0xad], &a->ram[0xad], 2); // ptr_to_retaddr_parameters memcpy(&b->ram[0x641], &a->ram[0x641], 2); // apu_attempts_countdown memcpy(&b->ram[0x0], &a->ram[0x0], 3); // R0 memcpy(&b->ram[0x5e9], &a->ram[0x5e9], 4); // mult_tmp memcpy(&b->ram[0x19b3], &a->ram[0x19b3], 2); // mode7_spawn_param memcpy(&a->ram[0x611], &b->ram[0x611], 6); // coroutine_state (copy from mine to theirs) memcpy(&a->ram[0x77e], &b->ram[0x77e], 5); // my counter 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 && */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++) RomPtr(addr)[i] = value[i]; } // Patches add/sub to ignore carry void FixupCarry(uint32 addr) { *RomPtr(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) { char buf[256]; snprintf(buf, sizeof(buf), "unable to load rom: %s", filename); Die(buf); } g_sram = g_snes->cart->ram; 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[4] = { 0x0a, 0x0a, 0x0a, 0x0a }; PatchBytes(0x82E915, t, 4); } // SpawnDoorClosingPLM reads from 0x12 { 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)); } RtlUpdateSnesPatchForBugfix(); for (size_t i = 0; i != arraysize(kPatchedCarrys); i++) { uint8 t = *RomPtr(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) { // 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); } 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(); } }