// System routines 

#include "sm_rtl.h"
#include "ida_types.h"
#include "variables.h"
#include "funcs.h"
#include "enemy_types.h"
#include "spc_player.h"


#define kMusicPointers (*(LongPtr*)RomFixedPtr(0x8fe7e1))
#define kTimerDigitsSpritemapPtr ((uint16*)RomFixedPtr(0x809fd4))
#define kLoadStationLists ((uint16*)RomFixedPtr(0x80c4b5))
#define off_80CD46 ((uint16*)RomFixedPtr(0x80cd46))

void APU_UploadBank(uint32 addr) {  // 0x808028
  if (!g_use_my_apu_code)
    return;
  RtlApuUpload(RomPtr(addr));
}

uint16 NextRandom(void) {  // 0x808111
  uint16 RegWord = LOBYTE(random_number) * 5;
  uint8 Reg = HIBYTE(random_number) * 5;

  int carry = HIBYTE(RegWord) + Reg + 1;
  HIBYTE(RegWord) = carry;
  uint16 result = (carry >> 8) + RegWord + 17;
  random_number = result;
  return result;
}

void ReleaseButtonsFilter(uint16 v0) {  // 0x808146
  timed_held_input_timer_reset = v0;
  bool v1 = ((uint16)~joypad1_newkeys & joypad1_lastkeys) == joypad_released_keys;
  joypad_released_keys = ~joypad1_newkeys & joypad1_lastkeys;
  if (!v1) {
    timed_held_input_timer = timed_held_input_timer_reset;
    timed_held_input = 0;
    goto LABEL_6;
  }
  if ((--timed_held_input_timer & 0x8000) == 0) {
    timed_held_input = 0;
    goto LABEL_6;
  }
  timed_held_input_timer = 0;
  previous_timed_held_input = timed_held_input;
  timed_held_input = ~joypad1_newkeys & joypad1_lastkeys;
LABEL_6:
  newly_held_down_timed_held_input = timed_held_input & (previous_timed_held_input ^ timed_held_input);
}

uint16 PrepareBitAccess(uint16 a) {  // 0x80818E
  bitmask = 1 << (a & 7);
  return a >> 3;
}

void SetBossBitForCurArea(uint16 a) {  // 0x8081A6
  boss_bits_for_area[area_index] |= a;
}

void ClearBossBitForCurArea(uint16 a) {  // 0x8081C0
  boss_bits_for_area[area_index] &= ~a;
}

uint8 CheckBossBitForCurArea(uint16 a) {  // 0x8081DC
  return (a & boss_bits_for_area[area_index]) != 0;
}

void SetEventHappened(uint16 a) {  // 0x8081FA
  uint16 v1 = PrepareBitAccess(a);
  events_that_happened[v1] |= bitmask;
}

void ClearEventHappened(uint16 v0) {  // 0x808212
  uint16 v1 = PrepareBitAccess(v0);
  events_that_happened[v1] &= ~bitmask;
}

uint16 CheckEventHappened(uint16 a) {  // 0x808233
  uint16 idx = PrepareBitAccess(a);
  return (bitmask & events_that_happened[idx]) != 0;
}

void CopySuperMetroidString(void) {  // 0x80824F
  memcpy(&g_sram[0x1fe0], "supermetroid", 12);
  RtlWriteSram();
}

void VerifySRAM(void) {  // 0x808261
  num_demo_sets = 3;
  if (LoadFromSram(0) && LoadFromSram(1) && LoadFromSram(2)) {
    memcpy(&g_sram[0x1fe0], "madadameyohn", 12);
    RtlWriteSram();
  } else {
    if (!memcmp(&g_sram[0x1fe0], "supermetroid", 12))
      num_demo_sets = 4;
  }
}

uint32 Multiply16x16(uint16 a, uint16 j) {  // 0x8082D6
  uint32 result = (uint32)a * (uint32)j;
  return result;
}

CoroutineRet WaitForNMI_Async(void) {  // 0x808338
  // Return 0 from this routine as soon as the coroutine has finished
  if (coroutine_completion_flags) {
    coroutine_completion_flags = 0;
    return 0;
  }
  waiting_for_nmi = 1;
  coroutine_completion_flags = 1;
  return 1;
}

CoroutineRet WaitForNMI_NoUpdate_Async(void) {
  // Return 0 from this routine as soon as the coroutine has finished
  if (coroutine_completion_flags) {
    coroutine_completion_flags = 0;
    return 0;
  }
  coroutine_completion_flags = 1;
  return 1;
}

void EnableNMI(void) {  // 0x80834B
  uint8 v0 = reg_NMITIMEN | 0x80;
  WriteReg(NMITIMEN, reg_NMITIMEN | 0x80);
  reg_NMITIMEN = v0;
}

void DisableNMI(void) {  // 0x80835D
  uint8 v0 = reg_NMITIMEN & 0x7F;
  WriteReg(NMITIMEN, reg_NMITIMEN & 0x7F);
  reg_NMITIMEN = v0;
}

void ScreenOff(void) {
  reg_INIDISP |= 0x80;
}

void ScreenOn(void) {
  reg_INIDISP &= ~0x80;
}

void memset7E(uint16 *k, uint16 a, uint16 j) {  // 0x8083F6
  do {
    *k++ = a;
  } while (j -= 2);
}

CoroutineRet Vector_RESET_Async(void) {  // 0x80841C
  COROUTINE_BEGIN(coroutine_state_0, 1)
  WriteReg(MEMSEL, 1);
  reg_MEMSEL = 1;
  // Removed code to wait 4 frames
  uint16 bak = bug_fix_counter;
  memset(g_ram, 0, 8192);
  bug_fix_counter = bak;
  COROUTINE_AWAIT(2, InitializeIoDisplayLogo_Async());
  COROUTINE_MANUAL_POS(3); // Soft reset position
  APU_UploadBank(0xCF8000);
  WriteReg(INIDISP, 0x8F);
  bak = bug_fix_counter;
  memset(g_ram, 0, 0x10000);
  bug_fix_counter = bak;
  WriteReg(NMITIMEN, 0);
  reg_NMITIMEN = 0;
  reg_INIDISP = 0x8f;
  InitializeCpuIoRegs();
  InitializePpuIoRegs();
  WriteLotsOf0x1c2f();
  sfx_readpos[0] = 0;
  sfx_readpos[1] = 0;
  sfx_readpos[2] = 0;
  sfx_writepos[0] = 0;
  sfx_writepos[1] = 0;
  sfx_writepos[2] = 0;
  sfx_state[0] = 0;
  sfx_state[1] = 0;
  sfx_state[2] = 0;
  sfx_cur[0] = 0;
  sfx_cur[1] = 0;
  sfx_cur[2] = 0;
  sfx_clear_delay[0] = 0;
  sfx_clear_delay[1] = 0;
  sfx_clear_delay[2] = 0;
  sfx1_queue[0] = 0;
  sfx2_queue[0] = 0;
  sfx3_queue[0] = 0;
  oam_next_ptr = 0;
  reg_OAMaddr_UNUSED = 0;
  ClearOamExt();
  ClearUnusedOam();
  nmi_copy_samus_halves = 0;
  nmi_copy_samus_top_half_src = 0;
  nmi_copy_samus_bottom_half_src = 0;
  EnableNMI();
  RtlApuWrite(APUI00, 0);
  RtlApuWrite(APUI02, 0);

  // Removed code to wait 4 frames
  random_number = 97;
  music_timer = 0;
  music_queue_delay[0] = 0;
  music_queue_delay[1] = 0;
  music_queue_delay[2] = 0;
  music_queue_delay[3] = 0;
  music_queue_delay[4] = 0;
  music_queue_delay[5] = 0;
  music_queue_delay[6] = 0;
  music_queue_delay[7] = 0;
  enable_debug = 0;
  VerifySRAM();
  debug_disable_sounds = 0;
  sound_handler_downtime = 0;
  COROUTINE_END(2);
}

void InvalidInterrupt_Crash(void) {  // 0x808573
  printf("InvalidInterrupt_Crash\n");
  Unreachable();
  for (;;);
}

void LoadMirrorOfExploredMapTiles(void) {  // 0x80858C
  uint16 v1 = swap16(area_index);
  uint16 v2 = 0;
  do {
    *(uint16 *)&map_tiles_explored[v2] = explored_map_tiles_saved[v1 >> 1];
    v1 += 2;
    v2 += 2;
  } while ((int16)(v2 - 256) < 0);
  has_area_map = map_station_byte_array[area_index];
}

void SaveExploredMapTilesToSaved(void) {  // 0x8085C6
  uint16 v1 = swap16(area_index);
  uint16 v2 = 0;
  do {
    explored_map_tiles_saved[v1 >> 1] = *(uint16 *)&map_tiles_explored[v2];
    v1 += 2;
    v2 += 2;
  } while ((int16)(v2 - 256) < 0);
  if (has_area_map)
    *(uint16 *)&map_station_byte_array[area_index] |= 0xFF;
}

void InitializeCpuIoRegs(void) {  // 0x80875D
  WriteReg(NMITIMEN, 1);
  reg_NMITIMEN = 1;
  WriteReg(WRIO, 0);
  WriteReg(WRMPYA, 0);
  WriteReg(WRMPYB, 0);
  WriteReg(WRDIVL, 0);
  WriteReg(WRDIVH, 0);
  WriteReg(WRDIVB, 0);
  WriteReg(HTIMEL, 0);
  WriteReg(HTIMEH, 0);
  WriteReg(VTIMEL, 0);
  WriteReg(VTIMEH, 0);
  WriteReg(MDMAEN, 0);
  WriteReg(HDMAEN, 0);
  reg_HDMAEN = 0;
  WriteReg(MEMSEL, 1);
  reg_MEMSEL = 1;
}

void InitializePpuIoRegs(void) {  // 0x808792
  WriteReg(INIDISP, 0x8F);
  reg_INIDISP = 0x8f;
  WriteReg(OBSEL, 3);
  reg_OBSEL = 3;
  WriteReg(OAMADDL, 0);
  LOBYTE(reg_OAMaddr_UNUSED) = 0;
  WriteReg(OAMADDH, 0x80);
  HIBYTE(reg_OAMaddr_UNUSED) = 0x80;
  WriteReg(OAMDATA, 0);
  WriteReg(OAMDATA, 0);
  WriteReg(BGMODE, 9);
  reg_BGMODE = 9;
  WriteReg(MOSAIC, 0);
  reg_MOSAIC = 0;
  WriteReg(BG1SC, 0x40);
  reg_BG1SC = 64;
  WriteReg(BG2SC, 0x44);
  reg_BG2SC = 68;
  WriteReg(BG3SC, 0x48);
  reg_BG3SC = 72;
  WriteReg(BG4SC, 0);
  reg_BG4SC = 0;
  WriteReg(BG12NBA, 0);
  reg_BG12NBA = 0;
  WriteReg(BG34NBA, 5);
  reg_BG34NBA = 5;
  WriteReg(BG1HOFS, 0);
  WriteReg(BG1HOFS, 0);
  WriteReg(BG1VOFS, 0);
  WriteReg(BG1VOFS, 0);
  WriteReg(BG2HOFS, 0);
  WriteReg(BG2HOFS, 0);
  WriteReg(BG2VOFS, 0);
  WriteReg(BG2VOFS, 0);
  WriteReg(BG3HOFS, 0);
  WriteReg(BG3HOFS, 0);
  WriteReg(BG3VOFS, 0);
  WriteReg(BG3VOFS, 0);
  WriteReg(BG4HOFS, 0);
  WriteReg(BG4HOFS, 0);
  WriteReg(BG4VOFS, 0);
  WriteReg(BG4VOFS, 0);
  WriteReg(VMAIN, 0);
  WriteReg(M7SEL, 0);
  reg_M7SEL = 0;
  WriteReg(M7A, 0);
  WriteReg(M7B, 0);
  WriteReg(M7C, 0);
  WriteReg(M7D, 0);
  WriteReg(M7X, 0);
  WriteReg(M7Y, 0);
  WriteReg(W12SEL, 0);
  reg_W12SEL = 0;
  WriteReg(W34SEL, 0);
  reg_W34SEL = 0;
  WriteReg(WOBJSEL, 0);
  reg_WOBJSEL = 0;
  WriteReg(WH0, 0);
  reg_WH0 = 0;
  WriteReg(WH1, 0xF8);
  reg_WH1 = -8;
  WriteReg(WH2, 0);
  reg_WH2 = 0;
  WriteReg(WH3, 0);
  reg_WH3 = 0;
  WriteReg(WBGLOG, 0);
  reg_WBGLOG = 0;
  WriteReg(WOBJLOG, 0);
  reg_WOBJLOG = 0;
  WriteReg(TM, 0x11);
  reg_TM = 17;
  WriteReg(TMW, 0x11);
  reg_TMW = 17;
  WriteReg(TS, 2);
  reg_TS = 2;
  WriteReg(TSW, 2);
  reg_TSW = 2;
  WriteReg(CGWSEL, 2);
  next_gameplay_CGWSEL = 2;
  WriteReg(CGADSUB, 0xA1);
  next_gameplay_CGADSUB = -95;
  WriteReg(COLDATA, 0xE0);
  WriteReg(COLDATA, 0xE0);
  WriteReg(COLDATA, 0x80);
  reg_COLDATA[0] = 0x80;
  WriteReg(COLDATA, 0x40);
  reg_COLDATA[1] = 64;
  WriteReg(COLDATA, 0x20);
  reg_COLDATA[2] = 32;
  WriteReg(SETINI, 0);
  reg_SETINI = 0;
}

void WriteLotsOf0x1c2f(void) {  // 0x8088D1
  sub_8088EB(0x1C2F);
  sub_8088FE(0x1C2F);
  sub_808911(0x1C2F);
}

void sub_8088EB(uint16 a) {  // 0x8088EB
  memset7E((uint16*)(g_ram + 0x3000), a, 0x800);
}

void sub_8088FE(uint16 a) {  // 0x8088FE
  memset7E((uint16 *)(g_ram + 0x4000), a, 0x800);
}

void sub_808911(uint16 a) {  // 0x808911
  memset7E((uint16 *)(g_ram + 0x6000), a, 0x800);
}

