auto Cartridge::loadCartridge(Markup::Node node) -> void {
  information.title.cartridge = node["information/title"].text();
  auto board = node["board"];
  information.region = board["region"].text() == "pal" ? Region::PAL : Region::NTSC;

  if(board["mcc"] || board["bsmemory"]) {
    if(auto pathID = platform->load(ID::BSMemory, "BS Memory", "bs")) {
      bsmemory.pathID = pathID();
      loadBSMemory();
    }
  }
  if(board["sufamiturbo"]) {
    if(auto pathID = platform->load(ID::SufamiTurboA, "Sufami Turbo", "st")) {
      sufamiturboA.pathID = pathID();
      loadSufamiTurboA();
    }
  }

  if(auto node = board["rom"]) loadROM(node);
  if(auto node = board["ram"]) loadRAM(node);
  if(auto node = board["icd2"]) loadICD2(node);
  if(auto node = board["mcc"]) loadMCC(node);
  if(auto node = board["bsmemory"]) loadBSMemoryPack(node);
  if(auto node = board.find("sufamiturbo")) if(node(0)) loadSufamiTurbo(node(0), 0);
  if(auto node = board.find("sufamiturbo")) if(node(1)) loadSufamiTurbo(node(1), 1);
  if(auto node = board["nss"]) loadNSS(node);
  if(auto node = board["event"]) loadEvent(node);
  if(auto node = board["sa1"]) loadSA1(node);
  if(auto node = board["superfx"]) loadSuperFX(node);
  if(auto node = board["armdsp"]) loadARMDSP(node);
  if(auto node = board["hitachidsp"]) loadHitachiDSP(node, node["information/board"].text().match("2DC*") ? 2 : 1);
  if(auto node = board["necdsp"]) loadNECDSP(node);
  if(auto node = board["epsonrtc"]) loadEpsonRTC(node);
  if(auto node = board["sharprtc"]) loadSharpRTC(node);
  if(auto node = board["spc7110"]) loadSPC7110(node);
  if(auto node = board["sdd1"]) loadSDD1(node);
  if(auto node = board["obc1"]) loadOBC1(node);
  if(auto node = board["msu1"]) loadMSU1(node);
}

auto Cartridge::loadGameBoy(Markup::Node node) -> void {
}

auto Cartridge::loadBSMemory(Markup::Node node) -> void {
  information.title.bsMemory = node["information/title"].text();
  bsmemory.readonly = (node["board/rom/type"].text() == "mrom");

  loadMemory(bsmemory.memory, node["board/rom"], File::Required, bsmemory.pathID);
}

auto Cartridge::loadSufamiTurboA(Markup::Node node) -> void {
  information.title.sufamiTurboA = node["information/title"].text();

  loadMemory(sufamiturboA.rom, node["board/rom"], File::Required, sufamiturboA.pathID);
  loadMemory(sufamiturboA.ram, node["board/ram"], File::Optional, sufamiturboA.pathID);

  if(node["board/linkable"]) {
    if(auto pathID = platform->load(ID::SufamiTurboB, "Sufami Turbo", "st")) {
      sufamiturboB.pathID = pathID();
      loadSufamiTurboB();
    }
  }
}

auto Cartridge::loadSufamiTurboB(Markup::Node node) -> void {
  information.title.sufamiTurboB = node["information/title"].text();

  loadMemory(sufamiturboB.rom, node["board/rom"], File::Required, sufamiturboB.pathID);
  loadMemory(sufamiturboB.ram, node["board/ram"], File::Optional, sufamiturboB.pathID);
}

//

auto Cartridge::loadROM(Markup::Node node) -> void {
  loadMemory(rom, node, File::Required);
  for(auto leaf : node.find("map")) loadMap(leaf, rom);
}

auto Cartridge::loadRAM(Markup::Node node) -> void {
  loadMemory(ram, node, File::Optional);
  for(auto leaf : node.find("map")) loadMap(leaf, ram);
}

auto Cartridge::loadICD2(Markup::Node node) -> void {
  has.GameBoySlot = true;
  has.ICD2 = true;
  icd2.revision = max(1, node["revision"].natural());

  //Game Boy core loads data through ICD2 interface
  for(auto leaf : node.find("map")) loadMap(leaf, {&ICD2::readIO, &icd2}, {&ICD2::writeIO, &icd2});
}

