auto PPU::latch_counters() -> void {
  regs.hcounter = cpu.hdot();
  regs.vcounter = cpu.vcounter();
  regs.counters_latched = true;
}

auto PPU::interlace() const -> bool { return display.interlace; }
auto PPU::overscan() const -> bool { return display.overscan; }
auto PPU::hires() const -> bool { return regs.pseudo_hires || regs.bgmode == 5 || regs.bgmode == 6; }

auto PPU::get_vram_addr() -> uint16 {
  uint16 addr = regs.vram_addr;
  switch(regs.vram_mapping) {
  case 0: break;
  case 1: addr = (addr & 0xff00) | ((addr & 0x001f) << 3) | ((addr >> 5) & 7); break;
  case 2: addr = (addr & 0xfe00) | ((addr & 0x003f) << 3) | ((addr >> 6) & 7); break;
  case 3: addr = (addr & 0xfc00) | ((addr & 0x007f) << 3) | ((addr >> 7) & 7); break;
  }
  return (addr << 1);
}

auto PPU::vram_read(uint addr) -> uint8 {
  if(regs.display_disable) return vram[addr];
  if(cpu.vcounter() >= display.height) return vram[addr];
  return 0x00;
}

auto PPU::vram_write(uint addr, uint8 data) -> void {
  if(regs.display_disable || cpu.vcounter() >= display.height) {
    vram[addr] = data;
    cache.tilevalid[0][addr >> 4] = false;
    cache.tilevalid[1][addr >> 5] = false;
    cache.tilevalid[2][addr >> 6] = false;
    return;
  }
}

auto PPU::oam_read(uint addr) -> uint8 {
  if(addr & 0x0200) addr &= 0x021f;
  if(regs.display_disable) return oam[addr];
  if(cpu.vcounter() >= display.height) return oam[addr];
  return oam[0x0218];
}

auto PPU::oam_write(uint addr, uint8 data) -> void {
  if(addr & 0x0200) addr &= 0x021f;
  if(!regs.display_disable && cpu.vcounter() < display.height) addr = 0x0218;
  oam[addr] = data;
  sprite.update_list(addr, data);
}

auto PPU::cgram_read(uint addr) -> uint8 {
  return cgram[addr];
}

auto PPU::cgram_write(uint addr, uint8 data) -> void {
  cgram[addr] = data;
}