void HandleFadeOut(void) {  // 0x808924
  if ((int16)(screen_fade_counter - 1) < 0) {
    screen_fade_counter = screen_fade_delay;
    if ((reg_INIDISP & 0xF) != 0) {
      if ((reg_INIDISP & 0xF) == 1)
        reg_INIDISP = 0x80;
      else
        reg_INIDISP = (reg_INIDISP & 0xF) - 1;
    }
  } else {
    --screen_fade_counter;
  }
}

void HandleFadeIn(void) {  // 0x80894D
  if ((int16)(screen_fade_counter - 1) < 0) {
    screen_fade_counter = screen_fade_delay;
    if (((reg_INIDISP + 1) & 0xF) != 0)
      reg_INIDISP = (reg_INIDISP + 1) & 0xF;
  } else {
    --screen_fade_counter;
  }
}

void ClearUnusedOam(void) {
  for (int i = oam_next_ptr >> 2; i < 0x80; i++)
    oam_ent[i].ycoord = 0xf0;
  oam_next_ptr = 0;
}

void ClearOamExt(void) {  // 0x808B1A
  memset(oam_ext, 0, sizeof(oam_ext[0]) * 16);
}

void QueueMode7Transfers(uint8 db, uint16 k) {  // 0x808B4F
  const uint8 *p = RomPtrWithBank(db, k);
  uint8 *dst = (uint8 *)mode7_write_queue + mode7_vram_write_queue_tail;
  for (;;) {
    int f = GET_BYTE(p);
    if (f & 0x80) {
      dst[9] = 0;
      memcpy(dst, p, 9);
      p += 9, dst += 9;
    } else if (f & 0x40) {
      dst[7] = 0;
      memcpy(dst, p, 7);
      p += 7, dst += 7;
    } else {
      break;
    }
  }
  mode7_vram_write_queue_tail = dst - (uint8 *)mode7_write_queue;
}

void NMI_ProcessMode7Queue(void) {  // 0x808BBA
  if (mode7_vram_write_queue_tail) {
    NMI_ProcessMode7QueueInner(&g_ram[0x2D0]);
    *(uint16 *)&mode7_write_queue[0].field_0 = 0;
    mode7_vram_write_queue_tail = 0;
  }
}

void NMI_ProcessMode7QueueInner(const uint8 *p) {  // 0x808BD3
  while (1) {
    uint8 v2;
    while (1) {
      v2 = *p;
      if ((v2 & 0x80) == 0)
        break;
      WriteReg(DMAP1, v2 & 0x1F);
      Mode7CgvmWriteQueue *v5 = (Mode7CgvmWriteQueue *)p;
      WriteRegWord(A1T1L, v5->src_addr.addr);
      WriteReg(A1B1, v5->src_addr.bank);
      WriteRegWord(DAS1L, v5->count);
      if (v2 & 0x40)
        WriteReg(BBAD1, 0x19);
      else
        WriteReg(BBAD1, 0x18);
      WriteRegWord(VMADDL, v5->vram_addr);
      WriteReg(VMAIN, v5->vmain);
      WriteReg(MDMAEN, 2);
      p += sizeof(Mode7CgvmWriteQueue);
    }
    if (!(v2 & 0x40))
      break;
    WriteReg(DMAP1, v2 & 0x1F);
    WriteRegWord(A1T1L, *(uint16 *)(p + 1));
    WriteReg(A1B1, p[3]);
    WriteRegWord(DAS1L, *((uint16 *)p + 2));
    WriteReg(BBAD1, 0x22);
    WriteReg(CGADD, p[6]);
    WriteReg(MDMAEN, 2);
    p += 7;
  }
}

void NMI_ProcessVramWriteQueue(void) {  // 0x808C83
  if (vram_write_queue_tail) {
    gVramWriteEntry(vram_write_queue_tail)->size = 0;
    WriteRegWord(DMAP1, 0x1801);
    for (int i = 0; ; i += 7) {
      VramWriteEntry *e = gVramWriteEntry(i);
      if (!e->size)
        break;
      WriteRegWord(DAS1L, e->size);
      WriteRegWord(A1T1L, e->src.addr);
      WriteReg(A1B1, e->src.bank);
      WriteRegWord(VMAIN, sign16(e->vram_dst) ? 0x81 : 0x80);
      WriteRegWord(VMADDL, e->vram_dst);
      WriteReg(MDMAEN, 2);
    }
  }
  vram_write_queue_tail = 0;
  Nmi_ProcessHorizScrollingDma();
  Nmi_ProcessVertScrollingDma();
}

void Nmi_ProcessHorizScrollingDma(void) {  // 0x808CD8
  WriteReg(VMAIN, 0x81);
  if ((uint8)bg1_update_col_enable) {
    LOBYTE(bg1_update_col_enable) = 0;
    uint16 v0 = bg1_update_col_unwrapped_dst;
    WriteRegWord(VMADDL, bg1_update_col_unwrapped_dst);
    WriteRegWord(DMAP1, 0x1801);
    WriteRegWord(A1T1L, ADDR16_OF_RAM(*bg1_column_update_tilemap_left_halves));
    WriteReg(A1B1, 0x7E);
    uint16 v1 = bg1_update_col_unwrapped_size;
    WriteRegWord(DAS1L, bg1_update_col_unwrapped_size);
    WriteReg(MDMAEN, 2);
    WriteRegWord(VMADDL, v0 + 1);
    WriteRegWord(DAS1L, v1);
    WriteRegWord(A1T1L, ADDR16_OF_RAM(*bg1_column_update_tilemap_right_halves));
    WriteReg(MDMAEN, 2);
    WriteRegWord(A1T1L, bg1_update_col_wrapped_left_src);
    uint16 v2 = bg1_update_col_wrapped_size;
    if (bg1_update_col_wrapped_size) {
      WriteRegWord(DAS1L, bg1_update_col_wrapped_size);
      uint16 v3 = bg1_update_col_wrapped_dst;
      WriteRegWord(VMADDL, bg1_update_col_wrapped_dst);
      WriteReg(MDMAEN, 2);
      WriteRegWord(VMADDL, v3 + 1);
      WriteRegWord(DAS1L, v2);
      WriteRegWord(A1T1L, bg1_update_col_wrapped_right_src);
      WriteReg(MDMAEN, 2);
    }
  }
  if ((uint8)bg2_update_col_enable) {
    LOBYTE(bg2_update_col_enable) = 0;
    uint16 v4 = bg2_update_col_unwrapped_dst;
    WriteRegWord(VMADDL, bg2_update_col_unwrapped_dst);
    WriteRegWord(DMAP1, 0x1801);
    WriteRegWord(A1T1L, ADDR16_OF_RAM(*bg2_column_update_tilemap_left_halves));
    WriteReg(A1B1, 0x7E);
    uint16 v5 = bg2_update_col_unwrapped_size;
    WriteRegWord(DAS1L, bg2_update_col_unwrapped_size);
    WriteReg(MDMAEN, 2);
    WriteRegWord(VMADDL, v4 + 1);
    WriteRegWord(DAS1L, v5);
    WriteRegWord(A1T1L, ADDR16_OF_RAM(*bg2_column_update_tilemap_right_halves));
    WriteReg(MDMAEN, 2);
    WriteRegWord(A1T1L, bg2_update_col_wrapped_left_src);
    uint16 v6 = bg2_update_col_wrapped_size;
    if (bg2_update_col_wrapped_size) {
      WriteRegWord(DAS1L, bg2_update_col_wrapped_size);
      uint16 v7 = bg2_update_col_wrapped_dst;
      WriteRegWord(VMADDL, bg2_update_col_wrapped_dst);
      WriteReg(MDMAEN, 2);
      WriteRegWord(VMADDL, v7 + 1);
      WriteRegWord(DAS1L, v6);
      WriteRegWord(A1T1L, bg2_update_col_wrapped_right_src);
      WriteReg(MDMAEN, 2);
    }
  }
}

void Nmi_ProcessVertScrollingDma(void) {  // 0x808DAC
  WriteReg(VMAIN, 0x80);
  if ((uint8)bg1_update_row_enable) {
    LOBYTE(bg1_update_row_enable) = 0;
    uint16 v0 = bg1_update_row_unwrapped_dst;
    WriteRegWord(VMADDL, bg1_update_row_unwrapped_dst);
    WriteRegWord(DMAP1, 0x1801);
    WriteRegWord(A1T1L, ADDR16_OF_RAM(*bg1_column_update_tilemap_top_halves));
    WriteReg(A1B1, 0x7E);
    uint16 v1 = bg1_update_row_unwrapped_size;
    WriteRegWord(DAS1L, bg1_update_row_unwrapped_size);
    WriteReg(MDMAEN, 2);
    WriteRegWord(VMADDL, v0 | 0x20);
    WriteRegWord(DAS1L, v1);
    WriteRegWord(A1T1L, ADDR16_OF_RAM(*bg1_column_update_tilemap_bottom_halves));
    WriteReg(MDMAEN, 2);
    WriteRegWord(A1T1L, bg1_update_row_wrapped_top_src);
    uint16 v2 = bg1_update_row_wrapped_size;
    if (bg1_update_row_wrapped_size) {
      WriteRegWord(DAS1L, bg1_update_row_wrapped_size);
      uint16 v3 = bg1_update_row_wrapped_dst;
      WriteRegWord(VMADDL, bg1_update_row_wrapped_dst);
      WriteReg(MDMAEN, 2);
      WriteRegWord(VMADDL, v3 | 0x20);
      WriteRegWord(DAS1L, v2);
      WriteRegWord(A1T1L, bg1_update_row_wrapped_bottom_src);
      WriteReg(MDMAEN, 2);
    }
  }
  if ((uint8)bg2_update_row_enable) {
    LOBYTE(bg2_update_row_enable) = 0;
    uint16 v4 = bg2_update_row_unwrapped_dst;
    WriteRegWord(VMADDL, bg2_update_row_unwrapped_dst);
    WriteRegWord(DMAP1, 0x1801);
    WriteRegWord(A1T1L, ADDR16_OF_RAM(*bg2_column_update_tilemap_top_halves));
    WriteReg(A1B1, 0x7E);
    uint16 v5 = bg2_update_row_unwrapped_size;
    WriteRegWord(DAS1L, bg2_update_row_unwrapped_size);
    WriteReg(MDMAEN, 2);
    WriteRegWord(VMADDL, v4 | 0x20);
    WriteRegWord(DAS1L, v5);
    WriteRegWord(A1T1L, ADDR16_OF_RAM(*bg2_column_update_tilemap_bottom_halves));
    WriteReg(MDMAEN, 2);
    WriteRegWord(A1T1L, bg2_update_row_wrapped_top_src);
    uint16 v6 = bg2_update_row_wrapped_size;
    if (bg2_update_row_wrapped_size) {
      WriteRegWord(DAS1L, bg2_update_row_wrapped_size);
      uint16 v7 = bg2_update_row_wrapped_dst;
      WriteRegWord(VMADDL, bg2_update_row_wrapped_dst);
      WriteReg(MDMAEN, 2);
      WriteRegWord(VMADDL, v7 | 0x20);
      WriteRegWord(DAS1L, v6);
      WriteRegWord(A1T1L, bg2_update_row_wrapped_bottom_src);
      WriteReg(MDMAEN, 2);
    }
  }
}

void NMI_ProcessVramReadQueue(void) {  // 0x808EA2
  if ((uint8)vram_read_queue_tail) {
    *((uint8 *)&vram_read_queue[0].vram_target + (uint8)vram_read_queue_tail) = 0;
    int v0 = 0;
    WriteReg(VMAIN, 0x80);
    while (vram_read_queue[v0].vram_target) {
      WriteRegWord(VMADDL, vram_read_queue[v0].vram_target);
      ReadRegWord(RDVRAML);
      WriteRegWord(DMAP1, vram_read_queue[v0].dma_parameters);
      WriteRegWord(A1T1L, vram_read_queue[v0].src.addr);
      WriteRegWord(A1T1H, *(VoidP *)((uint8 *)&vram_read_queue[v0].src.addr + 1));
      WriteRegWord(DAS1L, vram_read_queue[v0].size);
      WriteRegWord(DAS10, 0);
      WriteRegWord(A2A1H, 0);
      WriteReg(MDMAEN, 2);
      ++v0;
    }
    LOBYTE(vram_read_queue_tail) = 0;
  }
}

uint8 HasQueuedMusic(void) {  // 0x808EF4
  int v0 = 14;
  while (!music_queue_delay[v0 >> 1]) {
    v0 -= 2;
    if (v0 < 0)
      return 0;
  }
  return 1;
}

void HandleMusicQueue(void) {  // 0x808F0C
  bool v0 = music_timer-- == 1;
  if ((music_timer & 0x8000) == 0) {
    if (!v0)
      return;
    if ((music_entry & 0x8000) != 0) {
      music_data_index = (uint8)music_entry;
      cur_music_track = -1;
      APU_UploadBank(Load24((LongPtr *)((uint8 *)&kMusicPointers + (uint8)music_entry)));
      cur_music_track = 0;
      int v5 = music_queue_read_pos;
      music_queue_track[v5 >> 1] = 0;
      music_queue_delay[v5 >> 1] = 0;
      music_queue_read_pos = (v5 + 2) & 0xE;
      sound_handler_downtime = 8;
      return;
    }
    uint8 v1 = music_entry & 0x7F;
    music_track_index = music_entry & 0x7F;
    RtlApuWrite(APUI00, music_entry & 0x7F);
    cur_music_track = v1;
    sound_handler_downtime = 8;
    int v2 = music_queue_read_pos;
    music_queue_track[v2 >> 1] = 0;
    music_queue_delay[v2 >> 1] = 0;
    music_queue_read_pos = (v2 + 2) & 0xE;
  }
  if (music_queue_read_pos == music_queue_write_pos) {
    music_timer = 0;
  } else {
    int v4 = music_queue_read_pos >> 1;
    music_entry = music_queue_track[v4];
    music_timer = music_queue_delay[v4];
  }
}

void QueueMusic_Delayed8(uint16 a) {  // 0x808FC1
  int8 v1;

  if (game_state < kGameState_40_TransitionToDemo && ((music_queue_write_pos + 2) & 0xE) != music_queue_read_pos) {
    v1 = music_queue_write_pos;
    int v2 = music_queue_write_pos >> 1;
    music_queue_track[v2] = a;
    music_queue_delay[v2] = 8;
    music_queue_write_pos = (v1 + 2) & 0xE;
  }
}

