#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); } 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(); } }