auto PPU::mmio_update_video_mode() -> void {
  switch(regs.bgmode) {
  case 0: {
    bg1.regs.mode = Background::Mode::BPP2; bg1.regs.priority0 = 8; bg1.regs.priority1 = 11;
    bg2.regs.mode = Background::Mode::BPP2; bg2.regs.priority0 = 7; bg2.regs.priority1 = 10;
    bg3.regs.mode = Background::Mode::BPP2; bg3.regs.priority0 = 2; bg3.regs.priority1 =  5;
    bg4.regs.mode = Background::Mode::BPP2; bg4.regs.priority0 = 1; bg4.regs.priority1 =  4;
    sprite.regs.priority0 = 3; sprite.regs.priority1 = 6; sprite.regs.priority2 = 9; sprite.regs.priority3 = 12;
  } break;

  case 1: {
    bg1.regs.mode = Background::Mode::BPP4;
    bg2.regs.mode = Background::Mode::BPP4;
    bg3.regs.mode = Background::Mode::BPP2;
    bg4.regs.mode = Background::Mode::Inactive;
    if(regs.bg3_priority) {
      bg1.regs.priority0 = 5; bg1.regs.priority1 =  8;
      bg2.regs.priority0 = 4; bg2.regs.priority1 =  7;
      bg3.regs.priority0 = 1; bg3.regs.priority1 = 10;
      sprite.regs.priority0 = 2; sprite.regs.priority1 = 3; sprite.regs.priority2 = 6; sprite.regs.priority3 = 9;
    } else {
      bg1.regs.priority0 = 6; bg1.regs.priority1 =  9;
      bg2.regs.priority0 = 5; bg2.regs.priority1 =  8;
      bg3.regs.priority0 = 1; bg3.regs.priority1 =  3;
      sprite.regs.priority0 = 2; sprite.regs.priority1 = 4; sprite.regs.priority2 = 7; sprite.regs.priority3 = 10;
    }
  } break;

  case 2: {
    bg1.regs.mode = Background::Mode::BPP4;
    bg2.regs.mode = Background::Mode::BPP4;
    bg3.regs.mode = Background::Mode::Inactive;
    bg4.regs.mode = Background::Mode::Inactive;
    bg1.regs.priority0 = 3; bg1.regs.priority1 = 7;
    bg2.regs.priority0 = 1; bg2.regs.priority1 = 5;
    sprite.regs.priority0 = 2; sprite.regs.priority1 = 4; sprite.regs.priority2 = 6; sprite.regs.priority3 = 8;
  } break;

  case 3: {
    bg1.regs.mode = Background::Mode::BPP8;
    bg2.regs.mode = Background::Mode::BPP4;
    bg3.regs.mode = Background::Mode::Inactive;
    bg4.regs.mode = Background::Mode::Inactive;
    bg1.regs.priority0 = 3; bg1.regs.priority1 = 7;
    bg2.regs.priority0 = 1; bg2.regs.priority1 = 5;
    sprite.regs.priority0 = 2; sprite.regs.priority1 = 4; sprite.regs.priority2 = 6; sprite.regs.priority3 = 8;
  } break;

  case 4: {
    bg1.regs.mode = Background::Mode::BPP8;
    bg2.regs.mode = Background::Mode::BPP2;
    bg3.regs.mode = Background::Mode::Inactive;
    bg4.regs.mode = Background::Mode::Inactive;
    bg1.regs.priority0 = 3; bg1.regs.priority1 = 7;
    bg2.regs.priority0 = 1; bg2.regs.priority1 = 5;
    sprite.regs.priority0 = 2; sprite.regs.priority1 = 4; sprite.regs.priority2 = 6; sprite.regs.priority3 = 8;
  } break;

  case 5: {
    bg1.regs.mode = Background::Mode::BPP4;
    bg2.regs.mode = Background::Mode::BPP2;
    bg3.regs.mode = Background::Mode::Inactive;
    bg4.regs.mode = Background::Mode::Inactive;
    bg1.regs.priority0 = 3; bg1.regs.priority1 = 7;
    bg2.regs.priority0 = 1; bg2.regs.priority1 = 5;
    sprite.regs.priority0 = 2; sprite.regs.priority1 = 4; sprite.regs.priority2 = 6; sprite.regs.priority3 = 8;
  } break;

  case 6: {
    bg1.regs.mode = Background::Mode::BPP4;
    bg2.regs.mode = Background::Mode::Inactive;
    bg3.regs.mode = Background::Mode::Inactive;
    bg4.regs.mode = Background::Mode::Inactive;
    bg1.regs.priority0 = 2; bg1.regs.priority1 = 5;
    sprite.regs.priority0 = 1; sprite.regs.priority1 = 3; sprite.regs.priority2 = 4; sprite.regs.priority3 = 6;
  } break;

  case 7: {
    if(regs.mode7_extbg == false) {
      bg1.regs.mode = Background::Mode::Mode7;
      bg2.regs.mode = Background::Mode::Inactive;
      bg3.regs.mode = Background::Mode::Inactive;
      bg4.regs.mode = Background::Mode::Inactive;
      bg1.regs.priority0 = 2; bg1.regs.priority1 = 2;
      sprite.regs.priority0 = 1; sprite.regs.priority1 = 3; sprite.regs.priority2 = 4; sprite.regs.priority3 = 5;
    } else {
      bg1.regs.mode = Background::Mode::Mode7;
      bg2.regs.mode = Background::Mode::Mode7;
      bg3.regs.mode = Background::Mode::Inactive;
      bg4.regs.mode = Background::Mode::Inactive;
      bg1.regs.priority0 = 3; bg1.regs.priority1 = 3;
      bg2.regs.priority0 = 1; bg2.regs.priority1 = 5;
      sprite.regs.priority0 = 2; sprite.regs.priority1 = 4; sprite.regs.priority2 = 6; sprite.regs.priority3 = 7;
    }
  } break;
  }
}