void QueueMusic_DelayedY(uint16 a, uint16 j) {  // 0x808FF7
  if (game_state < 0x28) {
    int v2 = music_queue_write_pos;
    music_queue_track[v2 >> 1] = a;
    int v4 = j;
    if (j < 8)
      v4 = 8;
    music_queue_delay[v2 >> 1] = v4;
    music_queue_write_pos = (v2 + 2) & 0xE;
  }
}

void QueueSfx1_Max15(uint16 a) {  // 0x809021
  QueueSfx1_Internal(a << 8 | 15);
}

void QueueSfx1_Max9(uint16 a) {  // 0x80902B
  QueueSfx1_Internal(a << 8 | 9);
}

void QueueSfx1_Max3(uint16 a) {  // 0x809035
  QueueSfx1_Internal(a << 8 | 3);
}

void QueueSfx1_Max1(uint16 a) {  // 0x80903F
  QueueSfx1_Internal(a << 8 | 1);
}

void QueueSfx1_Max6(uint16 a) {  // 0x809049
  QueueSfx1_Internal(a << 8 | 6);
}

void QueueSfx1_Internal(uint16 a) {  // 0x809051
  sfx_max_queued[0] = a;
  if (((sfx_writepos[0] - sfx_readpos[0]) & 0xF) < (uint8)a) {
    uint8 v2 = GET_HIBYTE(a);
    if (!debug_disable_sounds && game_state < 0x28 && (power_bomb_explosion_status & 0x8000) == 0) {
      uint8 v1 = sfx_writepos[0];
      uint8 v3 = sfx_writepos[0] + 1;
      if (v3 >= 0x10)
        v3 = 0;
      if (v3 == sfx_readpos[0]) {
        if (v2 < sfx1_queue[v1])
          sfx1_queue[v1] = v2;
      } else {
        sfx1_queue[sfx_writepos[0]] = v2;
        sfx_writepos[0] = v3;
        sfx1_queue[v3] = 0;
      }
    }
  }
}

void QueueSfx2_Max15(uint16 a) {  // 0x8090A3
  QueueSfx2_Internal(a << 8 | 15);
}

void QueueSfx2_Max9(uint16 a) {  // 0x8090AD
  QueueSfx2_Internal(a << 8 | 9);
}

void QueueSfx2_Max3(uint16 a) {  // 0x8090B7
  QueueSfx2_Internal(a << 8 | 3);
}

void QueueSfx2_Max1(uint16 a) {  // 0x8090C1
  QueueSfx2_Internal(a << 8 | 1);
}

void QueueSfx2_Max6(uint16 a) {  // 0x8090CB
  QueueSfx2_Internal(a << 8 | 6);
}

void QueueSfx2_Internal(uint16 a) {  // 0x8090D3
  sfx_max_queued[1] = a;
  if (((sfx_writepos[1] - sfx_readpos[1]) & 0xF) < (uint8)a) {
    uint8 v2 = GET_HIBYTE(a);
    if (!debug_disable_sounds && game_state < 0x28 && (power_bomb_explosion_status & 0x8000) == 0) {
      uint8 v1 = sfx_writepos[1];
      uint8 v3 = sfx_writepos[1] + 1;
      if (v3 >= 0x10)
        v3 = 0;
      if (v3 == sfx_readpos[1]) {
        if (v2 < sfx2_queue[v1])
          sfx2_queue[v1] = v2;
      } else {
        sfx2_queue[sfx_writepos[1]] = v2;
        sfx_writepos[1] = v3;
        sfx2_queue[v3] = 0;
      }
    }
  }
}

void QueueSfx3_Max15(uint16 a) {  // 0x809125
  QueueSfx3_Internal(a << 8 | 15);
}

void QueueSfx3_Max9(uint16 a) {  // 0x80912F
  QueueSfx3_Internal(a << 8 | 9);
}

void QueueSfx3_Max3(uint16 a) {  // 0x809139
  QueueSfx3_Internal(a << 8 | 3);
}

void QueueSfx3_Max1(uint16 a) {  // 0x809143
  QueueSfx3_Internal(a << 8 | 1);
}

void QueueSfx3_Max6(uint16 a) {  // 0x80914D
  QueueSfx3_Internal(a << 8 | 6);
}

void QueueSfx3_Internal(uint16 a) {  // 0x809155
  sfx_max_queued[2] = a;
  if (((sfx_writepos[2] - sfx_readpos[2]) & 0xF) < (uint8)a) {
    uint8 v2 = GET_HIBYTE(a);
    if (!debug_disable_sounds && game_state < 0x28 && (power_bomb_explosion_status & 0x8000) == 0) {
      uint8 v1 = sfx_writepos[2];
      uint8 v3 = sfx_writepos[2] + 1;
      if (v3 >= 0x10)
        v3 = 0;
      if (v3 == sfx_readpos[2]) {
        if (v2 < sfx3_queue[v1])
          sfx3_queue[v1] = v2;
      } else {
        sfx3_queue[sfx_writepos[2]] = v2;
        sfx_writepos[2] = v3;
        sfx3_queue[v3] = 0;
      }
    }
  }
}

void SetupDmaTransfer(const void *p) {  // 0x8091A9
  const StartDmaCopy *s = (const StartDmaCopy *)p;

  int v2 = s->chan * 16;
  WriteRegWord((SnesRegs)(v2 + DMAP0), *(uint16 *)&s->dmap);
  WriteRegWord((SnesRegs)(v2 + A1T0L), *(uint16 *)&s->a1.addr);
  WriteReg((SnesRegs)(v2 + A1B0), s->a1.bank);
  WriteRegWord((SnesRegs)(v2 + DAS0L), s->das);
}

void NmiUpdateIoRegisters(void) {  // 0x8091EE
  WriteReg(NMITIMEN, reg_NMITIMEN);
  WriteReg(INIDISP, reg_INIDISP);
  WriteReg(OBSEL, reg_OBSEL);
  WriteReg(BGMODE, reg_BGMODE);
  WriteReg(MOSAIC, reg_MOSAIC);
  WriteReg(BG1SC, reg_BG1SC);
  WriteReg(BG2SC, reg_BG2SC);
  WriteReg(BG3SC, reg_BG3SC);
  WriteReg(BG4SC, reg_BG4SC);
  WriteReg(BG12NBA, reg_BG12NBA);
  WriteReg(BG34NBA, reg_BG34NBA);
  WriteReg(M7SEL, reg_M7SEL);
  WriteReg(W12SEL, reg_W12SEL);
  WriteReg(W34SEL, reg_W34SEL);
  WriteReg(WOBJSEL, reg_WOBJSEL);
  WriteReg(WH0, reg_WH0);
  WriteReg(WH1, reg_WH1);
  WriteReg(WH2, reg_WH2);
  WriteReg(WH3, reg_WH3);
  WriteReg(WBGLOG, reg_WBGLOG);
  WriteReg(WOBJLOG, reg_WOBJLOG);
  gameplay_TM = reg_TM;
  WriteReg(TM, reg_TM);
  WriteReg(TMW, reg_TMW);
  WriteReg(TS, reg_TS);
  WriteReg(TSW, reg_TSW);
  WriteReg(CGWSEL, reg_CGWSEL);
  WriteReg(CGADSUB, reg_CGADSUB);
  gameplay_CGWSEL = next_gameplay_CGWSEL;
  gameplay_CGADSUB = next_gameplay_CGADSUB;
  WriteReg(COLDATA, reg_COLDATA[0]);
  WriteReg(COLDATA, reg_COLDATA[1]);
  WriteReg(COLDATA, reg_COLDATA[2]);
  WriteReg(SETINI, reg_SETINI);
  WriteReg(BG1HOFS, reg_BG1HOFS);
  WriteReg(BG1HOFS, HIBYTE(reg_BG1HOFS));
  WriteReg(BG1VOFS, reg_BG1VOFS);
  WriteReg(BG1VOFS, HIBYTE(reg_BG1VOFS));
  WriteReg(BG2HOFS, reg_BG2HOFS);
  WriteReg(BG2HOFS, HIBYTE(reg_BG2HOFS));
  WriteReg(BG2VOFS, reg_BG2VOFS);
  WriteReg(BG2VOFS, HIBYTE(reg_BG2VOFS));
  WriteReg(BG3HOFS, reg_BG3HOFS);
  WriteReg(BG3HOFS, HIBYTE(reg_BG3HOFS));
  WriteReg(BG3VOFS, reg_BG3VOFS);
  WriteReg(BG3VOFS, HIBYTE(reg_BG3VOFS));
  WriteReg(BG4HOFS, reg_BG4HOFS);
  WriteReg(BG4HOFS, HIBYTE(reg_BG4HOFS));
  WriteReg(BG4VOFS, reg_BG4VOFS);
  WriteReg(BG4VOFS, HIBYTE(reg_BG4VOFS));
  HIBYTE(hdma_data_table_in_ceres) = reg_BGMODE_fake;
  if ((reg_BGMODE & 7) == 7 || (reg_BGMODE_fake & 7) == 7) {
    WriteReg(M7A, reg_M7A);
    WriteReg(M7A, HIBYTE(reg_M7A));
    WriteReg(M7B, reg_M7B);
    WriteReg(M7B, HIBYTE(reg_M7B));
    WriteReg(M7C, reg_M7C);
    WriteReg(M7C, HIBYTE(reg_M7C));
    WriteReg(M7D, reg_M7D);
    WriteReg(M7D, HIBYTE(reg_M7D));
    WriteReg(M7X, reg_M7X);
    WriteReg(M7X, HIBYTE(reg_M7X));
    WriteReg(M7Y, reg_M7Y);
    WriteReg(M7Y, HIBYTE(reg_M7Y));
  }
}

void NmiUpdatePalettesAndOam(void) {  // 0x80933A
  WriteRegWord(DMAP0, 0x400);
  WriteRegWord(A1T0L, ADDR16_OF_RAM(*oam_ent));
  WriteReg(A1B0, 0);
  WriteRegWord(DAS0L, 0x220);
  WriteRegWord(OAMADDL, 0);
  WriteRegWord(DMAP1, 0x2200);
  WriteRegWord(A1T1L, ADDR16_OF_RAM(*palette_buffer));
  WriteReg(A1B1, 0x7E);
  WriteRegWord(DAS1L, 0x200);
  WriteReg(CGADD, 0);
  WriteReg(MDMAEN, 3);
}

void NmiTransferSamusToVram(void) {  // 0x809376
  WriteReg(VMAIN, 0x80);
  if ((uint8)nmi_copy_samus_halves) {
    SamusTileAnimationTileDefs *td = (SamusTileAnimationTileDefs *)RomPtr_92(nmi_copy_samus_top_half_src);
    WriteRegWord(VMADDL, 0x6000);
    WriteRegWord(DMAP1, 0x1801);
    WriteRegWord(A1T1L, td->src.addr);
    WriteReg(A1B1, td->src.bank);
    WriteRegWord(DAS1L, td->part1_size);
    WriteReg(MDMAEN, 2);
    WriteRegWord(VMADDL, 0x6100);
    WriteRegWord(A1T1L, td->src.addr + td->part1_size);
    if (td->part2_size) {
      WriteRegWord(DAS1L, td->part2_size);
      WriteReg(MDMAEN, 2);
    }
  }
  if (HIBYTE(nmi_copy_samus_halves)) {
    SamusTileAnimationTileDefs *td = (SamusTileAnimationTileDefs *)RomPtr_92(nmi_copy_samus_bottom_half_src);
    WriteRegWord(VMADDL, 0x6080);
    WriteRegWord(DMAP1, 0x1801);
    WriteRegWord(A1T1L, td->src.addr);
    WriteReg(A1B1, td->src.bank);
    WriteRegWord(DAS1L, td->part1_size);
    WriteReg(MDMAEN, 2);
    WriteRegWord(VMADDL, 0x6180);
    WriteRegWord(A1T1L, td->src.addr + td->part1_size);
    if (td->part2_size) {
      WriteRegWord(DAS1L, td->part2_size);
      WriteReg(MDMAEN, 2);
    }
  }
}

void NmiProcessAnimtilesVramTransfers(void) {  // 0x809416
  if ((animtiles_enable_flag & 0x8000) != 0) {
    for (int i = 10; (i & 0x80) == 0; i -= 2) {
      int v1 = i >> 1;
      if (animtiles_ids[v1]) {
        if (animtiles_src_ptr[v1]) {
          WriteRegWord(A1T0L, animtiles_src_ptr[v1]);
          WriteReg(A1B0, 0x87);
          WriteRegWord(DMAP0, 0x1801);
          WriteRegWord(DAS0L, animtiles_sizes[v1]);
          WriteRegWord(VMADDL, animtiles_vram_ptr[v1]);
          WriteReg(VMAIN, 0x80);
          WriteReg(MDMAEN, 1);
          animtiles_src_ptr[v1] = 0;
        }
      }
    }
  }
}

