auto CPU::read(uint8 bank, uint13 addr) -> uint8 {
  auto data = read_(bank, addr);
  if(auto result = cheat.find(bank << 13 | addr, data)) data = result();
  return data;
}

auto CPU::read_(uint8 bank, uint13 addr) -> uint8 {
  //$00-7f  HuCard
  if(!bank.bit(7)) {
    return cartridge.read(bank << 13 | addr);
  }

  //$f7  BRAM
  if(bank == 0xf7) {
    return bram[addr.bits(0,10)];
  }

  //$f8-fb  RAM
  if(bank >= 0xf8 && bank <= 0xfb) {
    if(Model::PCEngine()) return ram[addr];
    if(Model::SuperGrafx()) return ram[bank.bits(0,1) << 13 | addr];
  }

  //$ff  Hardware
  if(bank == 0xff) {
    //$0000-03ff  VDC or VPC
    if((addr & 0x1c00) == 0x0000) {
      HuC6280::io();  //penalty cycle
      if(Model::PCEngine()) return vdc0.read(addr);
      if(Model::SuperGrafx()) return vpc.read(addr);
    }

    //$0400-07ff  VCE
    if((addr & 0x1c00) == 0x0400) {
      HuC6280::io();  //penalty cycle
      return vce.read(addr);
    }

    //$0800-0bff  PSG
    if((addr & 0x1c00) == 0x0800) {
      return io.mdr;
    }

    //$0c00-0fff  Timer
    if((addr & 0x1c00) == 0x0c00) {
      return (io.mdr & 0x80) | timer.value;
    }

    //$1000-13ff  I/O
    if((addr & 0x1c00) == 0x1000) {
      //note 1: Turbografx-16 games check this bit for region protection.
      //yet PC Engine games do not. since we cannot tell the games apart,
      //it's more compatible to always identify as a Turbografx-16 system.
      //note 2: we state that the CD-ROM drive is present.
      //this is so games can use its backup RAM for save data.
      return (
        controllerPort.device->readData() << 0
      | 1 << 4
      | 1 << 5
      | 0 << 6  //device (0 = Turbografx-16; 1 = PC Engine)
      | 0 << 7  //add-on (0 = CD-ROM; 1 = nothing)
      );
    }

    //$1400-17ff  IRQ
    if((addr & 0x1c00) == 0x1400) {
      if(addr.bits(0,1) == 0) {
        return io.mdr;
      }

      if(addr.bits(0,1) == 1) {
        return io.mdr;
      }

      if(addr.bits(0,1) == 2) {
        return (
          irq.disableExternal << 0
        | irq.disableVDC << 1
        | irq.disableTimer << 2
        | (io.mdr & 0xf8)
        );
      }

      if(addr.bits(0,1) == 3) {
        bool pendingExternal = 0;
        bool pendingVDC = vdc0.irqLine() | vdc1.irqLine();
        bool pendingTimer = timer.irqLine();
        return (
          pendingExternal << 0
        | pendingVDC << 1
        | pendingTimer << 2
        | (io.mdr & 0xf8)
        );
      }
    }

    //$1800-1bff  CD-ROM
    if((addr & 0x1c00) == 0x1800) {
      return 0xff;
    }

    //$1c00-1fff  unmapped
    if((addr & 0x1c00) == 0x1c00) {
      return 0xff;
    }
  }

  return 0xff;
}

auto CPU::write(uint8 bank, uint13 addr, uint8 data) -> void {
  //$00-7f  HuCard
  if(!bank.bit(7)) {
    return cartridge.write(bank << 13 | addr, data);
  }

  //$f7  BRAM
  if(bank == 0xf7) {
    bram[addr.bits(0,10)] = data;
    return;
  }

  //$f8-fb  RAM
  if(bank >= 0xf8 && bank <= 0xfb) {
    if(Model::PCEngine()) ram[addr] = data;
    if(Model::SuperGrafx()) ram[bank.bits(0,1) << 13 | addr] = data;
    return;
  }

  //$1fe000-1fffff  Hardware
  if(bank == 0xff) {
    //$0000-03ff  VDC or VPC
    if((addr & 0x1c00) == 0x0000) {
      HuC6280::io();  //penalty cycle
      if(Model::PCEngine()) return vdc0.write(addr, data);
      if(Model::SuperGrafx()) return vpc.write(addr, data);
    }

    //$0400-07ff  VCE
    if((addr & 0x1c00) == 0x0400) {
      HuC6280::io();  //penalty cycle
      return vce.write(addr, data);
    }

    //$0800-0bff  PSG
    if((addr & 0x1c00) == 0x0800) {
      return psg.write(addr, io.mdr = data);
    }

    //$0c00-0fff  Timer
    if((addr & 0x1c00) == 0x0c00) {
      io.mdr = data;
      if(!addr.bit(0)) {
        timer.latch = data.bits(0,6);
      } else {
        timer.enable = data.bit(0);
        if(timer.enable) timer.start();
      }
      return;
    }

    //$1000-13ff  I/O
    if((addr & 0x1c00) == 0x1000) {
      io.mdr = data;
      controllerPort.device->writeData(data.bits(0,1));
      return;
    }

    //$1400-17ff  IRQ
    if((addr & 0x1c00) == 0x1400) {
      io.mdr = data;
      if(addr.bits(0,1) == 2) {
        irq.disableExternal = data.bit(0);
        irq.disableVDC = data.bit(1);
        irq.disableTimer = data.bit(2);
        return;
      }

      if(addr.bits(0,1) == 3) {
        timer.line = 0;
        return;
      }
    }

    //$1800-1bff  CD-ROM
    if((addr & 0x1c00) == 0x1800) {
      return;
    }

    //$1c00-1fff  unmapped
    if((addr & 0x1c00) == 0x1c00) {
      return;
    }
  }
}

//ST0, ST1, ST2
auto CPU::store(uint2 addr, uint8 data) -> void {
  HuC6280::io();  //penalty cycle
  if(addr) addr++;  //0,1,2 => 0,2,3
  if(Model::PCEngine()) vdc0.write(addr, data);
  if(Model::SuperGrafx()) vpc.store(addr, data);
}