auto PPU::mmio_read(uint addr, uint8 data) -> uint8 {
  cpu.synchronizePPU();

  switch(addr & 0xffff) {
  case 0x2104: case 0x2105: case 0x2106: case 0x2108: case 0x2109: case 0x210a:
  case 0x2114: case 0x2115: case 0x2116: case 0x2118: case 0x2119: case 0x211a:
  case 0x2124: case 0x2125: case 0x2126: case 0x2128: case 0x2129: case 0x212a: {
    return regs.ppu1_mdr;
  }

  case 0x2134: {  //MPYL
    unsigned result = ((int16)regs.m7a * (int8)(regs.m7b >> 8));
    regs.ppu1_mdr = result >>  0;
    return regs.ppu1_mdr;
  }

  case 0x2135: {  //MPYM
    unsigned result = ((int16)regs.m7a * (int8)(regs.m7b >> 8));
    regs.ppu1_mdr = result >>  8;
    return regs.ppu1_mdr;
  }

  case 0x2136: {  //MPYH
    unsigned result = ((int16)regs.m7a * (int8)(regs.m7b >> 8));
    regs.ppu1_mdr = result >> 16;
    return regs.ppu1_mdr;
  }

  case 0x2137: {  //SLHV
    if(cpu.pio() & 0x80) latch_counters();
    return data;
  }

  case 0x2138: {  //OAMDATAREAD
    regs.ppu1_mdr = oam_read(regs.oam_addr);
    regs.oam_addr = (regs.oam_addr + 1) & 0x03ff;
    sprite.set_first();
    return regs.ppu1_mdr;
  }

  case 0x2139: {  //VMDATALREAD
    regs.ppu1_mdr = regs.vram_readbuffer >> 0;
    if(regs.vram_incmode == 0) {
      uint16 addr = get_vram_addr();
      regs.vram_readbuffer  = vram_read(addr + 0) << 0;
      regs.vram_readbuffer |= vram_read(addr + 1) << 8;
      regs.vram_addr += regs.vram_incsize;
    }
    return regs.ppu1_mdr;
  }

  case 0x213a: {  //VMDATAHREAD
    regs.ppu1_mdr = regs.vram_readbuffer >> 8;
    if(regs.vram_incmode == 1) {
      uint16 addr = get_vram_addr();
      regs.vram_readbuffer  = vram_read(addr + 0) << 0;
      regs.vram_readbuffer |= vram_read(addr + 1) << 8;
      regs.vram_addr += regs.vram_incsize;
    }
    return regs.ppu1_mdr;
  }

  case 0x213b: {  //CGDATAREAD
    if((regs.cgram_addr & 1) == 0) {
      regs.ppu2_mdr = cgram_read(regs.cgram_addr);
    } else {
      regs.ppu2_mdr = (regs.ppu2_mdr & 0x80) | (cgram_read(regs.cgram_addr) & 0x7f);
    }
    regs.cgram_addr = (regs.cgram_addr + 1) & 0x01ff;
    return regs.ppu2_mdr;
  }

  case 0x213c: {  //OPHCT
    if(regs.latch_hcounter == 0) {
      regs.ppu2_mdr = regs.hcounter & 0xff;
    } else {
      regs.ppu2_mdr = (regs.ppu2_mdr & 0xfe) | (regs.hcounter >> 8);
    }
    regs.latch_hcounter ^= 1;
    return regs.ppu2_mdr;
  }

  case 0x213d: {  //OPVCT
    if(regs.latch_vcounter == 0) {
      regs.ppu2_mdr = regs.vcounter & 0xff;
    } else {
      regs.ppu2_mdr = (regs.ppu2_mdr & 0xfe) | (regs.vcounter >> 8);
    }
    regs.latch_vcounter ^= 1;
    return regs.ppu2_mdr;
  }

  case 0x213e: {  //STAT77
    regs.ppu1_mdr &= 0x10;
    regs.ppu1_mdr |= sprite.regs.time_over << 7;
    regs.ppu1_mdr |= sprite.regs.range_over << 6;
    regs.ppu1_mdr |= 0x01;  //version
    return regs.ppu1_mdr;
  }

  case 0x213f: {  //STAT78
    regs.latch_hcounter = 0;
    regs.latch_vcounter = 0;

    regs.ppu2_mdr &= 0x20;
    regs.ppu2_mdr |= cpu.field() << 7;
    if((cpu.pio() & 0x80) == 0) {
      regs.ppu2_mdr |= 0x40;
    } else if(regs.counters_latched) {
      regs.ppu2_mdr |= 0x40;
      regs.counters_latched = false;
    }
    regs.ppu2_mdr |= (system.region() == System::Region::NTSC ? 0 : 1) << 4;
    regs.ppu2_mdr |= 0x03;  //version
    return regs.ppu2_mdr;
  }
  }

  return data;
}