auto Cartridge::loadMCC(Markup::Node node) -> void {
  has.BSMemorySlot = true;
  has.MCC = true;

  loadMemory(mcc.rom, node["rom"], File::Required);
  loadMemory(mcc.ram, node["ram"], File::Optional);

  for(auto leaf : node.find("map")) leaf.text() == "mcu"
  ? loadMap(leaf, {&MCC::mcuRead, &mcc}, {&MCC::mcuWrite, &mcc})
  : loadMap(leaf, {&MCC::read, &mcc}, {&MCC::write, &mcc});
  for(auto leaf : node["ram"].find("map")) loadMap(leaf, mcc.ram);
}

auto Cartridge::loadBSMemoryPack(Markup::Node node) -> void {
  has.BSMemorySlot = true;

  for(auto leaf : node.find("map")) {
    if(bsmemory.memory.size() == 0) continue;
    loadMap(leaf, bsmemory);
  }
}

auto Cartridge::loadSufamiTurbo(Markup::Node node, bool slot) -> void {
  has.SufamiTurboSlots = true;

  for(auto leaf : node["rom"].find("map")) {
    auto& cart = (slot == 0 ? sufamiturboA : sufamiturboB);
    if(cart.rom.size() == 0) continue;
    loadMap(leaf, cart.rom);
  }

  for(auto leaf : node["ram"].find("map")) {
    auto& cart = (slot == 0 ? sufamiturboA : sufamiturboB);
    if(cart.ram.size() == 0) continue;
    loadMap(leaf, cart.ram);
  }
}

auto Cartridge::loadNSS(Markup::Node node) -> void {
  has.NSSDIP = true;
  nss.dip = platform->dipSettings(node);

  for(auto leaf : node.find("map")) loadMap(leaf, {&NSS::read, &nss}, {&NSS::write, &nss});
}

auto Cartridge::loadEvent(Markup::Node node) -> void {
  auto roms = node.find("rom");
  if(roms.size() != 4) return;

  has.Event = true;

  for(uint n : range(4)) loadMemory(event.rom[n], roms[n], File::Required);
  loadMemory(event.ram, node["ram"], File::Optional);

  event.board = Event::Board::CampusChallenge92;
  if(node.text() == "CC92") event.board = Event::Board::CampusChallenge92;
  if(node.text() == "PF94") event.board = Event::Board::Powerfest94;
  event.timer = node["timer"].natural();

  for(auto leaf : node.find("map")) leaf.text() == "mcu"
  ? loadMap(leaf, {&Event::mcuRead, &event}, {&Event::mcuWrite, &event})
  : loadMap(leaf, {&Event::read, &event}, {&Event::write, &event});
  for(auto leaf : node["ram"].find("map")) loadMap(leaf, event.ram);
}

auto Cartridge::loadSA1(Markup::Node node) -> void {
  has.SA1 = true;

  loadMemory(sa1.rom, node["rom"], File::Required);
  loadMemory(sa1.bwram, node["bwram"], File::Optional);
  loadMemory(sa1.iram, node["iram"], File::Optional);

  for(auto leaf : node.find("map")) loadMap(leaf, {&SA1::readIO, &sa1}, {&SA1::writeIO, &sa1});
  for(auto leaf : node["rom"].find("map")) loadMap(leaf, {&SA1::mmcromRead, &sa1}, {&SA1::mmcromWrite, &sa1});
  for(auto leaf : node["bwram"].find("map")) loadMap(leaf, {&SA1::mmcbwramRead, &sa1}, {&SA1::mmcbwramWrite, &sa1});
  for(auto leaf : node["iram"].find("map")) loadMap(leaf, sa1.cpuiram);
}

auto Cartridge::loadSuperFX(Markup::Node node) -> void {
  has.SuperFX = true;

  loadMemory(superfx.rom, node["rom"], File::Required);
  loadMemory(superfx.ram, node["ram"], File::Optional);

  for(auto leaf : node.find("map")) loadMap(leaf, {&SuperFX::readIO, &superfx}, {&SuperFX::writeIO, &superfx});
  for(auto leaf : node["rom"].find("map")) loadMap(leaf, superfx.cpurom);
  for(auto leaf : node["ram"].find("map")) loadMap(leaf, superfx.cpuram);
}

