#ifdef CART_CPP

//================
//Normal cartridge
//================

bool Cartridge::load_normal(const char *base) {
  uint8_t *data;
  unsigned size;
  bool patch_applied;
  cart.filename = base;

  load_begin(ModeNormal);
  if(load_image(base, data, size, patch_applied) == false) return false;

  snes.config.path.current = basepath(cart.filename);
  if(patch_applied) set(patched, true);

  cartinfo_t cartinfo;
  read_header(cartinfo, cart.rom = data, cart.rom_size = size);
  set_cartinfo(cartinfo);

  if(cartinfo.ram_size > 0) {
    load_ram(get_filename(base, "srm", snes.config.path.save), cart.ram, cart.ram_size = cartinfo.ram_size, 0xff);
  }

  if(cartinfo.srtc || cartinfo.spc7110rtc) {
    load_ram(get_filename(base, "rtc", snes.config.path.save), cart.rtc, cart.rtc_size = 20, 0x00);
  }

  load_end();
  set(name, basename(base));
  return true;
}

void Cartridge::unload_normal() {
  if(cart.ram) save_file(get_filename(cart.filename, "srm", snes.config.path.save), cart.ram, cart.ram_size);
  if(cart.rtc) save_file(get_filename(cart.filename, "rtc", snes.config.path.save), cart.rtc, cart.rtc_size);
}

//======================
//BS-X slotted cartridge
//======================

bool Cartridge::load_bsx_slotted(const char *base, const char *slot) {
  uint8_t *data;
  unsigned size;
  bool patch_applied;
  cart.filename = base;
  bs.filename = slot;

  load_begin(ModeBsxSlotted);
  if(load_image(base, data, size, patch_applied) == false) return false;

  snes.config.path.current = basepath(cart.filename);
  if(patch_applied) set(patched, true);

  cartinfo_t cartinfo;
  read_header(cartinfo, cart.rom = data, cart.rom_size = size);
  set_cartinfo(cartinfo);

  if(load_image(slot, data, size, patch_applied) == true) {
    set(bsx_flash_loaded, true);
    if(patch_applied) set(patched, true);
    bs.ram = data;
    bs.ram_size = size;
  }

  if(cartinfo.ram_size > 0) {
    load_ram(get_filename(base, "srm", snes.config.path.save), cart.ram, cart.ram_size = cartinfo.ram_size, 0xff);
  }

  load_end();
  string filename = basename(base);
  if(*slot) filename << " + " << basename(slot);
  set(name, filename);
  return true;
}

void Cartridge::unload_bsx_slotted() {
  if(cart.ram) save_file(get_filename(cart.filename, "srm", snes.config.path.save), cart.ram, cart.ram_size);
}

//====================
//BS-X flash cartridge
//====================

bool Cartridge::load_bsx(const char *base, const char *slot) {
  uint8_t *data;
  unsigned size;
  bool patch_applied;
  cart.filename = base;
  bs.filename = slot;

  load_begin(ModeBsx);
  if(load_image(base, data, size, patch_applied) == false) return false;

  snes.config.path.current = basepath(cart.filename);
  if(patch_applied) set(patched, true);

  cartinfo_t cartinfo;
  read_header(cartinfo, cart.rom = data, cart.rom_size = size);
  set_cartinfo(cartinfo);

  cart.ram = 0;
  cart.ram_size = 0;

  memset(bsxcart.sram.handle (), 0x00, bsxcart.sram.size ());
  memset(bsxcart.psram.handle(), 0x00, bsxcart.psram.size());

  if(load_file(get_filename(base, "srm", snes.config.path.save), data, size, CompressionNone) == true) {
    memcpy(bsxcart.sram.handle (), data, min(bsxcart.sram.size (), size));
    delete[] data;
  }

  if(load_file(get_filename(base, "psr", snes.config.path.save), data, size, CompressionNone) == true) {
    memcpy(bsxcart.psram.handle(), data, min(bsxcart.psram.size(), size));
    delete[] data;
  }

  if(load_image(slot, data, size, patch_applied) == true) {
    set(bsx_flash_loaded, true);
    if(patch_applied) set(patched, true);
    bs.ram = data;
    bs.ram_size = size;
  }

  load_end();
  set(name, !*slot ? basename(base) : basename(slot));
  return true;
}