void ReadJoypadInputs(void) {  // 0x809459
  uint16 RegWord = ReadRegWord(JOY1L);
  joypad1_lastkeys = RegWord;
  joypad1_newkeys = RegWord & (joypad1_prev ^ RegWord);
  joypad1_newkeys2_UNUSED = RegWord & (joypad1_prev ^ RegWord);
  if (RegWord && RegWord == joypad1_prev) {
    if (!--joypad1_keyrepeat_ctr) {
      joypad1_newkeys2_UNUSED = joypad1_lastkeys;
      joypad1_keyrepeat_ctr = joypad_ctr_repeat_next;
    }
  } else {
    joypad1_keyrepeat_ctr = joypad_ctr_repeat_first;
  }
  joypad1_prev = joypad1_lastkeys;
  if (enable_debug) {
    uint16 v1 = ReadRegWord(JOY2L);
    joypad2_last = v1;
    joypad2_new_keys = v1 & (joypad2_prev ^ v1);
    joypad2_newkeys2 = v1 & (joypad2_prev ^ v1);
    if (v1 && v1 == joypad2_prev) {
      if (!--joypad2_keyrepeat_ctr) {
        joypad2_newkeys2 = joypad2_last;
        joypad2_keyrepeat_ctr = joypad_ctr_repeat_next;
      }
    } else {
      joypad2_keyrepeat_ctr = joypad_ctr_repeat_first;
    }
    joypad2_prev = joypad2_last;
    if (is_uploading_apu || joypad1_lastkeys != (kButton_Select | kButton_Start | kButton_L | kButton_R)) {
      if (enable_debug) {
        joypad_dbg_1 = 0;
        joypad_dbg_2 = 0;
        if ((joypad_dbg_flags & 0x4000) == 0) {
          if ((joypad1_lastkeys & (kButton_Select | kButton_L)) == (kButton_Select | kButton_L)) {
            joypad_dbg_1 = joypad1_newkeys;
            joypad1_lastkeys = 0;
            joypad1_newkeys = 0;
          }
          if ((joypad1_lastkeys & (kButton_Select | kButton_R)) == (kButton_Select | kButton_R)) {
            joypad_dbg_2 = joypad1_newkeys;
            joypad1_lastkeys = 0;
            joypad1_newkeys = 0;
          }
          if ((joypad_dbg_2 & 0x80) != 0)
            *(uint16 *)&reg_NMITIMEN ^= 0x30;
          if ((joypad_dbg_2 & 0x8000) != 0) {
            bool v2 = (~joypad_dbg_flags & 0x8000) != 0;
            joypad_dbg_flags ^= 0x8000;
            if (v2) {
              joypad_dbg_missiles_swap = samus_missiles;
              joypad_dbg_super_missiles_swap = samus_super_missiles;
              joypad_dbg_power_bombs_swap = samus_power_bombs;
              samus_missiles = 0;
              samus_super_missiles = 0;
              samus_power_bombs = 0;
            } else {
              samus_missiles = joypad_dbg_missiles_swap;
              samus_super_missiles = joypad_dbg_super_missiles_swap;
              samus_power_bombs = joypad_dbg_power_bombs_swap;
            }
          }
          if ((joypad_dbg_2 & 0x40) != 0)
            joypad_dbg_flags ^= 0x2000;
        }
      } else {
        joypad_dbg_1 = 0;
        joypad_dbg_2 = 0;
        joypad2_last &= 0x10;
        joypad2_new_keys &= 0x10;
      }
    } else {
      debug_disable_sounds = 0;
      SoftReset();
    }
  }
}

void Vector_NMI(void) {  // 0x809583
  if (waiting_for_nmi) {
    NmiUpdatePalettesAndOam();
    NmiTransferSamusToVram();
    NmiProcessAnimtilesVramTransfers();
    NmiUpdateIoRegisters();
    for (int i = 0; i != 6; ++i) {
      if (hdma_object_channels_bitmask[i])
        WriteRegWord((SnesRegs)(LOBYTE(hdma_object_bank_slot[i]) + 17154), hdma_object_table_pointers[i]);
    }
    if (reg_BGMODE == 7 || reg_BGMODE_fake == 7)
      NMI_ProcessMode7Queue();
    NMI_ProcessVramWriteQueue();
    NMI_ProcessVramReadQueue();
    WriteReg(HDMAEN, reg_HDMAEN);
    waiting_for_nmi = 0;
    nmi_frames_missed = 0;
    ++nmi_frame_counter_byte;
    ++nmi_frame_counter_word;
  } else if (++nmi_frames_missed >= nmi_frames_missed_max) {
    nmi_frames_missed_max = nmi_frames_missed;
  }
  ++nmi_frame_counter_including_lag;
}

void CopyToVramNow(uint16 vram_dst, uint32 src, uint16 size) {
  // src can point either to ram or rom
  WriteReg(INIDISP, 0x80);
  WriteRegWord(VMADDL, vram_dst);
  WriteRegWord(DMAP1, 0x1801);
  WriteRegWord(A1T1L, (uint16)src);
  WriteReg(A1B1, src >> 16);
  WriteRegWord(DAS1L, size);
  WriteReg(VMAIN, 0x80);
  WriteReg(MDMAEN, 2);
  WriteReg(INIDISP, 0xF);
}

void IrqHandler_SetResult(uint16 a, uint16 y, uint16 x) {
  //  printf("Setting irq next: %d, %d, %d\n", a, x, y);
  cur_irq_handler = a;
  WriteRegWord(VTIMEL, y);
  WriteRegWord(HTIMEL, x);
}

void IrqHandler_0_Nothing(void) {  // 0x80966E
  uint16 a = irqhandler_next_handler;
  irqhandler_next_handler = 0;
  IrqHandler_SetResult(a, 0, 0);
}

void IrqHandler_2_DisableIRQ(void) {  // 0x809680
  *(uint16 *)&reg_NMITIMEN &= ~0x30;
  IrqHandler_SetResult(0, 0, 0);
}

void IrqHandler_4_Main_BeginHudDraw(void) {  // 0x80968B
  WriteReg(BG3SC, 0x5A);
  WriteReg(CGWSEL, 0);
  WriteReg(CGADSUB, 0);
  WriteReg(TM, 4);
  IrqHandler_SetResult(6, 31, 152);
}

void IrqHandler_6_Main_EndHudDraw(void) {  // 0x8096A9
  WriteReg(CGWSEL, gameplay_CGWSEL);
  WriteReg(CGADSUB, gameplay_CGADSUB);
  WriteReg(BG3SC, gameplay_BG3SC);
  WriteReg(TM, gameplay_TM);

  uint16 a = irqhandler_next_handler;
  irqhandler_next_handler = 0;
  IrqHandler_SetResult(a ? a : 4, 0, 152);
}

void IrqHandler_8_StartOfDoor_BeginHud(void) {  // 0x8096D3
  WriteReg(BG3SC, 0x5A);
  WriteReg(TM, 4);
  WriteReg(CGWSEL, 0);
  WriteReg(CGADSUB, 0);
  IrqHandler_SetResult(10, 31, 152);
}

void IrqHandler_10_StartOfDoor_EndHud(void) {  // 0x8096F1
  uint8 v0;
  if (((previous_cre_bitset | cre_bitset) & 1) != 0)
    v0 = 16;
  else
    v0 = 17;
  WriteReg(TM, v0);
  uint16 a = irqhandler_next_handler;
  irqhandler_next_handler = 0;
  IrqHandler_SetResult(a ? a : 8, 0, 152);
}

void IrqHandler_12_Draygon_BeginHud(void) {  // 0x80971A
  WriteReg(TM, 4);
  WriteReg(CGWSEL, 0);
  WriteReg(CGADSUB, 0);
  IrqHandler_SetResult(14, 31, 152);
}

void IrqHandler_14_Draygon_EndHud(void) {  // 0x809733
  WriteReg(BG3SC, gameplay_BG3SC);
  WriteReg(CGWSEL, gameplay_CGWSEL);
  WriteReg(CGADSUB, gameplay_CGADSUB);
  uint16 a = irqhandler_next_handler;
  irqhandler_next_handler = 0;
  IrqHandler_SetResult(a ? a : 12, 0, 152);
}

void IrqHandler_16_VerticalDoor_BeginHud(void) {  // 0x809758
  WriteReg(TM, 4);
  WriteReg(CGWSEL, 0);
  WriteReg(CGADSUB, 0);
  IrqHandler_SetResult(18, 31, 152);
}

void IrqHandler_18_VerticalDoor_EndHud(void) {  // 0x809771
  uint8 v0;
  if (((previous_cre_bitset | cre_bitset) & 1) != 0)
    v0 = 16;
  else
    v0 = 17;
  WriteReg(TM, v0);
  WriteReg(CGWSEL, 0);
  WriteReg(CGADSUB, 0);
  if ((door_transition_flag & 0x8000) == 0)
    Irq_FollowDoorTransition();
  IrqHandler_SetResult(20, 216, 152);
}

void IrqHandler_20_VerticalDoor_EndDraw(void) {  // 0x8097A9
  uint16 a = irqhandler_next_handler;
  irqhandler_next_handler = 0;
  IrqHandler_SetResult(a ? a : 16, 0, 152);
}

void IrqHandler_22_HorizDoor_BeginHud(void) {  // 0x8097C1
  WriteReg(TM, 4);
  WriteReg(CGWSEL, 0);
  WriteReg(CGADSUB, 0);
  IrqHandler_SetResult(24, 31, 152);
}

void IrqHandler_24_HorizDoor_EndHud(void) {  // 0x8097DA
  uint8 v0;

  if (((previous_cre_bitset | cre_bitset) & 1) != 0)
    v0 = 16;
  else
    v0 = 17;
  WriteReg(TM, v0);
  WriteReg(CGWSEL, 0);
  WriteReg(CGADSUB, 0);
  if ((door_transition_flag & 0x8000) == 0)
    Irq_FollowDoorTransition();
  IrqHandler_SetResult(26, 160, 152);
}

void IrqHandler_26_HorizDoor_EndDraw(void) {  // 0x80980A
  uint16 a = irqhandler_next_handler;
  irqhandler_next_handler = 0;
  IrqHandler_SetResult(a ? a : 22, 0, 152);
}

void EnableIrqInterrupts(void) {  // 0x80982A
  WriteRegWord(VTIMEL, 0);
  WriteRegWord(HTIMEL, 152);
  *(uint16 *)&reg_NMITIMEN |= 0x30;
}

void EnableIrqInterruptsNow(void) {  // 0x809841
  WriteRegWord(VTIMEL, 0);
  WriteRegWord(HTIMEL, 152);
  *(uint16 *)&reg_NMITIMEN |= 0x30;
  WriteReg(NMITIMEN, reg_NMITIMEN);
}

void DisableIrqInterrupts(void) {  // 0x80985F
  *(uint16 *)&reg_NMITIMEN &= ~0x30;
}

static Func_V *const kIrqHandlers[14] = {  // 0x80986A
  IrqHandler_0_Nothing,
  IrqHandler_2_DisableIRQ,
  IrqHandler_4_Main_BeginHudDraw,
  IrqHandler_6_Main_EndHudDraw,
  IrqHandler_8_StartOfDoor_BeginHud,
  IrqHandler_10_StartOfDoor_EndHud,
  IrqHandler_12_Draygon_BeginHud,
  IrqHandler_14_Draygon_EndHud,
  IrqHandler_16_VerticalDoor_BeginHud,
  IrqHandler_18_VerticalDoor_EndHud,
  IrqHandler_20_VerticalDoor_EndDraw,
  IrqHandler_22_HorizDoor_BeginHud,
  IrqHandler_24_HorizDoor_EndHud,
  IrqHandler_26_HorizDoor_EndDraw,
};

void Vector_IRQ(void) {
  kIrqHandlers[cur_irq_handler >> 1]();
}

static const uint16 kHudTilemaps[32] = {  // 0x8099CF
  0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f,
  0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c1d, 0x2c1d, 0x2c1d, 0x2c1d, 0x2c1d, 0x2c1c,
};
static const uint16 kHudTilemaps_Row1to3[96] = {
  0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f,
  0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c12, 0x2c12, 0x2c23, 0x2c12, 0x2c12, 0x2c1e,
  0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f,
  0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2822, 0x2822, 0x2823, 0x2813, 0x2c14, 0x2c1e,
  0x2c0f, 0x2c0b, 0x2c0c, 0x2c0d, 0x2c32, 0x2c0f, 0x2c09, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f,
  0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c0f, 0x2c12, 0x2c12, 0xa824, 0x2815, 0x2c16, 0x2c1e,
};
static const uint16 kHudTilemaps_AutoReserve[12] = { 0x3c33, 0x3c46, 0x3c47, 0x3c48, 0xbc33, 0xbc46, 0x2c33, 0x2c46, 0x2c47, 0x2c48, 0xac33, 0xac46 };
static const uint16 kHudTilemaps_Missiles[22] = {
  0x344b, 0x3449, 0x744b, 0x344c, 0x344a, 0x744c, 0x3434, 0x7434, 0x3435, 0x7435, 0x3436, 0x7436, 0x3437, 0x7437, 0x3438, 0x7438,
  0x3439, 0x7439, 0x343a, 0x743a, 0x343b, 0x743b,
};

void AddMissilesToHudTilemap(void) {
  if ((hud_tilemap[10] & 0x3FF) == 15) {
    hud_tilemap[10] = kHudTilemaps_Missiles[0];
    hud_tilemap[11] = kHudTilemaps_Missiles[1];
    hud_tilemap[12] = kHudTilemaps_Missiles[2];
    hud_tilemap[42] = kHudTilemaps_Missiles[3];
    hud_tilemap[43] = kHudTilemaps_Missiles[4];
    hud_tilemap[44] = kHudTilemaps_Missiles[5];
  }
}

void AddSuperMissilesToHudTilemap(void) {  // 0x809A0E
  AddToTilemapInner(0x1C, (const uint16*)RomPtr_80(addr_kHudTilemaps_Missiles + 12));
}

void AddPowerBombsToHudTilemap(void) {  // 0x809A1E
  AddToTilemapInner(0x22, (const uint16 *)RomPtr_80(addr_kHudTilemaps_Missiles + 20));
}

void AddGrappleToHudTilemap(void) {  // 0x809A2E
  AddToTilemapInner(0x28, (const uint16 *)RomPtr_80(addr_kHudTilemaps_Missiles + 28));
}

void AddXrayToHudTilemap(void) {  // 0x809A3E
  AddToTilemapInner(0x2E, (const uint16 *)RomPtr_80(addr_kHudTilemaps_Missiles + 36));
}

void AddToTilemapInner(uint16 k, const uint16 *j) {  // 0x809A4C
  int v2 = k >> 1;
  if ((hud_tilemap[v2] & 0x3FF) == 15) {
    hud_tilemap[v2] = j[0];
    hud_tilemap[v2 + 1] = j[1];
    hud_tilemap[v2 + 32] = j[2];
    hud_tilemap[v2 + 33] = j[3];
  }
}