auto Cartridge::loadARMDSP(Markup::Node node) -> void {
  has.ARMDSP = true;

  if(auto fp = platform->open(ID::SuperFamicom, node["prom"]["name"].text(), File::Read, File::Required)) {
    for(auto n : range(128 * 1024)) armdsp.programROM[n] = fp->read();
  }
  if(auto fp = platform->open(ID::SuperFamicom, node["drom"]["name"].text(), File::Read, File::Required)) {
    for(auto n : range( 32 * 1024)) armdsp.dataROM[n] = fp->read();
  }
  if(auto fp = platform->open(ID::SuperFamicom, node["ram"]["name"].text(), File::Read)) {
    for(auto n : range( 16 * 1024)) armdsp.programRAM[n] = fp->read();
  }

  for(auto leaf : node.find("map")) loadMap(leaf, {&ArmDSP::read, &armdsp}, {&ArmDSP::write, &armdsp});
}

auto Cartridge::loadHitachiDSP(Markup::Node node, uint roms) -> void {
  has.HitachiDSP = true;

  hitachidsp.Frequency = node["frequency"].natural();
  if(hitachidsp.Frequency == 0) hitachidsp.Frequency = 20'000'000;
  hitachidsp.Roms = roms;  //1 or 2

  loadMemory(hitachidsp.rom, node["rom"], File::Required);
  loadMemory(hitachidsp.ram, node["ram"], File::Optional);

  for(auto& word : hitachidsp.dataROM) word = 0x000000;
  for(auto& word : hitachidsp.dataRAM) word = 0x00;

  if(auto fp = platform->open(ID::SuperFamicom, node["drom"]["name"].text(), File::Read, File::Required)) {
    for(auto n : range(1 * 1024)) hitachidsp.dataROM[n] = fp->readl(3);
  }
  if(auto fp = platform->open(ID::SuperFamicom, node["dram"]["name"].text(), File::Read)) {
    for(auto n : range(3 * 1024)) hitachidsp.dataRAM[n] = fp->readl(1);
  }

  for(auto leaf : node.find("map")) loadMap(leaf, {&HitachiDSP::dspRead, &hitachidsp}, {&HitachiDSP::dspWrite, &hitachidsp});
  for(auto leaf : node["rom"].find("map")) loadMap(leaf, {&HitachiDSP::romRead, &hitachidsp}, {&HitachiDSP::romWrite, &hitachidsp});
  for(auto leaf : node["ram"].find("map")) loadMap(leaf, {&HitachiDSP::ramRead, &hitachidsp}, {&HitachiDSP::ramWrite, &hitachidsp});
  for(auto leaf : node["dram"].find("map")) loadMap(leaf, {&HitachiDSP::dramRead, &hitachidsp}, {&HitachiDSP::dramWrite, &hitachidsp});
}

auto Cartridge::loadNECDSP(Markup::Node node) -> void {
  has.NECDSP = true;

  necdsp.Frequency = node["frequency"].natural();
  if(necdsp.Frequency == 0) necdsp.Frequency = 8000000;
  necdsp.revision
  = node["model"].text() == "uPD7725"  ? NECDSP::Revision::uPD7725
  : node["model"].text() == "uPD96050" ? NECDSP::Revision::uPD96050
  : NECDSP::Revision::uPD7725;

  for(auto& word : necdsp.programROM) word = 0x000000;
  for(auto& word : necdsp.dataROM) word = 0x0000;
  for(auto& word : necdsp.dataRAM) word = 0x0000;

  uint size[3] = {0};
  if(necdsp.revision == NECDSP::Revision::uPD7725 ) memory::assign(size,  2048, 1024,  256);
  if(necdsp.revision == NECDSP::Revision::uPD96050) memory::assign(size, 16384, 2048, 2048);

  if(auto fp = platform->open(ID::SuperFamicom, node["prom"]["name"].text(), File::Read, File::Required)) {
    for(auto n : range(size[0])) necdsp.programROM[n] = fp->readl(3);
  }
  if(auto fp = platform->open(ID::SuperFamicom, node["drom"]["name"].text(), File::Read, File::Required)) {
    for(auto n : range(size[1])) necdsp.dataROM[n] = fp->readl(2);
  }
  if(auto fp = platform->open(ID::SuperFamicom, node["dram"]["name"].text(), File::Read)) {
    for(auto n : range(size[2])) necdsp.dataRAM[n] = fp->readl(2);
  }

  for(auto leaf : node.find("map")) loadMap(leaf, {&NECDSP::read, &necdsp}, {&NECDSP::write, &necdsp});
  for(auto leaf : node["dram"].find("map")) loadMap(leaf, {&NECDSP::readRAM, &necdsp}, {&NECDSP::writeRAM, &necdsp});
}