void Cartridge::unload_bsx() {
  save_file(get_filename(cart.filename, "srm", snes.config.path.save), bsxcart.sram.handle (), bsxcart.sram.size ());
  save_file(get_filename(cart.filename, "psr", snes.config.path.save), bsxcart.psram.handle(), bsxcart.psram.size());
}

//============================
//Sufami Turbo flash cartridge
//============================

bool Cartridge::load_sufami_turbo(const char *base, const char *slotA, const char *slotB) {
  uint8_t *data;
  unsigned size;
  bool patch_applied;
  cart.filename = base;
  stA.filename = slotA;
  stB.filename = slotB;

  load_begin(ModeSufamiTurbo);
  if(load_image(base, data, size, patch_applied) == false) return false;

  snes.config.path.current = basepath(cart.filename);
  if(patch_applied) set(patched, true);

  cartinfo_t cartinfo;
  read_header(cartinfo, cart.rom = data, cart.rom_size = size);
  set_cartinfo(cartinfo);

  if(load_image(slotA, data, size, patch_applied) == true) {
    if(patch_applied) set(patched, true);
    stA.rom = new(zeromemory) uint8_t[stA.rom_size = 0x100000];
    memcpy(stA.rom, data, min(size, stA.rom_size));
    delete[] data;

    load_ram(get_filename(slotA, "srm", snes.config.path.save), stA.ram, stA.ram_size = 0x020000, 0xff);
  }

  if(load_image(slotB, data, size, patch_applied) == true) {
    if(patch_applied) set(patched, true);
    stB.rom = new(zeromemory) uint8_t[stB.rom_size = 0x100000];
    memcpy(stB.rom, data, min(size, stB.rom_size));
    delete[] data;

    load_ram(get_filename(slotB, "srm", snes.config.path.save), stB.ram, stB.ram_size = 0x020000, 0xff);
  }

  load_end();
  string filename;
       if(!*slotA && !*slotB) filename << basename(base);
  else if( *slotA && !*slotB) filename << basename(slotA);
  else if(!*slotA &&  *slotB) filename << basename(slotB);
  else filename << basename(slotA) << " + " << basename(slotB);
  set(name, filename);
  return true;
}

void Cartridge::unload_sufami_turbo() {
  if(stA.ram) save_file(get_filename(stA.filename, "srm", snes.config.path.save), stA.ram, stA.ram_size);
  if(stB.ram) save_file(get_filename(stB.filename, "srm", snes.config.path.save), stB.ram, stB.ram_size);
}

//=================
//utility functions
//=================

Cartridge::Type Cartridge::detect_image_type(const char *filename) const {
  uint8_t *data;
  unsigned size;
  bool patch_applied;
  if(!load_image(filename, data, size, patch_applied)) return TypeUnknown;

  cartinfo_t info;
  read_header(info, data, size);
  delete[] data;
  return info.type;
}

bool Cartridge::load_image(const char *filename, uint8_t *&data, unsigned &size, bool &patched) const {
  if(!filename || !*filename) return false;
  if(!load_file(filename, data, size, CompressionAuto)) return false;

  if((size & 0x7fff) == 512) {
    //remove 512-byte header
    memmove(data, data + 512, size -= 512);
  }

  uint8_t *pdata;
  unsigned psize;
  if(load_file(get_filename(filename, "ups", snes.config.path.patch), pdata, psize, CompressionInspect) == true) {
    apply_patch(pdata, psize, data, size);
    delete[] pdata;
    patched = true;
  } else {
    patched = false;
  }

  return true;
}

bool Cartridge::load_ram(const char *filename, uint8_t *&data, unsigned size, uint8_t init) const {
  data = new uint8_t[size];
  memset(data, init, size);

  uint8_t *savedata;
  unsigned savesize;
  if(load_file(filename, savedata, savesize, CompressionNone) == false) return false;

  memcpy(data, savedata, min(size, savesize));
  delete[] savedata;
  return true;
}

#endif
