#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 "enemy_types.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,
  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,

  0xA8A459,
  0xA8A45F,
  0xA8A465,
  0xA8A46B,

  0xA8A477,
  0xA8A47D,
  0xA8A483,
  0xA8A489,


  0xA8a543,
  0xA8a54f,
  0xA8a55b,
  0xA8a567,
  0xA8a573,
  0xA8a57f,
  0xA8a58b,

  0x84D7CB, // PlmInstr room_width
  0x84D7E2,
  0x84D7F4,
  0x84D803,

  0x8888CD,

  0x8888F0,
  0x8888E3,

  0x80A5F3,
  0x80A845,
  0x80A925,
  0x80A6AA,

  0x948D94,
  0x948E25,

  0x9082A8,
  0x9082AE,

  0xA48CA1,
  0xA48CA4,
};

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;
  } else if (FixBugHook(0xb39ddb)) {
    // Botwoon_Func_26 uses regs that are overwritten
    // This made the flicker slightly worse, so added hysteresis
    Enemy_Botwoon *E = Get_Botwoon(cur_enemy_index);
    REMOVED_R18 = E->base.x_pos - E->botwoon_var_56;
    REMOVED_R20 = E->base.y_pos - E->botwoon_var_57;
  } else if (FixBugHook(0xB39E13)) {
    // add botwoon hysteresis
    Enemy_Botwoon *E = Get_Botwoon(cur_enemy_index);
    REMOVED_R22 = E->botwoon_var_45 = (uint8)(E->botwoon_var_45 + (int8)(REMOVED_R22 - E->botwoon_var_45) * 3 / 4);
  }

  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], 0x51);  // r18, r20, R22 etc
  memcpy(&b->ram[0x1f5b], &a->ram[0x1f5b], 0x100 - 0x5b);  // stacck
  memcpy(&b->ram[0xad], &a->ram[0xad], 4);  // ptr_to_retaddr_parameters etc 
  memcpy(&b->ram[0x5e7], &a->ram[0x5e7], 14);  // bitmask, mult_tmp, mult_product_lo etc

  memcpy(&b->ram[0x5BC], &a->ram[0x5BC], 9);  // door_transition_vram_update etc
  memcpy(&a->ram[0x60B], &b->ram[0x60B], 6);  // eproj_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(&a->ram[0x78F], &b->ram[0x78F], 2);  // door_bts
  
  memcpy(&a->ram[0x7b7], &b->ram[0x7b7], 2);  // event_pointer
  memcpy(&a->ram[0x933], &b->ram[0x933], 10);  // var933 etc
  memcpy(&b->ram[0xA82], &a->ram[0xA82], 2);  // xray_angle
  memcpy(&b->ram[0xB24], &a->ram[0xB24], 4);  // 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[0x1966], &a->ram[0x1966], 6);  // current_fx_entry_offset etc
  memcpy(&b->ram[0x1993], &a->ram[0x1993], 2);  // eproj_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;
}

uint16 currently_installed_bug_fix_counter;

void RtlUpdateSnesPatchForBugfix() {
  currently_installed_bug_fix_counter = bug_fix_counter;
  // 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();
  }
}