void InitializeHud(void) {  // 0x809A79
  WriteRegWord(VMADDL, addr_unk_605800);
  WriteRegWord(VMAIN, 0x80);
  static const StartDmaCopy unk_809A8F = { 1, 1, 0x18, LONGPTR(0x80988b), 0x0040 };
  SetupDmaTransfer(&unk_809A8F);
  WriteReg(MDMAEN, 2);
  for (int i = 0; i != 192; i += 2)
    hud_tilemap[i >> 1] = kHudTilemaps_Row1to3[i >> 1];
  if ((equipped_items & 0x8000) != 0)
    AddXrayToHudTilemap();
  if ((equipped_items & 0x4000) != 0)
    AddGrappleToHudTilemap();
  if (samus_max_missiles)
    AddMissilesToHudTilemap();
  if (samus_max_super_missiles)
    AddSuperMissilesToHudTilemap();
  if (samus_max_power_bombs)
    AddPowerBombsToHudTilemap();
  samus_prev_health = 0;
  samus_prev_missiles = 0;
  samus_prev_super_missiles = 0;
  samus_prev_power_bombs = 0;
  samus_prev_hud_item_index = 0;
  InitializeMiniMapBroken();
  if (samus_max_missiles)
    DrawThreeHudDigits(addrl_kDigitTilesetsWeapon, samus_missiles, 0x94);
  if (samus_max_super_missiles)
    DrawTwoHudDigits(addrl_kDigitTilesetsWeapon, samus_super_missiles, 0x9C);
  if (samus_max_power_bombs)
    DrawTwoHudDigits(addrl_kDigitTilesetsWeapon, samus_power_bombs, 0xA2);
  ToggleHudItemHighlight(hud_item_index, 0x1000);
  ToggleHudItemHighlight(samus_prev_hud_item_index, 0x1400);
  HandleHudTilemap();
}

static const uint16 kEnergyTankIconTilemapOffsets[14] = { 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 2, 4, 6, 8, 0xa, 0xc, 0xe };

void HandleHudTilemap(void) {  // 0x809B44
  if (reserve_health_mode == 1) {
    const uint16 *v1 = (const uint16 *)RomPtr_80(addr_kHudTilemaps_AutoReserve);
    if (!samus_reserve_health)
      v1 += 6;
    hud_tilemap[8] = v1[0];
    hud_tilemap[9] = v1[1];
    hud_tilemap[40] = v1[2];
    hud_tilemap[41] = v1[3];
    hud_tilemap[72] = v1[4];
    hud_tilemap[73] = v1[5];
  }
  if (samus_health != samus_prev_health) {
    samus_prev_health = samus_health;

    uint16 r20 = SnesDivide(samus_health, 100);
    uint16 r18 = SnesModulus(samus_health, 100);
    int v2 = 0;
    int n = SnesDivide(samus_max_health, 100) + 1;
    do {
      if (!--n)
        break;
      uint16 v3 = 13360;
      if (r20) {
        --r20;
        v3 = 10289;
      }
      hud_tilemap[kEnergyTankIconTilemapOffsets[v2 >> 1] >> 1] = v3;
      v2 += 2;
    } while ((int16)(v2 - 28) < 0);
    DrawTwoHudDigits(addrl_kDigitTilesetsHealth, r18, 0x8C);
  }
  if (samus_max_missiles && samus_missiles != samus_prev_missiles) {
    samus_prev_missiles = samus_missiles;
    DrawThreeHudDigits(addrl_kDigitTilesetsWeapon, samus_missiles, 0x94);
  }
  if (samus_max_super_missiles && samus_super_missiles != samus_prev_super_missiles) {
    samus_prev_super_missiles = samus_super_missiles;
    if ((joypad_dbg_flags & 0x1F40) != 0)
      DrawThreeHudDigits(addrl_kDigitTilesetsWeapon, samus_prev_super_missiles, 0x9C);
    else
      DrawTwoHudDigits(addrl_kDigitTilesetsWeapon, samus_prev_super_missiles, 0x9C);
  }
  if (samus_max_power_bombs && samus_power_bombs != samus_prev_power_bombs) {
    samus_prev_power_bombs = samus_power_bombs;
    DrawTwoHudDigits(addrl_kDigitTilesetsWeapon, samus_power_bombs, 0xA2);
  }
  if (hud_item_index != samus_prev_hud_item_index) {
    ToggleHudItemHighlight(hud_item_index, 0x1000);
    ToggleHudItemHighlight(samus_prev_hud_item_index, 0x1400);
    samus_prev_hud_item_index = hud_item_index;
    if (samus_movement_type != 3
        && samus_movement_type != 20
        && grapple_beam_function == 0xC4F0
        && !time_is_frozen_flag) {
      QueueSfx1_Max6(0x39);
    }
  }
  uint16 v4 = 5120;
  if ((nmi_frame_counter_byte & 0x10) != 0)
    v4 = 4096;
  ToggleHudItemHighlight(samus_auto_cancel_hud_item_index, v4);
  uint16 v5 = vram_write_queue_tail;
  gVramWriteEntry(vram_write_queue_tail)->size = 192;
  v5 += 2;
  gVramWriteEntry(v5)->size = ADDR16_OF_RAM(*hud_tilemap);
  v5 += 2;
  gVramWriteEntry(v5++)->size = 126;
  gVramWriteEntry(v5)->size = addr_unk_605820;
  vram_write_queue_tail = v5 + 2;
}

static const uint16 kHudItemTilemapOffsets[5] = { 0x14, 0x1c, 0x22, 0x28, 0x2e };

void ToggleHudItemHighlight(uint16 a, uint16 k) {  // 0x809CEA
  int16 v2;

  hud_item_tilemap_palette_bits = k;
  v2 = a - 1;
  if (v2 >= 0) {
    int v3 = kHudItemTilemapOffsets[v2] >> 1;
    if (hud_tilemap[v3] != 11279)
      hud_tilemap[v3] = hud_item_tilemap_palette_bits | hud_tilemap[v3] & 0xE3FF;
    if (hud_tilemap[v3 + 1] != 11279)
      hud_tilemap[v3 + 1] = hud_item_tilemap_palette_bits | hud_tilemap[v3 + 1] & 0xE3FF;
    if (hud_tilemap[v3 + 32] != 11279)
      hud_tilemap[v3 + 32] = hud_item_tilemap_palette_bits | hud_tilemap[v3 + 32] & 0xE3FF;
    if (hud_tilemap[v3 + 33] != 11279)
      hud_tilemap[v3 + 33] = hud_item_tilemap_palette_bits | hud_tilemap[v3 + 33] & 0xE3FF;
    if (!(2 * v2)) {
      if (hud_tilemap[v3 + 2] != 11279)
        hud_tilemap[v3 + 2] = hud_item_tilemap_palette_bits | hud_tilemap[v3 + 2] & 0xE3FF;
      if (hud_tilemap[v3 + 34] != 11279)
        hud_tilemap[v3 + 34] = hud_item_tilemap_palette_bits | hud_tilemap[v3 + 34] & 0xE3FF;
    }
  }
}

void DrawThreeHudDigits(uint32 addr, uint16 a, uint16 k) {  // 0x809D78
  hud_tilemap[k >> 1] = GET_WORD(RomPtr(addr + 2 * (a / 100)));
  DrawTwoHudDigits(addr, a % 100, k + 2);
}

void DrawTwoHudDigits(uint32 addr, uint16 a, uint16 k) {  // 0x809D98
  int v3 = k >> 1;
  hud_tilemap[v3] = GET_WORD(RomPtr(addr + 2 * (a / 10)));
  hud_tilemap[v3 + 1] = GET_WORD(RomPtr(addr + 2 * (a % 10)));
}

static Func_U8 *const kTimerProcessFuncs[7] = {  // 0x809DE7
  ProcessTimer_Empty,
  ProcessTimer_CeresStart,
  ProcessTimer_MotherBrainStart,
  ProcessTimer_InitialDelay,
  ProcessTimer_MovementDelayed,
  ProcessTimer_MovingIntoPlace,
  ProcessTimer_Decrement,
};

uint8 ProcessTimer(void) {
  return kTimerProcessFuncs[(uint8)timer_status]();
}

uint8 ProcessTimer_CeresStart(void) {  // 0x809E09
  ClearTimerRam();
  SetTimerMinutes(0x100);
  timer_status = -32765;
  return 0;
}

uint8 ProcessTimer_Empty(void) {  // 0x809E1A
  return 0;
}

uint8 ProcessTimer_MotherBrainStart(void) {  // 0x809E1C
  ClearTimerRam();
  SetTimerMinutes(0x300);
  timer_status = -32765;
  return 0;
}

uint8 ProcessTimer_InitialDelay(void) {  // 0x809E2F
  LOBYTE(timer_x_pos) = timer_x_pos + 1;
  if ((uint8)timer_x_pos >= 0x10)
    LOBYTE(timer_status) = timer_status + 1;
  return 0;
}

uint8 ProcessTimer_MovementDelayed(void) {  // 0x809E41
  LOBYTE(timer_x_pos) = timer_x_pos + 1;
  if ((uint8)timer_x_pos >= 0x60) {
    LOBYTE(timer_x_pos) = 0;
    LOBYTE(timer_status) = timer_status + 1;
  }
  return ProcessTimer_Decrement();
}

uint8 ProcessTimer_MovingIntoPlace(void) {  // 0x809E58
  int v0 = 0;
  uint16 v1 = timer_x_pos + 224;
  if ((uint16)(timer_x_pos + 224) >= 0xDC00) {
    v0 = 1;
    v1 = -9216;
  }
  timer_x_pos = v1;
  uint16 v2 = timer_y_pos - 193;
  if ((uint16)(timer_y_pos - 193) < 0x3000) {
    ++v0;
    v2 = 12288;
  }
  timer_y_pos = v2;
  if (v0 == 2)
    ++timer_status;
  return ProcessTimer_Decrement();
}

void SetTimerMinutes(uint16 a) {  // 0x809E8C
  *(uint16 *)&timer_centiseconds = 0;
  *(uint16 *)&timer_seconds = a;
}

void ClearTimerRam(void) {  // 0x809E93
  timer_x_pos = 0x8000;
  timer_y_pos = 0x8000;
  *(uint16 *)&timer_centiseconds = 0;
  *(uint16 *)&timer_seconds = 0;
  timer_status = 0;
}

bool DecrementDecimal(uint8 *number, uint8 value) {
  int result = (*number & 0xf) + (~value & 0xf) + 1;
  if (result < 0x10) result = (result - 0x6) & ((result - 0x6 < 0) ? 0xf : 0x1f);
  result = (*number & 0xf0) + (~value & 0xf0) + result;
  if (result < 0x100) result -= 0x60;
  *number = result;
  return result < 0x100;
}

uint8 ProcessTimer_Decrement(void) {  // 0x809EA9
  static const uint8 kTimerCentisecondDecrements[128] = {
    1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 2, 1, 2, 2,
    1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1, 2,
    1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 2, 1, 2, 2,
    1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1, 2,
    1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 2, 1, 2, 2,
    1, 2, 2, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2, 2, 1, 2,
    1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 2, 1, 2, 2,
    1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 2, 1, 2, 2,
  };
  if (DecrementDecimal(&timer_centiseconds, kTimerCentisecondDecrements[nmi_frame_counter_word & 0x7f])) {
    if (DecrementDecimal(&timer_seconds, 1)) {
      if (DecrementDecimal(&timer_minutes, 1)) {
        timer_centiseconds = 0;
        timer_seconds = 0;
        timer_minutes = 0;
      } else {
        timer_seconds = 0x59;
      }
    }
  }
  return (timer_centiseconds | timer_seconds | timer_minutes) == 0;

}

void DrawTimer(void) {  // 0x809F6C
  DrawTimerSpritemap(0, addr_word_80A060);
  DrawTwoTimerDigits(*(uint16 *)&timer_minutes, 0xFFE4);
  DrawTwoTimerDigits(*(uint16 *)&timer_seconds, 0xFFFC);
  DrawTwoTimerDigits(*(uint16 *)&timer_centiseconds, 0x14);
}


void DrawTwoTimerDigits(uint16 a, uint16 k) {  // 0x809F95
  DrawTimerSpritemap(k, kTimerDigitsSpritemapPtr[(a & 0xF0) >> 4]);
  DrawTimerSpritemap(k + 8, kTimerDigitsSpritemapPtr[a & 0xF]);
}

void DrawTimerSpritemap(uint16 a, uint16 j) {  // 0x809FB3
  DrawSpritemap(0x80, j, a + HIBYTE(timer_x_pos), HIBYTE(timer_y_pos), 2560);
}

CoroutineRet StartGameplay_Async(void) {  // 0x80A07B
  COROUTINE_BEGIN(coroutine_state_2, 0)
  WriteRegWord(MDMAEN, 0);
  scrolling_finished_hook = 0;
  music_data_index = 0;
  music_track_index = 0;
  timer_status = 0;
  ResetSoundQueues();
  debug_disable_sounds = -1;
  DisableNMI();
  DisableIrqInterrupts();
  LoadDestinationRoomThings();
  COROUTINE_AWAIT(1, Play20FramesOfMusic_Async());
  ClearAnimtiles();
  WaitUntilEndOfVblankAndClearHdma();
  InitializeSpecialEffectsForNewRoom();
  ClearPLMs();
  ClearEprojs();
  ClearPaletteFXObjects();
  UpdateBeamTilesAndPalette();
  LoadColorsForSpritesBeamsAndEnemies();
  LoadEnemies();
  LoadRoomMusic();
  COROUTINE_AWAIT(2, Play20FramesOfMusic_Async());
  UpdateMusicTrackIndex();
  NullFunc();
  ClearBG2Tilemap();
  LoadLevelDataAndOtherThings();
  LoadFXHeader();
  LoadLibraryBackground();
  CalculateLayer2Xpos();
  CalculateLayer2Ypos();
  bg2_x_scroll = layer2_x_pos;
  bg2_y_scroll = layer2_y_pos;
  CalculateBgScrolls();
  DisplayViewablePartOfRoom();
  EnableNMI();
  irqhandler_next_handler = (room_loading_irq_handler == 0) ? 4 : room_loading_irq_handler;
  EnableIrqInterrupts();
  COROUTINE_AWAIT(3, Play20FramesOfMusic_Async());
  SpawnHardcodedPlm((SpawnHardcodedPlmArgs) { 0x08, 0x08, 0xb7eb });
  door_transition_function = FUNC16(DoorTransition_FadeInScreenAndFinish);
  COROUTINE_END(0);
}

CoroutineRet Play20FramesOfMusic_Async(void) {  // 0x80A12B
  COROUTINE_BEGIN(coroutine_state_3, 0)
  EnableNMI();
  for(my_counter = 0; my_counter != 20; my_counter++) {
    HandleMusicQueue();
    COROUTINE_AWAIT(1, WaitForNMI_Async());
  }
  DisableNMI();
  COROUTINE_END(0);
}