auto Cartridge::loadEpsonRTC(Markup::Node node) -> void {
  has.EpsonRTC = true;

  if(auto fp = platform->open(ID::SuperFamicom, node["ram"]["name"].text(), File::Read)) {
    uint8 data[16] = {0};
    for(auto& byte : data) fp->read();
    epsonrtc.load(data);
  }

  for(auto leaf : node.find("map")) loadMap(leaf, {&EpsonRTC::read, &epsonrtc}, {&EpsonRTC::write, &epsonrtc});
}

auto Cartridge::loadSharpRTC(Markup::Node node) -> void {
  has.SharpRTC = true;

  if(auto fp = platform->open(ID::SuperFamicom, node["ram"]["name"].text(), File::Read)) {
    uint8 data[16] = {0};
    for(auto& byte : data) fp->read();
    sharprtc.load(data);
  }

  for(auto leaf : node.find("map")) loadMap(leaf, {&SharpRTC::read, &sharprtc}, {&SharpRTC::write, &sharprtc});
}

auto Cartridge::loadSPC7110(Markup::Node node) -> void {
  has.SPC7110 = true;

  loadMemory(spc7110.prom, node["prom"], File::Required);
  loadMemory(spc7110.drom, node["drom"], File::Required);
  loadMemory(spc7110.ram, node["ram"], File::Optional);

  for(auto leaf : node.find("map")) leaf.text() == "mcu"
  ? loadMap(leaf, {&SPC7110::mcuromRead, &spc7110}, {&SPC7110::mcuromWrite, &spc7110})
  : loadMap(leaf, {&SPC7110::read, &spc7110}, {&SPC7110::write, &spc7110});
  for(auto leaf : node["ram"].find("map")) loadMap(leaf, {&SPC7110::mcuramRead, &spc7110}, {&SPC7110::mcuramWrite, &spc7110});
}

auto Cartridge::loadSDD1(Markup::Node node) -> void {
  has.SDD1 = true;

  loadMemory(sdd1.rom, node["rom"], File::Required);
  loadMemory(sdd1.ram, node["ram"], File::Optional);

  for(auto leaf : node.find("map")) loadMap(leaf, {&SDD1::read, &sdd1}, {&SDD1::write, &sdd1});
  for(auto leaf : node["rom"].find("map")) loadMap(leaf, {&SDD1::mcuromRead, &sdd1}, {&SDD1::mcuromWrite, &sdd1});
  for(auto leaf : node["ram"].find("map")) loadMap(leaf, {&SDD1::mcuramRead, &sdd1}, {&SDD1::mcuramWrite, &sdd1});
}

auto Cartridge::loadOBC1(Markup::Node node) -> void {
  has.OBC1 = true;

  loadMemory(obc1.ram, node["ram"], File::Optional);

  for(auto leaf : node.find("map")) loadMap(leaf, {&OBC1::read, &obc1}, {&OBC1::write, &obc1});
}

auto Cartridge::loadMSU1(Markup::Node node) -> void {
  has.MSU1 = true;

  for(auto leaf : node.find("map")) loadMap(leaf, {&MSU1::readIO, &msu1}, {&MSU1::writeIO, &msu1});
}

//

auto Cartridge::loadMemory(MappedRAM& ram, Markup::Node node, bool required, maybe<uint> id) -> void {
  if(!id) id = pathID();
  auto name = node["name"].text();
  auto size = node["size"].natural();
  ram.allocate(size);
  if(auto fp = platform->open(id(), name, File::Read, required)) {
    fp->read(ram.data(), ram.size());
  }
}

auto Cartridge::loadMap(Markup::Node map, SuperFamicom::Memory& memory) -> void {
  auto addr = map["address"].text();
  auto size = map["size"].natural();
  auto base = map["base"].natural();
  auto mask = map["mask"].natural();
  if(size == 0) size = memory.size();
  if(size == 0) return;
  bus.map({&SuperFamicom::Memory::read, &memory}, {&SuperFamicom::Memory::write, &memory}, addr, size, base, mask);
}

auto Cartridge::loadMap(
  Markup::Node map,
  const function<uint8 (uint24, uint8)>& reader,
  const function<void (uint24, uint8)>& writer
) -> void {
  auto addr = map["address"].text();
  auto size = map["size"].natural();
  auto base = map["base"].natural();
  auto mask = map["mask"].natural();
  bus.map(reader, writer, addr, size, base, mask);
}