auto PPU::mmio_write(uint addr, uint8 data) -> void {
  cpu.synchronizePPU();

  switch(addr & 0xffff) {
  case 0x2100: {  //INIDISP
    if(regs.display_disable && cpu.vcounter() == display.height) sprite.address_reset();
    regs.display_disable = data & 0x80;
    regs.display_brightness = data & 0x0f;
    return;
  }

  case 0x2101: {  //OBSEL
    sprite.regs.base_size = (data >> 5) & 7;
    sprite.regs.nameselect = (data >> 3) & 3;
    sprite.regs.tiledata_addr = (data & 3) << 14;
    sprite.list_valid = false;
    return;
  }

  case 0x2102: {  //OAMADDL
    regs.oam_baseaddr = (regs.oam_baseaddr & 0x0100) | (data << 0);
    sprite.address_reset();
    return;
  }

  case 0x2103: {  //OAMADDH
    regs.oam_priority = data & 0x80;
    regs.oam_baseaddr = ((data & 1) << 8) | (regs.oam_baseaddr & 0x00ff);
    sprite.address_reset();
    return;
  }

  case 0x2104: {  //OAMDATA
    if((regs.oam_addr & 1) == 0) regs.oam_latchdata = data;
    if(regs.oam_addr & 0x0200) {
      oam_write(regs.oam_addr, data);
    } else if((regs.oam_addr & 1) == 1) {
      oam_write((regs.oam_addr & ~1) + 0, regs.oam_latchdata);
      oam_write((regs.oam_addr & ~1) + 1, data);
    }
    regs.oam_addr = (regs.oam_addr + 1) & 0x03ff;
    sprite.set_first();
    return;
  }

  case 0x2105: {  //BGMODE
    bg4.regs.tile_size = data & 0x80;
    bg3.regs.tile_size = data & 0x40;
    bg2.regs.tile_size = data & 0x20;
    bg1.regs.tile_size = data & 0x10;
    regs.bg3_priority = data & 0x08;
    regs.bgmode = data & 0x07;
    mmio_update_video_mode();
    return;
  }

  case 0x2106: {  //MOSAIC
    unsigned mosaic_size = (data >> 4) & 15;
    bg4.regs.mosaic = (data & 0x08 ? mosaic_size : 0);
    bg3.regs.mosaic = (data & 0x04 ? mosaic_size : 0);
    bg2.regs.mosaic = (data & 0x02 ? mosaic_size : 0);
    bg1.regs.mosaic = (data & 0x01 ? mosaic_size : 0);
    return;
  }

  case 0x2107: {  //BG1SC
    bg1.regs.screen_addr = (data & 0x7c) << 9;
    bg1.regs.screen_size = data & 3;
    return;
  }

  case 0x2108: {  //BG2SC
    bg2.regs.screen_addr = (data & 0x7c) << 9;
    bg2.regs.screen_size = data & 3;
    return;
  }

  case 0x2109: {  //BG3SC
    bg3.regs.screen_addr = (data & 0x7c) << 9;
    bg3.regs.screen_size = data & 3;
    return;
  }

  case 0x210a: {  //BG4SC
    bg4.regs.screen_addr = (data & 0x7c) << 9;
    bg4.regs.screen_size = data & 3;
    return;
  }

  case 0x210b: {  //BG12NBA
    bg1.regs.tiledata_addr = (data & 0x07) << 13;
    bg2.regs.tiledata_addr = (data & 0x70) <<  9;
    return;
  }

  case 0x210c: {  //BG34NBA
    bg3.regs.tiledata_addr = (data & 0x07) << 13;
    bg4.regs.tiledata_addr = (data & 0x70) <<  9;
    return;
  }

  case 0x210d: {  //BG1HOFS
    regs.mode7_hoffset = (data << 8) | regs.mode7_latchdata;
    regs.mode7_latchdata = data;

    bg1.regs.hoffset = (data << 8) | (regs.bgofs_latchdata & ~7) | ((bg1.regs.hoffset >> 8) & 7);
    regs.bgofs_latchdata = data;
    return;
  }

  case 0x210e: {  //BG1VOFS
    regs.mode7_voffset = (data << 8) | regs.mode7_latchdata;
    regs.mode7_latchdata = data;

    bg1.regs.voffset = (data << 8) | regs.bgofs_latchdata;
    regs.bgofs_latchdata = data;
    return;
  }

  case 0x210f: {  //BG2HOFS
    bg2.regs.hoffset = (data << 8) | (regs.bgofs_latchdata & ~7) | ((bg2.regs.hoffset >> 8) & 7);
    regs.bgofs_latchdata = data;
    return;
  }

  case 0x2110: {  //BG2VOFS
    bg2.regs.voffset = (data << 8) | regs.bgofs_latchdata;
    regs.bgofs_latchdata = data;
    return;
  }

  case 0x2111: {  //BG3HOFS
    bg3.regs.hoffset = (data << 8) | (regs.bgofs_latchdata & ~7) | ((bg3.regs.hoffset >> 8) & 7);
    regs.bgofs_latchdata = data;
    return;
  }

  case 0x2112: {  //BG3VOFS
    bg3.regs.voffset = (data << 8) | regs.bgofs_latchdata;
    regs.bgofs_latchdata = data;
    return;
  }

  case 0x2113: {  //BG4HOFS
    bg4.regs.hoffset = (data << 8) | (regs.bgofs_latchdata & ~7) | ((bg4.regs.hoffset >> 8) & 7);
    regs.bgofs_latchdata = data;
    return;
  }

  case 0x2114: {  //BG4VOFS
    bg4.regs.voffset = (data << 8) | regs.bgofs_latchdata;
    regs.bgofs_latchdata = data;
    return;
  }

  case 0x2115: {  //VMAIN
    regs.vram_incmode = data & 0x80;
    regs.vram_mapping = (data >> 2) & 3;
    switch(data & 3) {
    case 0: regs.vram_incsize =   1; break;
    case 1: regs.vram_incsize =  32; break;
    case 2: regs.vram_incsize = 128; break;
    case 3: regs.vram_incsize = 128; break;
    }
    return;
  }

  case 0x2116: {  //VMADDL
    regs.vram_addr = (regs.vram_addr & 0xff00) | (data << 0);
    uint16 addr = get_vram_addr();
    regs.vram_readbuffer  = vram_read(addr + 0) << 0;
    regs.vram_readbuffer |= vram_read(addr + 1) << 8;
    return;
  }

  case 0x2117: {  //VMADDH
    regs.vram_addr = (data << 8) | (regs.vram_addr & 0x00ff);
    uint16 addr = get_vram_addr();
    regs.vram_readbuffer  = vram_read(addr + 0) << 0;
    regs.vram_readbuffer |= vram_read(addr + 1) << 8;
    return;
  }

  case 0x2118: {  //VMDATAL
    vram_write(get_vram_addr() + 0, data);
    if(regs.vram_incmode == 0) regs.vram_addr += regs.vram_incsize;
    return;
  }

  case 0x2119: {  //VMDATAH
    vram_write(get_vram_addr() + 1, data);
    if(regs.vram_incmode == 1) regs.vram_addr += regs.vram_incsize;
    return;
  }

  case 0x211a: {  //M7SEL
    regs.mode7_repeat = (data >> 6) & 3;
    regs.mode7_vflip = data & 0x02;
    regs.mode7_hflip = data & 0x01;
    return;
  }

  case 0x211b: {  //M7A
    regs.m7a = (data << 8) | regs.mode7_latchdata;
    regs.mode7_latchdata = data;
    return;
  }

  case 0x211c: {  //M7B
    regs.m7b = (data << 8) | regs.mode7_latchdata;
    regs.mode7_latchdata = data;
    return;
  }

  case 0x211d: {  //M7C
    regs.m7c = (data << 8) | regs.mode7_latchdata;
    regs.mode7_latchdata = data;
    return;
  }

  case 0x211e: {  //M7D
    regs.m7d = (data << 8) | regs.mode7_latchdata;
    regs.mode7_latchdata = data;
    return;
  }

  case 0x211f: {  //M7X
    regs.m7x = (data << 8) | regs.mode7_latchdata;
    regs.mode7_latchdata = data;
    return;
  }

  case 0x2120: {  //M7Y
    regs.m7y = (data << 8) | regs.mode7_latchdata;
    regs.mode7_latchdata = data;
    return;
  }

  case 0x2121: {  //CGADD
    regs.cgram_addr = data << 1;
    return;
  }

  case 0x2122: {  //CGDATA
    if((regs.cgram_addr & 1) == 0) {
      regs.cgram_latchdata = data;
    } else {
      cgram_write((regs.cgram_addr & ~1) + 0, regs.cgram_latchdata);
      cgram_write((regs.cgram_addr & ~1) + 1, data & 0x7f);
    }
    regs.cgram_addr = (regs.cgram_addr + 1) & 0x01ff;
    return;
  }

  case 0x2123: {  //W12SEL
    bg2.window.two_enable = data & 0x80;
    bg2.window.two_invert = data & 0x40;
    bg2.window.one_enable = data & 0x20;
    bg2.window.one_invert = data & 0x10;
    bg1.window.two_enable = data & 0x08;
    bg1.window.two_invert = data & 0x04;
    bg1.window.one_enable = data & 0x02;
    bg1.window.one_invert = data & 0x01;
    return;
  }

  case 0x2124: {  //W34SEL
    bg4.window.two_enable = data & 0x80;
    bg4.window.two_invert = data & 0x40;
    bg4.window.one_enable = data & 0x20;
    bg4.window.one_invert = data & 0x10;
    bg3.window.two_enable = data & 0x08;
    bg3.window.two_invert = data & 0x04;
    bg3.window.one_enable = data & 0x02;
    bg3.window.one_invert = data & 0x01;
    return;
  }

  case 0x2125: {  //WOBJSEL
    screen.window.two_enable = data & 0x80;
    screen.window.two_invert = data & 0x40;
    screen.window.one_enable = data & 0x20;
    screen.window.one_invert = data & 0x10;
    sprite.window.two_enable = data & 0x08;
    sprite.window.two_invert = data & 0x04;
    sprite.window.one_enable = data & 0x02;
    sprite.window.one_invert = data & 0x01;
    return;
  }

  case 0x2126: {  //WH0
    regs.window_one_left = data;
    return;
  }

  case 0x2127: {  //WH1
    regs.window_one_right = data;
    return;
  }

  case 0x2128: {  //WH2
    regs.window_two_left = data;
    return;
  }

  case 0x2129: {  //WH3
    regs.window_two_right = data;
    return;
  }

  case 0x212a: {  //WBGLOG
    bg4.window.mask = (data >> 6) & 3;
    bg3.window.mask = (data >> 4) & 3;
    bg2.window.mask = (data >> 2) & 3;
    bg1.window.mask = (data >> 0) & 3;
    return;
  }

  case 0x212b: {  //WOBJLOG
    screen.window.mask = (data >> 2) & 3;
    sprite.window.mask = (data >> 0) & 3;
    return;
  }

  case 0x212c: {  //TM
    sprite.regs.main_enable = data & 0x10;
    bg4.regs.main_enable = data & 0x08;
    bg3.regs.main_enable = data & 0x04;
    bg2.regs.main_enable = data & 0x02;
    bg1.regs.main_enable = data & 0x01;
    return;
  }

  case 0x212d: {  //TS
    sprite.regs.sub_enable = data & 0x10;
    bg4.regs.sub_enable = data & 0x08;
    bg3.regs.sub_enable = data & 0x04;
    bg2.regs.sub_enable = data & 0x02;
    bg1.regs.sub_enable = data & 0x01;
    return;
  }

  case 0x212e: {  //TMW
    sprite.window.main_enable = data & 0x10;
    bg4.window.main_enable = data & 0x08;
    bg3.window.main_enable = data & 0x04;
    bg2.window.main_enable = data & 0x02;
    bg1.window.main_enable = data & 0x01;
    return;
  }

  case 0x212f: {  //TSW
    sprite.window.sub_enable = data & 0x10;
    bg4.window.sub_enable = data & 0x08;
    bg3.window.sub_enable = data & 0x04;
    bg2.window.sub_enable = data & 0x02;
    bg1.window.sub_enable = data & 0x01;
    return;
  }

  case 0x2130: {  //CGWSEL
    screen.window.main_mask = (data >> 6) & 3;
    screen.window.sub_mask = (data >> 4) & 3;
    screen.regs.addsub_mode = data & 0x02;
    screen.regs.direct_color = data & 0x01;
    return;
  }

  case 0x2131: {  //CGADDSUB
    screen.regs.color_mode = data & 0x80;
    screen.regs.color_halve = data & 0x40;
    screen.regs.color_enable[6] = data & 0x20;
    screen.regs.color_enable[5] = data & 0x10;
    screen.regs.color_enable[4] = data & 0x10;
    screen.regs.color_enable[3] = data & 0x08;
    screen.regs.color_enable[2] = data & 0x04;
    screen.regs.color_enable[1] = data & 0x02;
    screen.regs.color_enable[0] = data & 0x01;
    return;
  }

  case 0x2132: {  //COLDATA
    if(data & 0x80) screen.regs.color_b = data & 0x1f;
    if(data & 0x40) screen.regs.color_g = data & 0x1f;
    if(data & 0x20) screen.regs.color_r = data & 0x1f;
    screen.regs.color = (screen.regs.color_b << 10) | (screen.regs.color_g << 5) | (screen.regs.color_r << 0);
    return;
  }

  case 0x2133: {  //SETINI
    regs.mode7_extbg = data & 0x40;
    regs.pseudo_hires = data & 0x08;
    regs.overscan = data & 0x04;
    sprite.regs.interlace = data & 0x02;
    regs.interlace = data & 0x01;
    mmio_update_video_mode();
    sprite.list_valid = false;
    return;
  }
  }
}