void ResumeGameplay(void) {  // 0x80A149
  WriteRegWord(MDMAEN, 0);
  DisableNMI();
  DisableIrqInterrupts();
  LoadCRETilesTilesetTilesAndPalette();
  LoadLibraryBackground();
  DisplayViewablePartOfRoom();
  LoadRoomPlmGfx();
  EnableNMI();
  EnableIrqInterrupts();
}

void DisplayViewablePartOfRoom(void) {  // 0x80A176
  int v1, v2, v3;

  v1 = (reg_BG1SC - reg_BG2SC) << 8;
  size_of_bg2 = v1 & 0xF800;
  CalculateBgScrollAndLayerPositionBlocks();
  v2 = 0;
  do {
    v3 = v2;
    blocks_to_update_x_block = layer1_x_block;
    blocks_to_update_y_block = layer1_y_block;
    vram_blocks_to_update_x_block = bg1_x_block;
    vram_blocks_to_update_y_block = bg1_y_block;
    UploadLevelDataColumn();
    if (!(layer2_scroll_x & 1)) {
      blocks_to_update_x_block = layer2_x_block;
      blocks_to_update_y_block = layer2_y_block;
      vram_blocks_to_update_x_block = bg2_x_block;
      vram_blocks_to_update_y_block = bg2_y_block;
      UploadBackgroundDataColumn();
    }
    NMI_ProcessVramWriteQueue();
    ++layer1_x_block;
    ++bg1_x_block;
    ++layer2_x_block;
    ++bg2_x_block;
    ++v2;
  } while (v3 != 16);
}

void QueueClearingOfFxTilemap(void) {  // 0x80A211
  for (int i = 3838; i >= 0; i -= 2)
    *(uint16 *)((uint8 *)ram4000.xray_tilemaps + (uint16)i) = 6222;
  uint16 v1 = vram_write_queue_tail;
  VramWriteEntry *v2 = gVramWriteEntry(vram_write_queue_tail);
  v2->size = 3840;
  v2->src.addr = ADDR16_OF_RAM(ram4000);
  *(uint16 *)&v2->src.bank = 126;
  v2->vram_dst = addr_unk_605880;
  vram_write_queue_tail = v1 + 7;
}

void ClearBG2Tilemap(void) {  // 0x80A23F
  WriteRegWord(VMADDL, 0x4800);
  WriteRegWord(DMAP1, 0x1808);
  WriteRegWord(A1T1L, 0xA29A);
  WriteRegWord(A1B1, 0x80);
  WriteRegWord(DAS1L, 0x800);
  WriteReg(VMAIN, 0);
  WriteReg(MDMAEN, 2);
  WriteRegWord(VMADDL, 0x4800);
  WriteRegWord(DMAP1, 0x1908);
  WriteRegWord(A1T1L, 0xA29A);
  WriteRegWord(A1B1, 0x80);
  WriteRegWord(DAS1L, 0x800);
  WriteReg(VMAIN, 0x80);
  WriteReg(MDMAEN, 2);
}

void ClearFXTilemap(void) {  // 0x80A29C
  WriteRegWord(VMADDL, 0x5880);
  WriteRegWord(DMAP1, 0x1808);
  WriteRegWord(A1T1L, 0xA2F7);
  WriteRegWord(A1B1, 0x80);
  WriteRegWord(DAS1L, 0x780);
  WriteReg(VMAIN, 0);
  WriteReg(MDMAEN, 2);
  WriteRegWord(VMADDL, 0x5880);
  WriteRegWord(DMAP1, 0x1908);
  WriteRegWord(A1T1L, 0xA2F8);
  WriteRegWord(A1B1, 0x80);
  WriteRegWord(DAS1L, 0x780);
  WriteReg(VMAIN, 0x80);
  WriteReg(MDMAEN, 2);
}

uint8 CalculateLayer2Xpos(void) {  // 0x80A2F9
  if (!layer2_scroll_x) {
    layer2_x_pos = layer1_x_pos;
    return 0;
  }
  if (layer2_scroll_x != 1) {
    int t = layer2_scroll_x & 0xFE;
    uint8 var933 = t * LOBYTE(layer1_x_pos) >> 8;
    layer2_x_pos = t * HIBYTE(layer1_x_pos) + var933;
    return 0;
  }
  return 1;
}

uint8 CalculateLayer2Ypos(void) {  // 0x80A33A
  if (!layer2_scroll_y) {
    layer2_y_pos = layer1_y_pos;
    return 0;
  }
  if (layer2_scroll_y != 1) {
    int t = layer2_scroll_y & 0xFE;
    uint8 var933 = t * (uint8)layer1_y_pos >> 8;
    layer2_y_pos = t * HIBYTE(layer1_y_pos) + var933;
    return 0;
  }
  return 1;
}

void CalculateBgScrolls(void) {  // 0x80A37B
  reg_BG1HOFS = bg1_x_offset + layer1_x_pos;
  reg_BG1VOFS = bg1_y_offset + layer1_y_pos;
  reg_BG2HOFS = bg2_x_scroll + layer2_x_pos;
  reg_BG2VOFS = bg2_y_scroll + layer2_y_pos;
}

void UpdateScrollVarsUpdateMap(void) {  // 0x80A3A0
  CalculateBgScrolls();
  UpdateBgGraphicsWhenScrolling();
}

void CalculateLayer2PosAndScrollsWhenScrolling(void) {  // 0x80A3AB
  if (!time_is_frozen_flag) {
    reg_BG1HOFS = bg1_x_offset + layer1_x_pos;
    reg_BG1VOFS = bg1_y_offset + layer1_y_pos;
    if (!CalculateLayer2Xpos())
      reg_BG2HOFS = bg2_x_scroll + layer2_x_pos;
    if (!CalculateLayer2Ypos())
      reg_BG2VOFS = bg2_y_scroll + layer2_y_pos;
    UpdateBgGraphicsWhenScrolling();
  }
}

void UpdateBgGraphicsWhenScrolling(void) {  // 0x80A3DF
  CalculateBgScrollAndLayerPositionBlocks();
  int v0 = 0;
  bool v1 = (int16)(layer1_x_block - previous_layer1_x_block) < 0;
  if (layer1_x_block != previous_layer1_x_block) {
    previous_layer1_x_block = layer1_x_block;
    if (!v1)
      v0 = 16;
    blocks_to_update_x_block = layer1_x_block + v0;
    vram_blocks_to_update_x_block = bg1_x_block + v0;
    blocks_to_update_y_block = layer1_y_block;
    vram_blocks_to_update_y_block = bg1_y_block;
    UploadLevelDataColumn();
  }
  if (!(layer2_scroll_x & 1)) {
    int v2 = 0;
    bool v3 = (int16)(layer2_x_block - previous_layer2_x_block) < 0;
    if (layer2_x_block != previous_layer2_x_block) {
      previous_layer2_x_block = layer2_x_block;
      if (!v3)
        v2 = 16;
      blocks_to_update_x_block = layer2_x_block + v2;
      vram_blocks_to_update_x_block = bg2_x_block + v2;
      blocks_to_update_y_block = layer2_y_block;
      vram_blocks_to_update_y_block = bg2_y_block;
      UploadBackgroundDataColumn();
    }
  }
  int v4 = 1;
  bool v5 = (int16)(layer1_y_block - previous_layer1_y_block) < 0;
  if (layer1_y_block != previous_layer1_y_block) {
    previous_layer1_y_block = layer1_y_block;
    if (!v5)
      v4 = 15;
    blocks_to_update_y_block = layer1_y_block + v4;
    vram_blocks_to_update_y_block = bg1_y_block + v4;
    blocks_to_update_x_block = layer1_x_block;
    vram_blocks_to_update_x_block = bg1_x_block;
    UpdateLevelDataRow();
  }
  if (!(layer2_scroll_y & 1)) {
    int v6 = 1;
    bool v7 = (int16)(layer2_y_block - previous_layer2_y_block) < 0;
    if (layer2_y_block != previous_layer2_y_block) {
      previous_layer2_y_block = layer2_y_block;
      if (!v7)
        v6 = 15;
      blocks_to_update_y_block = layer2_y_block + v6;
      vram_blocks_to_update_y_block = bg2_y_block + v6;
      blocks_to_update_x_block = layer2_x_block;
      vram_blocks_to_update_x_block = bg2_x_block;
      UpdateBackgroundDataRow();
    }
  }
}

void CalculateBgScrollAndLayerPositionBlocks(void) {  // 0x80A4BB
  bg1_x_block = reg_BG1HOFS >> 4;
  bg2_x_block = reg_BG2HOFS >> 4;
  uint16 v0 = layer1_x_pos >> 4;
  if (((layer1_x_pos >> 4) & 0x800) != 0)
    v0 |= 0xF000;
  layer1_x_block = v0;
  uint16 v1 = layer2_x_pos >> 4;
  if (((layer2_x_pos >> 4) & 0x800) != 0)
    v1 |= 0xF000;
  layer2_x_block = v1;
  bg1_y_block = reg_BG1VOFS >> 4;
  bg2_y_block = reg_BG2VOFS >> 4;
  uint16 v2 = layer1_y_pos >> 4;
  if (((layer1_y_pos >> 4) & 0x800) != 0)
    v2 |= 0xF000;
  layer1_y_block = v2;
  uint16 v3 = layer2_y_pos >> 4;
  if (((layer2_y_pos >> 4) & 0x800) != 0)
    v3 |= 0xF000;
  layer2_y_block = v3;
}

void HandleAutoscrolling_X(void) {  // 0x80A528
  int16 v1;
  int16 v3;
  int16 v6;
  uint16 v4;
  uint16 var933;

  if (!time_is_frozen_flag) {
    uint16 var939 = layer1_x_pos;
    if ((layer1_x_pos & 0x8000) != 0)
      layer1_x_pos = 0;
    uint16 v0 = swap16(room_width_in_scrolls - 1);
    if (v0 < layer1_x_pos)
      layer1_x_pos = v0;
    v1 = HIBYTE(layer1_x_pos);
    uint16 v2 = Mult8x8((uint16)(layer1_y_pos + 128) >> 8, room_width_in_scrolls) + v1;
    if (scrolls[v2]) {
      if (scrolls[(uint16)(v2 + 1)])
        return;
      var933 = layer1_x_pos & 0xFF00;
      uint16 v5 = var939 - absolute_moved_last_frame_x - 2;
      if ((int16)(v5 - (layer1_x_pos & 0xFF00)) < 0) {
        v4 = var933;
      } else {
        var939 = v5;
        v6 = HIBYTE(var939);
        if (scrolls[(uint16)(Mult8x8((uint16)(layer1_y_pos + 128) >> 8, room_width_in_scrolls) + v6)])
          v4 = var939;
        else
          v4 = (var939 & 0xFF00) + 256;
      }
    } else {
      var933 = (layer1_x_pos & 0xFF00) + 256;
      if ((uint16)(absolute_moved_last_frame_x + var939 + 2) >= var933) {
        v4 = var933;
      } else {
        var939 += absolute_moved_last_frame_x + 2;
        v3 = (uint8)(HIBYTE(var939) + 1);
        if (scrolls[(uint16)(Mult8x8((uint16)(layer1_y_pos + 128) >> 8, room_width_in_scrolls) + v3)])
          v4 = var939;
        else
          v4 = var939 & 0xFF00;
      }
    }
    layer1_x_pos = v4;
  }
}

void HandleScrollingWhenTriggeringScrollRight(void) {  // 0x80A641
  int16 v1;

  uint16 var939 = layer1_x_pos;
  if ((int16)(ideal_layer1_xpos - layer1_x_pos) < 0) {
    layer1_x_pos = ideal_layer1_xpos;
    layer1_x_subpos = 0;
  }
  uint16 v0 = swap16(room_width_in_scrolls - 1);
  if (v0 >= layer1_x_pos) {
    v1 = HIBYTE(layer1_x_pos);
    uint16 RegWord = Mult8x8((uint16)(layer1_y_pos + 128) >> 8, room_width_in_scrolls);
    if (!scrolls[(uint16)(RegWord + 1 + v1)]) {
      uint16 var933 = layer1_x_pos & 0xFF00;
      uint16 v4 = var939 - absolute_moved_last_frame_x - 2;
      if ((int16)(v4 - (layer1_x_pos & 0xFF00)) < 0)
        v4 = var933;
      layer1_x_pos = v4;
    }
  } else {
    layer1_x_pos = v0;
  }
}

void HandleScrollingWhenTriggeringScrollLeft(void) {  // 0x80A6BB
  int16 v0;

  uint16 var939 = layer1_x_pos;
  if ((int16)(layer1_x_pos - ideal_layer1_xpos) < 0) {
    layer1_x_pos = ideal_layer1_xpos;
    layer1_x_subpos = 0;
  }
  if ((layer1_x_pos & 0x8000) == 0) {
    v0 = HIBYTE(layer1_x_pos);
    uint16 prod = Mult8x8((uint16)(layer1_y_pos + 128) >> 8, room_width_in_scrolls);
    if (!scrolls[(uint16)(prod + v0)]) {
      uint16 var933 = (layer1_x_pos & 0xFF00) + 256;
      uint16 v1 = absolute_moved_last_frame_x + var939 + 2;
      if (v1 >= var933)
        v1 = var933;
      layer1_x_pos = v1;
    }
  } else {
    layer1_x_pos = 0;
  }
}