auto PPU::mmio_reset() -> void {
  //internal
  regs.ppu1_mdr = 0;
  regs.ppu2_mdr = 0;

  regs.vram_readbuffer = 0;
  regs.oam_latchdata = 0;
  regs.cgram_latchdata = 0;
  regs.bgofs_latchdata = 0;
  regs.mode7_latchdata = 0;

  regs.counters_latched = 0;
  regs.latch_hcounter = 0;
  regs.latch_vcounter = 0;

  sprite.regs.first_sprite = 0;
  sprite.list_valid = false;

  //$2100
  regs.display_disable = true;
  regs.display_brightness = 0;

  //$2101
  sprite.regs.base_size = 0;
  sprite.regs.nameselect = 0;
  sprite.regs.tiledata_addr = 0;

  //$2102-$2103
  regs.oam_baseaddr = 0;
  regs.oam_addr = 0;
  regs.oam_priority = 0;

  //$2105
  bg4.regs.tile_size = 0;
  bg3.regs.tile_size = 0;
  bg2.regs.tile_size = 0;
  bg1.regs.tile_size = 0;
  regs.bg3_priority = 0;
  regs.bgmode = 0;

  //$2106
  bg4.regs.mosaic = 0;
  bg3.regs.mosaic = 0;
  bg2.regs.mosaic = 0;
  bg1.regs.mosaic = 0;

  //$2107-$210a
  bg1.regs.screen_addr = 0;
  bg1.regs.screen_size = 0;
  bg2.regs.screen_addr = 0;
  bg2.regs.screen_size = 0;
  bg3.regs.screen_addr = 0;
  bg3.regs.screen_size = 0;
  bg4.regs.screen_addr = 0;
  bg4.regs.screen_size = 0;

  //$210b-$210c
  bg1.regs.tiledata_addr = 0;
  bg2.regs.tiledata_addr = 0;
  bg3.regs.tiledata_addr = 0;
  bg4.regs.tiledata_addr = 0;

  //$210d-$2114
  regs.mode7_hoffset = 0;
  regs.mode7_voffset = 0;
  bg1.regs.hoffset = 0;
  bg1.regs.voffset = 0;
  bg2.regs.hoffset = 0;
  bg2.regs.voffset = 0;
  bg3.regs.hoffset = 0;
  bg3.regs.voffset = 0;
  bg4.regs.hoffset = 0;
  bg4.regs.voffset = 0;

  //$2115
  regs.vram_incmode = 0;
  regs.vram_mapping = 0;
  regs.vram_incsize = 1;

  //$2116-$2117
  regs.vram_addr = 0;

  //$211a
  regs.mode7_repeat = 0;
  regs.mode7_vflip = 0;
  regs.mode7_hflip = 0;

  //$211b-$2120
  regs.m7a = 0;
  regs.m7b = 0;
  regs.m7c = 0;
  regs.m7d = 0;
  regs.m7x = 0;
  regs.m7y = 0;

  //$2121
  regs.cgram_addr = 0;

  //$2123-$2125
  bg1.window.one_enable = 0;
  bg1.window.one_invert = 0;
  bg1.window.two_enable = 0;
  bg1.window.two_invert = 0;

  bg2.window.one_enable = 0;
  bg2.window.one_invert = 0;
  bg2.window.two_enable = 0;
  bg2.window.two_invert = 0;

  bg3.window.one_enable = 0;
  bg3.window.one_invert = 0;
  bg3.window.two_enable = 0;
  bg3.window.two_invert = 0;

  bg4.window.one_enable = 0;
  bg4.window.one_invert = 0;
  bg4.window.two_enable = 0;
  bg4.window.two_invert = 0;

  sprite.window.one_enable = 0;
  sprite.window.one_invert = 0;
  sprite.window.two_enable = 0;
  sprite.window.two_invert = 0;

  screen.window.one_enable = 0;
  screen.window.one_invert = 0;
  screen.window.two_enable = 0;
  screen.window.two_invert = 0;

  //$2126-$2129
  regs.window_one_left = 0;
  regs.window_one_right = 0;
  regs.window_two_left = 0;
  regs.window_two_right = 0;

  //$212a-$212b
  bg1.window.mask = 0;
  bg2.window.mask = 0;
  bg3.window.mask = 0;
  bg4.window.mask = 0;
  sprite.window.mask = 0;
  screen.window.mask = 0;

  //$212c
  bg1.regs.main_enable = 0;
  bg2.regs.main_enable = 0;
  bg3.regs.main_enable = 0;
  bg4.regs.main_enable = 0;
  sprite.regs.main_enable = 0;

  //$212d
  bg1.regs.sub_enable = 0;
  bg2.regs.sub_enable = 0;
  bg3.regs.sub_enable = 0;
  bg4.regs.sub_enable = 0;
  sprite.regs.sub_enable = 0;

  //$212e
  bg1.window.main_enable = 0;
  bg2.window.main_enable = 0;
  bg3.window.main_enable = 0;
  bg4.window.main_enable = 0;
  sprite.window.main_enable = 0;

  //$212f
  bg1.window.sub_enable = 0;
  bg2.window.sub_enable = 0;
  bg3.window.sub_enable = 0;
  bg4.window.sub_enable = 0;
  sprite.window.sub_enable = 0;

  //$2130
  screen.window.main_mask = 0;
  screen.window.sub_mask = 0;
  screen.regs.addsub_mode = 0;
  screen.regs.direct_color = 0;

  //$2131
  screen.regs.color_mode = 0;
  screen.regs.color_halve = 0;
  screen.regs.color_enable[6] = 0;
  screen.regs.color_enable[5] = 0;
  screen.regs.color_enable[4] = 0;
  screen.regs.color_enable[3] = 0;
  screen.regs.color_enable[2] = 0;
  screen.regs.color_enable[1] = 0;
  screen.regs.color_enable[0] = 0;

  //$2132
  screen.regs.color_b = 0;
  screen.regs.color_g = 0;
  screen.regs.color_r = 0;
  screen.regs.color = 0;

  //$2133
  regs.mode7_extbg = 0;
  regs.pseudo_hires = 0;
  regs.overscan = 0;
  sprite.regs.interlace = 0;
  regs.interlace = 0;

  //$213e
  sprite.regs.time_over = 0;
  sprite.regs.range_over = 0;

  mmio_update_video_mode();
}