void HandleAutoscrolling_Y(void) {  // 0x80A731
  int16 v1;
  int16 v2;
  int16 v4;
  int16 v7;
  int16 v10;
  uint16 v8;

  if (!time_is_frozen_flag) {
    uint16 v0 = 0;
    v1 = (uint16)(layer1_x_pos + 128) >> 8;
    uint16 r20 = Mult8x8(HIBYTE(layer1_y_pos), room_width_in_scrolls) + v1;
    if (scrolls[r20] != 1)
      v0 = 31;
    uint16 var933 = v0;
    uint16 var939 = layer1_y_pos;
    if ((layer1_y_pos & 0x8000) != 0)
      layer1_y_pos = 0;
    LOBYTE(v2) = (uint16)(room_height_in_scrolls - 1) >> 8;
    HIBYTE(v2) = room_height_in_scrolls - 1;
    uint16 v3 = var933 + v2;
    if (v3 < layer1_y_pos)
      layer1_y_pos = v3;
    v4 = (uint16)(layer1_x_pos + 128) >> 8;
    uint16 v5 = Mult8x8(HIBYTE(layer1_y_pos), room_width_in_scrolls) + v4;
    if (!scrolls[v5]) {
      uint16 var935 = (layer1_y_pos & 0xFF00) + 256;
      uint16 v6 = absolute_moved_last_frame_y + var939 + 2;
      if (v6 >= var935) {
        v8 = var935;
      } else {
        var939 += absolute_moved_last_frame_y + 2;
        v7 = (uint16)(layer1_x_pos + 128) >> 8;
        uint16 prod = Mult8x8(HIBYTE(v6) + 1, room_width_in_scrolls);
        if (scrolls[(uint16)(prod + v7)])
          v8 = var939;
        else
          v8 = var939 & 0xFF00;
      }
      layer1_y_pos = v8;
      return;
    }
    if (!scrolls[(uint16)(room_width_in_scrolls + v5)]) {
      uint16 var937 = var933 + (layer1_y_pos & 0xFF00);
      if (var937 < layer1_y_pos) {
        uint16 v9 = var939 - absolute_moved_last_frame_y - 2;
        if ((int16)(v9 - var937) < 0) {
          v8 = var937;
        } else {
          var939 = v9;
          uint16 prod = Mult8x8(HIBYTE(v9), room_width_in_scrolls);
          v10 = (uint16)(layer1_x_pos + 128) >> 8;
          if (scrolls[(uint16)(prod + v10)])
            v8 = var939;
          else
            v8 = (var939 & 0xFF00) + 256;
        }
        layer1_y_pos = v8;
        return;
      }
    }
  }
}

void HandleScrollingWhenTriggeringScrollDown(void) {  // 0x80A893
  uint16 var939 = layer1_y_pos;
  int v0 = 0;
  uint16 prod = Mult8x8(HIBYTE(layer1_y_pos), room_width_in_scrolls);
  uint16 v1 = (uint16)(layer1_x_pos + 128) >> 8;
  uint16 r20 = prod + v1;
  if (scrolls[r20] != 1)
    v0 = 31;
  uint16 var933 = v0;
  if ((int16)(ideal_layer1_ypos - layer1_y_pos) < 0) {
    layer1_y_pos = ideal_layer1_ypos;
    layer1_y_subpos = 0;
  }
  uint16 v2 = swap16(room_height_in_scrolls - 1);
  uint16 var937 = var933 + v2;
  if ((uint16)(var933 + v2) < layer1_y_pos
      || !scrolls[(uint16)(room_width_in_scrolls + r20)]
      && (var937 = var933 + (layer1_y_pos & 0xFF00), var937 < layer1_y_pos)) {
    uint16 v3 = var939 - absolute_moved_last_frame_y - 2;
    if ((int16)(v3 - var937) < 0)
      v3 = var937;
    layer1_y_pos = v3;
  }
}

void HandleScrollingWhenTriggeringScrollUp(void) {  // 0x80A936
  int16 v0;

  uint16 var939 = layer1_y_pos;
  if ((int16)(layer1_y_pos - ideal_layer1_ypos) < 0) {
    layer1_y_pos = ideal_layer1_ypos;
    layer1_y_subpos = 0;
  }
  if ((layer1_y_pos & 0x8000) == 0) {
    uint16 prod = Mult8x8(HIBYTE(layer1_y_pos), room_width_in_scrolls);
    v0 = (uint16)(layer1_x_pos + 128) >> 8;
    if (!scrolls[(uint16)(prod + v0)]) {
      uint16 var933 = (layer1_y_pos & 0xFF00) + 256;
      uint16 v1 = absolute_moved_last_frame_y + var939 + 2;
      if (v1 >= var933)
        v1 = var933;
      layer1_y_pos = v1;
    }
  } else {
    layer1_y_pos = 0;
  }
}

void DebugScrollPosSaveLoad(void) {  // 0x80A9AC
  if ((joypad2_new_keys & 0x40) != 0)
    ++debug_saveload_scrollpos_toggle;
  if (debug_saveload_scrollpos_toggle & 1) {
    layer1_x_pos = debug_saved_xscroll;
    layer1_y_pos = debug_saved_yscroll;
  } else {
    debug_saved_xscroll = layer1_x_pos;
    debug_saved_yscroll = layer1_y_pos;
  }
}

void UploadBackgroundDataColumn(void) {  // 0x80A9D6
  UpdateLevelOrBackgroundDataColumn(0x1c);
}

void UploadLevelDataColumn(void) {  // 0x80A9DB
  UpdateLevelOrBackgroundDataColumn(0);
}

void UpdateLevelOrBackgroundDataColumn(uint16 k) {  // 0x80A9DE
  if (irq_enable_mode7)
    return;

  uint16 prod = Mult8x8(blocks_to_update_y_block, room_width_in_blocks);
  uint16 v1 = blocks_to_update_x_block;
  uint16 v2 = 2 * (prod + v1) + 2;
  if (k)
    v2 += 0x9600;
  const uint16 *r54 = (const uint16 * )(g_ram + 0x10000 + v2);
  uint16 v3 = (4 * vram_blocks_to_update_y_block) & 0x3C;
  *(uint16 *)((uint8 *)&bg1_update_col_wrapped_size + k) = v3;
  *(uint16 *)((uint8 *)&bg1_update_col_unwrapped_size + k) = (v3 ^ 0x3F) + 1;
  prod = Mult8x8(vram_blocks_to_update_y_block & 0xF, 0x40);
  uint16 var935 = vram_blocks_to_update_x_block & 0x1F;
  uint16 v4 = 2 * var935;
  uint16 var933 = prod + v4;
  uint16 v5 = addr_unk_605000;
  if (var935 >= 0x10)
    v5 = addr_unk_6053E0;
  if (k)
    v5 -= size_of_bg2;
  uint16 var937 = v5;
  *(uint16 *)((uint8 *)&bg1_update_col_unwrapped_dst + k) = var933 + v5;
  *(uint16 *)((uint8 *)&bg1_update_col_wrapped_dst + k) = var935 + var935 + var937;
  uint16 v6 = ADDR16_OF_RAM(*bg1_column_update_tilemap_left_halves);
  uint16 v7 = 0;
  if (k) {
    v6 = ADDR16_OF_RAM(*bg2_column_update_tilemap_left_halves);
    v7 = 264;
  }
  uint16 v8 = *(uint16 *)((uint8 *)&bg1_update_col_unwrapped_size + k) + v6;
  *(uint16 *)((uint8 *)&bg1_update_col_wrapped_left_src + k) = v8;
  *(uint16 *)((uint8 *)&bg1_update_col_wrapped_right_src + k) = v8 + 64;
  var937 = v7;
  uint16 t2 = k;
  uint16 v9 = 0;
  uint16 var939 = 16;
  do {
    uint16 var93B = r54[v9 >> 1];
    uint16 v10 = var93B & 0x3FF;
    uint16 v17 = v9;
    uint16 v11 = var937;
    uint16 v12 = var93B & 0xC00;
    if ((var93B & 0xC00) != 0) {
      if (v12 == 1024) {
        uint16 v14 = var937 >> 1;
        bg1_column_update_tilemap_left_halves[v14] = tile_table.tables[v10].top_right ^ 0x4000;
        bg1_column_update_tilemap_right_halves[v14] = tile_table.tables[v10].top_left ^ 0x4000;
        bg1_column_update_tilemap_left_halves[v14 + 1] = tile_table.tables[v10].bottom_right ^ 0x4000;
        bg1_column_update_tilemap_right_halves[v14 + 1] = tile_table.tables[v10].bottom_left ^ 0x4000;
      } else {
        uint16 v15 = var937 >> 1;
        uint16 v16;
        if (v12 == 2048) {
          bg1_column_update_tilemap_left_halves[v15] = tile_table.tables[v10].bottom_left ^ 0x8000;
          bg1_column_update_tilemap_right_halves[v15] = tile_table.tables[v10].bottom_right ^ 0x8000;
          bg1_column_update_tilemap_left_halves[v15 + 1] = tile_table.tables[v10].top_left ^ 0x8000;
          v16 = tile_table.tables[v10].top_right ^ 0x8000;
        } else {
          bg1_column_update_tilemap_left_halves[v15] = tile_table.tables[v10].bottom_right ^ 0xC000;
          bg1_column_update_tilemap_right_halves[v15] = tile_table.tables[v10].bottom_left ^ 0xC000;
          bg1_column_update_tilemap_left_halves[v15 + 1] = tile_table.tables[v10].top_right ^ 0xC000;
          v16 = tile_table.tables[v10].top_left ^ 0xC000;
        }
        bg1_column_update_tilemap_right_halves[v15 + 1] = v16;
      }
    } else {
      uint16 v13 = var937 >> 1;
      bg1_column_update_tilemap_left_halves[v13] = tile_table.tables[v10].top_left;
      bg1_column_update_tilemap_right_halves[v13] = tile_table.tables[v10].top_right;
      bg1_column_update_tilemap_left_halves[v13 + 1] = tile_table.tables[v10].bottom_left;
      bg1_column_update_tilemap_right_halves[v13 + 1] = tile_table.tables[v10].bottom_right;
    }
    var937 = v11 + 4;
    v9 = room_width_in_blocks * 2 + v17;
    --var939;
  } while (var939);
  ++ *(uint16 *)((uint8 *)&bg1_update_col_enable + t2);
}

void UpdateBackgroundDataRow(void) {  // 0x80AB70
  UpdateLevelOrBackgroundDataRow(0x1c);
}

void UpdateLevelDataRow(void) {  // 0x80AB75
  UpdateLevelOrBackgroundDataRow(0);
}

void UpdateLevelOrBackgroundDataRow(uint16 v0) {  // 0x80AB78
  if (irq_enable_mode7)
    return;
  uint16 prod = Mult8x8(blocks_to_update_y_block, room_width_in_blocks);
  uint16 v1 = blocks_to_update_x_block;
  uint16 v2 = 2 * (prod + v1) + 2;
  if (v0)
    v2 -= 27136;
  const uint16 *r54 = (const uint16 *)(g_ram + 0x10000 + v2);
  uint16 var933 = vram_blocks_to_update_x_block & 0xF;
  *(uint16 *)((uint8 *)&bg1_update_row_unwrapped_size + v0) = 4 * (16 - var933);
  *(uint16 *)((uint8 *)&bg1_update_row_wrapped_size + v0) = 4 * (var933 + 1);
  prod = Mult8x8(vram_blocks_to_update_y_block & 0xF, 0x40);
  uint16 var935 = vram_blocks_to_update_x_block & 0x1F;
  uint16 v3 = 2 * var935;
  var933 = prod + v3;
  uint16 var937 = addr_unk_605400;
  uint16 v4 = addr_unk_605000;
  if (var935 >= 0x10) {
    var937 = addr_unk_605000;
    v4 = addr_unk_6053E0;
  }
  if (v0)
    v4 -= size_of_bg2;
  *(uint16 *)((uint8 *)&bg1_update_row_unwrapped_dst + v0) = var933 + v4;
  uint16 v5 = var937;
  if (v0)
    v5 = var937 - size_of_bg2;
  *(uint16 *)((uint8 *)&bg1_update_row_wrapped_dst + v0) = prod + v5;
  uint16 v6 = ADDR16_OF_RAM(*bg1_column_update_tilemap_top_halves);
  uint16 v7 = 0;
  if (v0) {
    v6 = ADDR16_OF_RAM(*bg2_column_update_tilemap_top_halves);
    v7 = 264;
  }
  uint16 v8 = *(uint16 *)((uint8 *)&bg1_update_row_unwrapped_size + v0) + v6;
  *(uint16 *)((uint8 *)&bg1_update_row_wrapped_top_src + v0) = v8;
  *(uint16 *)((uint8 *)&bg1_update_row_wrapped_bottom_src + v0) = v8 + 68;
  var937 = v7;
  uint16 t2 = v0;
  uint16 v9 = 0;
  uint16 var939 = 17;
  do {
    uint16 var93B = r54[v9 >> 1];
    uint16 v10 = var93B & 0x3FF;
    uint16 v17 = v9;
    uint16 v11 = var937;
    uint16 v12 = var93B & 0xC00;
    if ((var93B & 0xC00) != 0) {
      if (v12 == 1024) {
        uint16 v14 = var937 >> 1;
        bg1_column_update_tilemap_top_halves[v14] = tile_table.tables[v10].top_right ^ 0x4000;
        bg1_column_update_tilemap_top_halves[v14 + 1] = tile_table.tables[v10].top_left ^ 0x4000;
        bg1_column_update_tilemap_bottom_halves[v14] = tile_table.tables[v10].bottom_right ^ 0x4000;
        bg1_column_update_tilemap_bottom_halves[v14 + 1] = tile_table.tables[v10].bottom_left ^ 0x4000;
      } else {
        uint16 v15 = var937 >> 1;
        uint16 v16;
        if (v12 == 2048) {
          bg1_column_update_tilemap_top_halves[v15] = tile_table.tables[v10].bottom_left ^ 0x8000;
          bg1_column_update_tilemap_top_halves[v15 + 1] = tile_table.tables[v10].bottom_right ^ 0x8000;
          bg1_column_update_tilemap_bottom_halves[v15] = tile_table.tables[v10].top_left ^ 0x8000;
          v16 = tile_table.tables[v10].top_right ^ 0x8000;
        } else {
          bg1_column_update_tilemap_top_halves[v15] = tile_table.tables[v10].bottom_right ^ 0xC000;
          bg1_column_update_tilemap_top_halves[v15 + 1] = tile_table.tables[v10].bottom_left ^ 0xC000;
          bg1_column_update_tilemap_bottom_halves[v15] = tile_table.tables[v10].top_right ^ 0xC000;
          v16 = tile_table.tables[v10].top_left ^ 0xC000;
        }
        bg1_column_update_tilemap_bottom_halves[v15 + 1] = v16;
      }
    } else {
      uint16 v13 = var937 >> 1;
      bg1_column_update_tilemap_top_halves[v13] = tile_table.tables[v10].top_left;
      bg1_column_update_tilemap_top_halves[v13 + 1] = tile_table.tables[v10].top_right;
      bg1_column_update_tilemap_bottom_halves[v13] = tile_table.tables[v10].bottom_left;
      bg1_column_update_tilemap_bottom_halves[v13 + 1] = tile_table.tables[v10].bottom_right;
    }
    var937 = v11 + 4;
    v9 = v17 + 2;
    --var939;
  } while (var939);
  ++ *(uint16 *)((uint8 *)&bg1_update_row_enable + t2);
}

void FixDoorsMovingUp(void) {  // 0x80AD1D
  door_transition_frame_counter = 0;
  CalculateBgScrollAndLayerPositionBlocks();
  UpdatePreviousLayerBlocks();
  ++previous_layer1_y_block;
  ++previous_layer2_y_block;
  DoorTransition_Up();
}

static Func_V *const kDoorTransitionSetupFuncs[4] = {  // 0x80AD30
  DoorTransitionScrollingSetup_Right,
  DoorTransitionScrollingSetup_Left,
  DoorTransitionScrollingSetup_Down,
  DoorTransitionScrollingSetup_Up,
};

void DoorTransitionScrollingSetup(void) {
  layer1_x_pos = door_destination_x_pos;
  layer1_y_pos = door_destination_y_pos;
  kDoorTransitionSetupFuncs[door_direction & 3]();
}

void DoorTransitionScrollingSetup_Right(void) {  // 0x80AD4A
  CalculateLayer2Xpos();
  layer2_x_pos -= 256;
  CalculateLayer2Ypos();
  layer1_x_pos -= 256;
  UpdateBgScrollOffsets();
  CalculateBgScrollAndLayerPositionBlocks();
  UpdatePreviousLayerBlocks();
  --previous_layer1_x_block;
  --previous_layer2_x_block;
  DoorTransition_Right();
}

void DoorTransitionScrollingSetup_Left(void) {  // 0x80AD74
  CalculateLayer2Xpos();
  layer2_x_pos += 256;
  CalculateLayer2Ypos();
  layer1_x_pos += 256;
  UpdateBgScrollOffsets();
  CalculateBgScrollAndLayerPositionBlocks();
  UpdatePreviousLayerBlocks();
  ++previous_layer1_x_block;
  ++previous_layer2_x_block;
  DoorTransition_Left();
}

void DoorTransitionScrollingSetup_Down(void) {  // 0x80AD9E
  CalculateLayer2Xpos();
  CalculateLayer2Ypos();
  layer2_y_pos -= 224;
  layer1_y_pos -= 224;
  UpdateBgScrollOffsets();
  CalculateBgScrollAndLayerPositionBlocks();
  UpdatePreviousLayerBlocks();
  --previous_layer1_y_block;
  --previous_layer2_y_block;
  DoorTransition_Down();
}

void DoorTransitionScrollingSetup_Up(void) {  // 0x80ADC8
  CalculateLayer2Xpos();
  uint16 v1 = layer1_y_pos;
  layer1_y_pos += 31;
  CalculateLayer2Ypos();
  layer2_y_pos += 224;
  layer1_y_pos = v1 + 256;
  UpdateBgScrollOffsets();
  door_destination_y_pos += 32;
  CalculateBgScrollAndLayerPositionBlocks();
  UpdatePreviousLayerBlocks();
  ++previous_layer1_y_block;
  ++previous_layer2_y_block;
  --layer1_y_pos;
  DoorTransition_Up();
}

void UpdatePreviousLayerBlocks(void) {  // 0x80AE10
  previous_layer1_x_block = layer1_x_block;
  previous_layer2_x_block = layer2_x_block;
  previous_layer1_y_block = layer1_y_block;
  previous_layer2_y_block = layer2_y_block;
}

void UpdateBgScrollOffsets(void) {  // 0x80AE29
  bg1_x_offset = reg_BG1HOFS - layer1_x_pos;
  bg1_y_offset = reg_BG1VOFS - layer1_y_pos;
  bg2_x_scroll = reg_BG2HOFS - layer1_x_pos;
  bg2_y_scroll = reg_BG2VOFS - layer1_y_pos;
}

static Func_U8 *const kDoorTransitionFuncs[4] = {
  DoorTransition_Right,
  DoorTransition_Left,
  DoorTransition_Down,
  DoorTransition_Up,
};

void Irq_FollowDoorTransition(void) {
  if (kDoorTransitionFuncs[door_direction & 3]()) {
    layer1_x_pos = door_destination_x_pos;
    layer1_y_pos = door_destination_y_pos;
    door_transition_flag |= 0x8000;
  }
}

uint8 DoorTransition_Right(void) {  // 0x80AE7E
  uint16 v2 = door_transition_frame_counter;
  AddToHiLo(&samus_x_pos, &samus_x_subpos, __PAIR32__(samus_door_transition_speed, samus_door_transition_subspeed));
  samus_prev_x_pos = samus_x_pos;
  layer1_x_pos += 4;
  layer2_x_pos += 4;
  UpdateScrollVarsUpdateMap();
  door_transition_frame_counter = v2 + 1;
  if (v2 != 63)
    return 0;
  UpdateScrollVarsUpdateMap();
  return 1;
}

uint8 DoorTransition_Left(void) {  // 0x80AEC2
  uint16 v2 = door_transition_frame_counter;
  AddToHiLo(&samus_x_pos, &samus_x_subpos, -IPAIR32(samus_door_transition_speed, samus_door_transition_subspeed));
  samus_prev_x_pos = samus_x_pos;
  layer1_x_pos -= 4;
  layer2_x_pos -= 4;
  UpdateScrollVarsUpdateMap();
  door_transition_frame_counter = v2 + 1;
  return v2 == 63;
}

uint8 DoorTransition_Down(void) {  // 0x80AF02
  uint16 v6 = door_transition_frame_counter;
  if (door_transition_frame_counter) {
    if (door_transition_frame_counter < 0x39) {
      AddToHiLo(&samus_y_pos, &samus_y_subpos, __PAIR32__(samus_door_transition_speed, samus_door_transition_subspeed));
      samus_prev_y_pos = samus_y_pos;
      layer1_y_pos += 4;
      layer2_y_pos += 4;
      UpdateScrollVarsUpdateMap();
    }
  } else {
    uint16 v5 = reg_BG1VOFS;
    uint16 v4 = reg_BG2VOFS;
    uint16 v3 = layer1_y_pos;
    layer1_y_pos -= 15;
    uint16 v2 = layer2_y_pos;
    layer2_y_pos -= 15;
    CalculateBgScrollAndLayerPositionBlocks();
    UpdatePreviousLayerBlocks();
    --previous_layer1_y_block;
    --previous_layer2_y_block;
    UpdateScrollVarsUpdateMap();
    layer2_y_pos = v2;
    layer1_y_pos = v3;
    reg_BG2VOFS = v4;
    reg_BG1VOFS = v5;
  }
  door_transition_frame_counter = v6 + 1;
  if ((uint16)(v6 + 1) < 0x39)
    return 0;
  UpdateScrollVarsUpdateMap();
  return 1;
}

uint8 DoorTransition_Up(void) {  // 0x80AF89
  uint16 v6 = door_transition_frame_counter;
  if (door_transition_frame_counter) {
    AddToHiLo(&samus_y_pos, &samus_y_subpos, -IPAIR32(samus_door_transition_speed, samus_door_transition_subspeed));
    samus_prev_y_pos = samus_y_pos;
    layer1_y_pos -= 4;
    layer2_y_pos -= 4;
    if (door_transition_frame_counter >= 5) {
      UpdateScrollVarsUpdateMap();
    } else {
      reg_BG1HOFS = bg1_x_offset + layer1_x_pos;
      reg_BG1VOFS = bg1_y_offset + layer1_y_pos;
      reg_BG2HOFS = bg2_x_scroll + layer2_x_pos;
      reg_BG2VOFS = bg2_y_scroll + layer2_y_pos;
    }
  } else {
    uint16 v5 = reg_BG1VOFS;
    uint16 v4 = reg_BG2VOFS;
    uint16 v3 = layer1_y_pos;
    layer1_y_pos -= 16;
    uint16 v2 = layer2_y_pos;
    layer2_y_pos -= 16;
    CalculateBgScrollAndLayerPositionBlocks();
    UpdatePreviousLayerBlocks();
    ++previous_layer1_y_block;
    ++previous_layer2_y_block;
    UpdateScrollVarsUpdateMap();
    layer2_y_pos = v2;
    layer1_y_pos = v3;
    reg_BG2VOFS = v4;
    reg_BG1VOFS = v5;
  }
  door_transition_frame_counter = v6 + 1;
  return v6 == 56;
}

void ConfigureMode7RotationMatrix(void) {  // 0x80B0C2
  if (irq_enable_mode7) {
    if ((nmi_frame_counter_word & 7) == 0) {
      reg_M7B = kSinCosTable8bit_Sext[((uint8)mode7_rotation_angle) + 64];
      reg_M7C = -reg_M7B;
      reg_M7A = kSinCosTable8bit_Sext[((uint8)(mode7_rotation_angle + 64)) + 64];
      reg_M7D = reg_M7A;
      ++mode7_rotation_angle;
    }
  }
}

static uint32 decompress_src;

static uint8 DecompNextByte() {
  uint8 b = *RomPtr(decompress_src);
  if ((decompress_src++ & 0xffff) == 0xffff)
    decompress_src += 0x8000;
  return b;
}

void DecompressToMem(uint32 src, uint8 *decompress_dst) {  // 0x80B119
  decompress_src = src;

  int src_pos, dst_pos = 0;
  while (1) {
    int len;
    uint8 cmd, b;
    b = DecompNextByte();
    if (b == 0xFF)
      break;
    if ((b & 0xE0) == 0xE0) {
      cmd = (8 * b) & 0xE0;
      len = ((b & 3) << 8 | DecompNextByte()) + 1;
    } else {
      cmd = b & 0xE0;
      len = (b & 0x1F) + 1;
    }
    if (cmd & 0x80) {
      uint8 want_xor = cmd & 0x20 ? 0xff : 0;
      if (cmd >= 0xC0) {
        src_pos = dst_pos - DecompNextByte();
      } else {
        src_pos = DecompNextByte();
        src_pos += DecompNextByte() * 256;
      }
      do {
        b = decompress_dst[src_pos++] ^ want_xor;
        decompress_dst[dst_pos++] = b;
      } while (--len);
    } else {
      switch (cmd) {
      case 0x20:
        b = DecompNextByte();
        do {
          decompress_dst[dst_pos++] = b;
        } while (--len);
        break;
      case 0x40: {
        b = DecompNextByte();
        uint8 b2 = DecompNextByte();
        do {
          decompress_dst[dst_pos++] = b;
          if (!--len)
            break;
          decompress_dst[dst_pos++] = b2;
        } while (--len);
        break;
      }
      case 0x60:
        b = DecompNextByte();
        do {
          decompress_dst[dst_pos++] = b++;
        } while (--len);
        break;
      default:
        do {
          b = DecompNextByte();
          decompress_dst[dst_pos++] = b;
        } while (--len);
        break;
      }
    }
  }
}

static uint8 ReadPpuByte(uint16 addr) {
  WriteRegWord(VMADDL, addr >> 1);
  ReadRegWord(RDVRAML);  // latch
  uint16 data = ReadRegWord(RDVRAML);
  return (addr & 1) ? GET_HIBYTE(data) : data;
}

void DecompressToVRAM(uint32 src, uint16 dst_addr) {  // 0x80B271
  decompress_src = src;
  int src_pos, dst_pos = dst_addr;
  while (1) {
    int len;
    uint8 b = DecompNextByte(), cmd;
    if (b == 0xFF)
      break;
    if ((b & 0xE0) == 0xE0) {
      cmd = (8 * b) & 0xE0;
      len = ((b & 3) << 8 | DecompNextByte()) + 1;
    } else {
      cmd = b & 0xE0;
      len = (b & 0x1F) + 1;
    }
    if (cmd & 0x80) {
      uint8 want_xor = cmd & 0x20 ? 0xff : 0;
      if (cmd >= 0xC0) {
        src_pos = dst_pos - DecompNextByte();
      } else {
        src_pos = DecompNextByte();
        src_pos += DecompNextByte() * 256;
        src_pos += dst_addr;
      }
      do {
        b = ReadPpuByte(src_pos++) ^ want_xor;
        WriteRegWord(VMADDL, dst_pos >> 1);
        WriteReg(VMDATAL + (dst_pos++ & 1), b);
      } while (--len);
    } else {
      switch (cmd) {
      case 0x20:
        b = DecompNextByte();
        do {
          WriteReg(VMDATAL + (dst_pos++ & 1), b);
        } while (--len);
        break;
      case 0x40: {
        b = DecompNextByte();
        uint8 b2 = DecompNextByte();
        do {
          WriteReg(VMDATAL + (dst_pos++ & 1), b);
          if (!--len)
            break;
          WriteReg(VMDATAL + (dst_pos++ & 1), b2);
        } while (--len);
        break;
      }
      case 0x60:
        b = DecompNextByte();
        do {
          WriteReg(VMDATAL + (dst_pos++ & 1), b++);
        } while (--len);
        break;
      default:
        do {
          b = DecompNextByte();
          WriteReg(VMDATAL + (dst_pos++ & 1), b++);
        } while (--len);
        break;
      }
    }
  }
}


void LoadFromLoadStation(void) {  // 0x80C437
  save_station_lockout_flag = 1;
  const LoadStationList *L = (LoadStationList *)RomPtr_80(kLoadStationLists[area_index] + 14 * load_station_index);

  room_ptr = L->room_ptr_;
  door_def_ptr = L->door_ptr;
//  door_bts = v0[2];
  bg1_x_offset = layer1_x_pos = L->screen_x_pos;
  bg1_y_offset = layer1_y_pos = L->screen_y_pos;
  samus_y_pos = layer1_y_pos + L->samus_y_offset;
  samus_prev_y_pos = samus_y_pos;
  samus_x_pos = layer1_x_pos + 128 + L->samus_x_offset;
  samus_prev_x_pos = samus_x_pos;
  reg_BG1HOFS = 0;
  reg_BG1VOFS = 0;
  LOBYTE(area_index) = get_RoomDefHeader(room_ptr)->area_index_;
  LOBYTE(debug_disable_minimap) = 0;
}


void SetElevatorsAsUsed(void) {  // 0x80CD07
  const uint8 *v0 = RomPtr_80(off_80CD46[area_index] + 4 * ((elevator_door_properties_orientation & 0xF) - 1));
  used_save_stations_and_elevators[v0[0]] |= v0[1];
  used_save_stations_and_elevators[v0[2]] |= v0[3];
}
