lsnes/rom.cpp
Ilari Liusvaara e0a4bfa510 Get rid of fieldsplitter
It was used more before, now it was just used to split author names
into component parts. This can be much better done with a dedicated
function.
2011-09-17 10:15:35 +03:00

834 lines
25 KiB
C++

#include "lsnes.hpp"
#include <snes/snes.hpp>
using SNES::config;
using SNES::System;
using SNES::Cartridge;
using SNES::Interface;
using SNES::cartridge;
#include <boost/iostreams/categories.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#include <boost/iostreams/filter/symmetric.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include "rom.hpp"
#include "command.hpp"
#include "zip.hpp"
#include "misc.hpp"
#include "memorymanip.hpp"
#include <stdexcept>
#include <sstream>
#include <iomanip>
#include <cstdint>
#include <set>
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
#include <nall/platform.hpp>
#include <nall/endian.hpp>
#include <nall/varint.hpp>
#include <nall/bit.hpp>
#include <nall/serializer.hpp>
#include <nall/property.hpp>
using namespace nall;
#include <ui-libsnes/libsnes.hpp>
//Some anti-typo defs.
#define SNES_TYPE "snes"
#define SNES_PAL "snes_pal"
#define SNES_NTSC "snes_ntsc"
#define BSX "bsx"
#define BSXSLOTTED "bsxslotted"
#define SUFAMITURBO "sufamiturbo"
#define SGB_TYPE "SGB"
#define SGB_PAL "sgb_pal"
#define SGB_NTSC "sgb_ntsc"
void strip_CR(std::string& x);
std::string gtype::tostring(rom_type rtype, rom_region region) throw(std::bad_alloc, std::runtime_error)
{
switch(rtype) {
case ROMTYPE_SNES:
switch(region) {
case REGION_AUTO: return "snes";
case REGION_NTSC: return "snes_ntsc";
case REGION_PAL: return "snes_pal";
};
case ROMTYPE_SGB:
switch(region) {
case REGION_AUTO: return "sgb";
case REGION_NTSC: return "sgb_ntsc";
case REGION_PAL: return "sgb_pal";
};
case ROMTYPE_BSX: return "bsx";
case ROMTYPE_BSXSLOTTED: return "bsxslotted";
case ROMTYPE_SUFAMITURBO: return "sufamiturbo";
default: throw std::runtime_error("tostring: ROMTYPE_NONE");
};
}
std::string gtype::tostring(gametype_t gametype) throw(std::bad_alloc, std::runtime_error)
{
switch(gametype) {
case GT_SNES_NTSC: return "snes_ntsc";
case GT_SNES_PAL: return "snes_pal";
case GT_SGB_NTSC: return "sgb_ntsc";
case GT_SGB_PAL: return "sgb_pal";
case GT_BSX: return "bsx";
case GT_BSX_SLOTTED: return "bsxslotted";
case GT_SUFAMITURBO: return "sufamiturbo";
default: throw std::runtime_error("tostring: GT_INVALID");
};
}
gametype_t gtype::togametype(rom_type rtype, rom_region region) throw(std::bad_alloc, std::runtime_error)
{
switch(rtype) {
case ROMTYPE_SNES:
switch(region) {
case REGION_AUTO: return GT_SGB_NTSC;
case REGION_NTSC: return GT_SNES_NTSC;
case REGION_PAL: return GT_SNES_PAL;
};
case ROMTYPE_SGB:
switch(region) {
case REGION_AUTO: return GT_SGB_NTSC;
case REGION_NTSC: return GT_SGB_NTSC;
case REGION_PAL: return GT_SGB_PAL;
};
case ROMTYPE_BSX: return GT_BSX;
case ROMTYPE_BSXSLOTTED: return GT_BSX_SLOTTED;
case ROMTYPE_SUFAMITURBO: return GT_SUFAMITURBO;
default: throw std::runtime_error("togametype: ROMTYPE_NONE");
};
}
gametype_t gtype::togametype(const std::string& gametype) throw(std::bad_alloc, std::runtime_error)
{
if(gametype == "snes_ntsc")
return GT_SNES_NTSC;
if(gametype == "snes_pal")
return GT_SNES_PAL;
if(gametype == "sgb_ntsc")
return GT_SGB_NTSC;
if(gametype == "sgb_pal")
return GT_SGB_PAL;
if(gametype == "bsx")
return GT_BSX;
if(gametype == "bsxslotted")
return GT_BSX_SLOTTED;
if(gametype == "sufamiturbo")
return GT_SUFAMITURBO;
throw std::runtime_error("Unknown game type '" + gametype + "'");
}
rom_type gtype::toromtype(const std::string& gametype) throw(std::bad_alloc, std::runtime_error)
{
if(gametype == "snes_ntsc")
return ROMTYPE_SNES;
if(gametype == "snes_pal")
return ROMTYPE_SNES;
if(gametype == "snes")
return ROMTYPE_SNES;
if(gametype == "sgb_ntsc")
return ROMTYPE_SGB;
if(gametype == "sgb_pal")
return ROMTYPE_SGB;
if(gametype == "sgb")
return ROMTYPE_SGB;
if(gametype == "bsx")
return ROMTYPE_BSX;
if(gametype == "bsxslotted")
return ROMTYPE_BSXSLOTTED;
if(gametype == "sufamiturbo")
return ROMTYPE_SUFAMITURBO;
throw std::runtime_error("Unknown game type '" + gametype + "'");
}
rom_type gtype::toromtype(gametype_t gametype) throw()
{
switch(gametype) {
case GT_SNES_NTSC: return ROMTYPE_SNES;
case GT_SNES_PAL: return ROMTYPE_SNES;
case GT_SGB_NTSC: return ROMTYPE_SGB;
case GT_SGB_PAL: return ROMTYPE_SGB;
case GT_BSX: return ROMTYPE_BSX;
case GT_BSX_SLOTTED: return ROMTYPE_BSXSLOTTED;
case GT_SUFAMITURBO: return ROMTYPE_SUFAMITURBO;
case GT_INVALID: throw std::runtime_error("toromtype: GT_INVALID");
};
}
rom_region gtype::toromregion(const std::string& gametype) throw(std::bad_alloc, std::runtime_error)
{
if(gametype == "snes_ntsc")
return REGION_NTSC;
if(gametype == "snes_pal")
return REGION_PAL;
if(gametype == "snes")
return REGION_AUTO;
if(gametype == "sgb_ntsc")
return REGION_NTSC;
if(gametype == "sgb_pal")
return REGION_PAL;
if(gametype == "sgb")
return REGION_AUTO;
if(gametype == "bsx")
return REGION_NTSC;
if(gametype == "bsxslotted")
return REGION_NTSC;
if(gametype == "sufamiturbo")
return REGION_NTSC;
throw std::runtime_error("Unknown game type '" + gametype + "'");
}
rom_region gtype::toromregion(gametype_t gametype) throw()
{
switch(gametype) {
case GT_SNES_NTSC: return REGION_NTSC;
case GT_SNES_PAL: return REGION_PAL;
case GT_SGB_NTSC: return REGION_NTSC;
case GT_SGB_PAL: return REGION_PAL;
case GT_BSX: return REGION_NTSC;
case GT_BSX_SLOTTED: return REGION_NTSC;
case GT_SUFAMITURBO: return REGION_NTSC;
case GT_INVALID: throw std::runtime_error("toromregion: GT_INVALID");
};
}
namespace
{
bool option_set(const std::vector<std::string>& cmdline, const std::string& option)
{
for(auto i = cmdline.begin(); i != cmdline.end(); i++)
if(*i == option)
return true;
return false;
}
const char* romtypes_to_recognize[] = {
"rom", "bsx", "bsxslotted", "dmg", "slot-a", "slot-b",
"rom-xml", "bsx-xml", "bsxslotted-xml", "dmg-xml", "slot-a-xml", "slot-b-xml"
};
enum rom_type current_rom_type = ROMTYPE_NONE;
enum rom_region current_region = REGION_NTSC;
uint64_t readval(const std::vector<char>& patch, size_t offset, size_t vsize) throw(std::runtime_error)
{
if(offset >= patch.size() || offset + vsize > patch.size())
throw std::runtime_error("IPS file corrupt");
uint64_t val = 0;
for(size_t i = 0; i < vsize; i++)
val = (val << 8) | static_cast<uint8_t>(patch[offset + i]);
return val;
}
std::string findoption(const std::vector<std::string>& cmdline, const std::string& option)
{
std::string value;
for(auto i = cmdline.begin(); i != cmdline.end(); ++i) {
std::string arg = *i;
if(arg.length() < 3 + option.length())
continue;
if(arg[0] != '-' || arg[1] != '-' || arg.substr(2, option.length()) != option ||
arg[2 + option.length()] != '=')
continue;
if(value == "")
value = arg.substr(3 + option.length());
else
std::cerr << "WARNING: Ignored duplicate option for '" << option << "'." << std::endl;
if(value == "")
throw std::runtime_error("Empty value for '" + option + "' is not allowed");
}
return value;
}
}
loaded_slot::loaded_slot() throw(std::bad_alloc)
{
valid = false;
}
loaded_slot::loaded_slot(const std::string& filename, const std::string& base, bool xml_flag) throw(std::bad_alloc,
std::runtime_error)
{
xml = xml_flag;
if(filename == "") {
valid = false;
return;
}
valid = true;
data = read_file_relative(filename, base);
sha256 = sha256::hash(data);
if(xml) {
size_t osize = data.size();
data.resize(osize + 1);
data[osize] = 0;
}
}
void loaded_slot::patch(const std::vector<char>& patch, int32_t offset) throw(std::bad_alloc, std::runtime_error)
{
bool warned_extend = false;
bool warned_negative = false;
size_t poffset = 0;
if(xml && valid)
data.resize(data.size() - 1);
if(readval(patch, poffset, 5) != 0x5041544348)
throw std::runtime_error("Bad IPS file magic");
poffset += 5;
while(1) {
uint64_t addr = readval(patch, poffset, 3);
if(addr == 0x454F46)
break;
uint64_t len = readval(patch, poffset + 3, 2);
size_t readstride;
size_t roffset;
size_t opsize;
if(len) {
//Verbatim block.
readstride = 1;
roffset = poffset + 5;
opsize = 5 + len;
} else {
//RLE block. Read real size first.
len = readval(patch, poffset + 5, 2);
readstride = 0;
roffset = poffset + 7;
opsize = 8;
}
for(uint64_t i = 0; i < len; i++) {
int64_t baddr = addr + i + offset;
if(baddr < 0) {
if(!warned_negative)
std::cerr << "WARNING: IPS patch tries to modify negative offset. "
<< "Bad patch or offset?" << std::endl;
warned_negative = true;
continue;
} else if(baddr >= static_cast<int64_t>(data.size())) {
if(!warned_extend)
std::cerr << "WARNING: IPS patch tries to extend the ROM. "
<< "Bad patch or offset? " << std::endl;
warned_extend = true;
size_t oldsize = data.size();
data.resize(baddr + 1);
for(size_t j = oldsize; j <= static_cast<uint64_t>(baddr); j++)
data[j] = 0;
}
size_t srcoff = roffset + readstride * i;
if(srcoff >= patch.size())
throw std::runtime_error("Corrupt IPS patch");
data[baddr] = static_cast<uint8_t>(patch[srcoff]);
}
poffset += opsize;
}
//Mark the slot as valid and update hash.
valid = true;
sha256 = sha256::hash(data);
if(xml) {
size_t osize = data.size();
data.resize(osize + 1);
data[osize] = 0;
}
}
rom_files::rom_files() throw()
{
rtype = ROMTYPE_NONE;
region = REGION_AUTO;
}
rom_files::rom_files(const std::vector<std::string>& cmdline) throw(std::bad_alloc, std::runtime_error)
{
rom = rom_xml = slota = slota_xml = slotb = slotb_xml = "";
std::string arr[sizeof(romtypes_to_recognize) / sizeof(romtypes_to_recognize[0])];
unsigned long flags = 0;
for(size_t i = 0; i < sizeof(romtypes_to_recognize) / sizeof(romtypes_to_recognize[0]); i++) {
arr[i] = findoption(cmdline, romtypes_to_recognize[i]);
if(arr[i] != "")
flags |= (1L << i);
}
rtype = recognize_platform(flags);
for(size_t i = 0; i < sizeof(romtypes_to_recognize) / sizeof(romtypes_to_recognize[0]); i++) {
if(arr[i] != "")
switch(recognize_commandline_rom(rtype, romtypes_to_recognize[i])) {
case 0: rom = arr[i]; break;
case 1: rom_xml = arr[i]; break;
case 2: slota = arr[i]; break;
case 3: slota_xml = arr[i]; break;
case 4: slotb = arr[i]; break;
case 5: slotb_xml = arr[i]; break;
};
}
region = (rtype == ROMTYPE_SGB || rtype == ROMTYPE_SNES) ? REGION_AUTO : REGION_NTSC;
if(option_set(cmdline, "--ntsc"))
region = REGION_NTSC;
else if(option_set(cmdline, "--pal"))
region = REGION_PAL;
base_file = "";
}
void rom_files::resolve_relative() throw(std::bad_alloc, std::runtime_error)
{
rom = resolve_file_relative(rom, base_file);
rom_xml = resolve_file_relative(rom_xml, base_file);
slota = resolve_file_relative(slota, base_file);
slota_xml = resolve_file_relative(slota_xml, base_file);
slotb = resolve_file_relative(slotb, base_file);
slotb_xml = resolve_file_relative(slotb_xml, base_file);
base_file = "";
}
std::pair<enum rom_type, enum rom_region> get_current_rom_info() throw()
{
return std::make_pair(current_rom_type, current_region);
}
loaded_rom::loaded_rom() throw()
{
rtype = ROMTYPE_NONE;
region = orig_region = REGION_AUTO;
}
loaded_rom::loaded_rom(const rom_files& files) throw(std::bad_alloc, std::runtime_error)
{
std::string _slota = files.slota;
std::string _slota_xml = files.slota_xml;
std::string _slotb = files.slotb;
std::string _slotb_xml = files.slotb_xml;
if(files.rtype == ROMTYPE_NONE) {
rtype = ROMTYPE_NONE;
region = orig_region = files.region;
return;
}
if((_slota != "" || _slota_xml != "") && files.rtype == ROMTYPE_SNES) {
messages << "WARNING: SNES takes only 1 ROM image" << std::endl;
_slota = "";
_slota_xml = "";
}
if((_slotb != "" || _slotb_xml != "") && files.rtype != ROMTYPE_SUFAMITURBO) {
messages << "WARNING: Only Sufami Turbo takes 3 ROM images" << std::endl;
_slotb = "";
_slotb_xml = "";
}
if(files.rom_xml != "" && files.rom == "")
messages << "WARNING: " << name_subrom(files.rtype, 0) << " specified without corresponding "
<< name_subrom(files.rtype, 1) << std::endl;
if(_slota_xml != "" && _slota == "")
messages << "WARNING: " << name_subrom(files.rtype, 2) << " specified without corresponding "
<< name_subrom(files.rtype, 3) << std::endl;
if(_slotb_xml != "" && _slotb == "")
messages << "WARNING: " << name_subrom(files.rtype, 4) << " specified without corresponding "
<< name_subrom(files.rtype, 5) << std::endl;
rtype = files.rtype;
rom = loaded_slot(files.rom, files.base_file);
rom_xml = loaded_slot(files.rom_xml, files.base_file, true);
slota = loaded_slot(_slota, files.base_file);
slota_xml = loaded_slot(_slota_xml, files.base_file, true);
slotb = loaded_slot(_slotb, files.base_file);
slotb_xml = loaded_slot(_slotb_xml, files.base_file, true);
orig_region = region = files.region;
}
void loaded_rom::load() throw(std::bad_alloc, std::runtime_error)
{
current_rom_type = ROMTYPE_NONE;
if(region == REGION_AUTO && orig_region != REGION_AUTO)
region = orig_region;
if(region != orig_region && orig_region != REGION_AUTO)
throw std::runtime_error("Trying to force incompatible region");
if(rtype == ROMTYPE_NONE)
throw std::runtime_error("Can't insert cartridge of type NONE!");
switch(region) {
case REGION_AUTO:
config.region = System::Region::Autodetect;
break;
case REGION_NTSC:
config.region = System::Region::NTSC;
break;
case REGION_PAL:
config.region = System::Region::PAL;
break;
default:
throw std::runtime_error("Trying to force unknown region");
}
switch(rtype) {
case ROMTYPE_SNES:
if(!snes_load_cartridge_normal(rom_xml, rom, rom))
throw std::runtime_error("Can't load cartridge ROM");
break;
case ROMTYPE_BSX:
if(region == REGION_PAL)
throw std::runtime_error("BSX can't be PAL");
if(!snes_load_cartridge_bsx(rom_xml, rom, rom, slota_xml, slota, slota))
throw std::runtime_error("Can't load cartridge ROM");
break;
case ROMTYPE_BSXSLOTTED:
if(region == REGION_PAL)
throw std::runtime_error("Slotted BSX can't be PAL");
if(!snes_load_cartridge_bsx_slotted(rom_xml, rom, rom, slota_xml, slota, slota))
throw std::runtime_error("Can't load cartridge ROM");
break;
case ROMTYPE_SGB:
if(!snes_load_cartridge_super_game_boy(rom_xml, rom, rom, slota_xml, slota, slota))
throw std::runtime_error("Can't load cartridge ROM");
break;
case ROMTYPE_SUFAMITURBO:
if(region == REGION_PAL)
throw std::runtime_error("Sufami Turbo can't be PAL");
if(!snes_load_cartridge_sufami_turbo(rom_xml, rom, rom, slota_xml, slota, slota, slotb_xml, slotb,
slotb))
throw std::runtime_error("Can't load cartridge ROM");
break;
default:
throw std::runtime_error("Unknown cartridge type");
}
if(region == REGION_AUTO)
region = snes_get_region() ? REGION_PAL : REGION_NTSC;
snes_power();
current_rom_type = rtype;
current_region = region;
refresh_cart_mappings();
}
void loaded_rom::do_patch(const std::vector<std::string>& cmdline) throw(std::bad_alloc,
std::runtime_error)
{
int32_t offset = 0;
for(auto i = cmdline.begin(); i != cmdline.end(); i++) {
std::string opt = *i;
if(opt.length() >= 13 && opt.substr(0, 13) == "--ips-offset=") {
try {
offset = parse_value<int32_t>(opt.substr(13));
} catch(std::exception& e) {
throw std::runtime_error("Invalid IPS offset option '" + opt + "': " + e.what());
}
continue;
}
if(opt.length() < 6 || opt.substr(0, 6) != "--ips-")
continue;
size_t split = opt.find_first_of("=");
if(split > opt.length())
throw std::runtime_error("Invalid IPS patch argument '" + opt + "'");
std::string kind = opt.substr(6, split - 6);
std::string filename = opt.substr(split + 1);
messages << "Patching " << kind << " using '" << filename << "'" << std::endl;
std::vector<char> ips;
try {
ips = read_file_relative(filename, "");
} catch(std::bad_alloc& e) {
OOM_panic();
} catch(std::exception& e) {
throw std::runtime_error("Can't read IPS '" + filename + "': " + e.what());
}
try {
switch(recognize_commandline_rom(rtype, kind)) {
case 0: rom.patch(ips, offset); break;
case 1: rom_xml.patch(ips, offset); break;
case 2: slota.patch(ips, offset); break;
case 3: slota_xml.patch(ips, offset); break;
case 4: slotb.patch(ips, offset); break;
case 5: slotb_xml.patch(ips, offset); break;
default:
throw std::runtime_error("Invalid subROM '" + kind + "' to patch");
}
} catch(std::bad_alloc& e) {
OOM_panic();
} catch(std::exception& e) {
throw std::runtime_error("Can't Patch with IPS '" + filename + "': " + e.what());
}
}
}
namespace
{
std::string sram_name(const nall::string& _id, Cartridge::Slot slotname)
{
std::string id(_id, _id.length());
if(slotname == Cartridge::Slot::SufamiTurboA)
return "slota." + id.substr(1);
if(slotname == Cartridge::Slot::SufamiTurboB)
return "slotb." + id.substr(1);
return id.substr(1);
}
}
std::map<std::string, std::vector<char>> save_sram() throw(std::bad_alloc)
{
std::map<std::string, std::vector<char>> out;
for(unsigned i = 0; i < cartridge.nvram.size(); i++) {
Cartridge::NonVolatileRAM& r = cartridge.nvram[i];
std::string savename = sram_name(r.id, r.slot);
std::vector<char> x;
x.resize(r.size);
memcpy(&x[0], r.data, r.size);
out[savename] = x;
}
return out;
}
void load_sram(std::map<std::string, std::vector<char>>& sram) throw(std::bad_alloc)
{
std::set<std::string> used;
if(sram.empty())
return;
for(unsigned i = 0; i < cartridge.nvram.size(); i++) {
Cartridge::NonVolatileRAM& r = cartridge.nvram[i];
std::string savename = sram_name(r.id, r.slot);
if(sram.count(savename)) {
std::vector<char>& x = sram[savename];
if(r.size != x.size())
messages << "WARNING: SRAM '" << savename << "': Loaded " << x.size()
<< " bytes, but the SRAM is " << r.size << "." << std::endl;
memcpy(r.data, &x[0], (r.size < x.size()) ? r.size : x.size());
used.insert(savename);
} else
messages << "WARNING: SRAM '" << savename << ": No data." << std::endl;
}
for(auto i = sram.begin(); i != sram.end(); ++i)
if(!used.count(i->first))
messages << "WARNING: SRAM '" << i->first << ": Not found on cartridge." << std::endl;
}
std::map<std::string, std::vector<char>> load_sram_commandline(const std::vector<std::string>& cmdline)
throw(std::bad_alloc, std::runtime_error)
{
std::map<std::string, std::vector<char>> ret;
for(auto i = cmdline.begin(); i != cmdline.end(); i++) {
std::string opt = *i;
if(opt.length() >= 11 && opt.substr(0, 11) == "--continue=") {
size_t split = opt.find_first_of("=");
if(split > opt.length() - 1)
throw std::runtime_error("Bad SRAM option '" + opt + "'");
std::string file = opt.substr(split + 1);
zip_reader r(file);
for(auto j = r.begin(); j != r.end(); j++) {
std::string fname = *j;
if(fname.length() < 6 || fname.substr(0, 5) != "sram.")
continue;
std::istream& x = r[fname];
try {
std::vector<char> out;
boost::iostreams::back_insert_device<std::vector<char>> rd(out);
boost::iostreams::copy(x, rd);
delete &x;
ret[fname.substr(5, split - 5)] = out;
} catch(...) {
delete &x;
throw;
}
}
continue;
}
if(opt.length() < 8 || opt.substr(0, 7) != "--sram-")
continue;
size_t split = opt.find_first_of("=");
if(split > opt.length() - 1)
throw std::runtime_error("Bad SRAM option '" + opt + "'");
std::string kind = opt.substr(7, split - 7);
std::string file = opt.substr(split + 1);
if(kind == "")
throw std::runtime_error("Bad SRAM option '" + opt + "'");
try {
ret[kind] = read_file_relative(file, "");
} catch(std::bad_alloc& e) {
throw;
} catch(std::runtime_error& e) {
throw std::runtime_error("Can't load SRAM '" + kind + "': " + e.what());
}
}
return ret;
}
std::vector<char> save_core_state() throw(std::bad_alloc)
{
SNES::system.runtosave();
std::vector<char> ret;
serializer s = SNES::system.serialize();
ret.resize(s.size());
memcpy(&ret[0], s.data(), s.size());
size_t offset = ret.size();
unsigned char tmp[32];
sha256::hash(tmp, ret);
ret.resize(offset + 32);
memcpy(&ret[offset], tmp, 32);
return ret;
}
void load_core_state(const std::vector<char>& buf) throw(std::runtime_error)
{
if(buf.size() < 32)
throw std::runtime_error("Savestate corrupt");
unsigned char tmp[32];
sha256::hash(tmp, reinterpret_cast<const uint8_t*>(&buf[0]), buf.size() - 32);
if(memcmp(tmp, &buf[buf.size() - 32], 32))
throw std::runtime_error("Savestate corrupt");
serializer s(reinterpret_cast<const uint8_t*>(&buf[0]), buf.size() - 32);
if(!SNES::system.unserialize(s))
throw std::runtime_error("SNES core rejected savestate");
}
namespace
{
struct index_entry
{
std::string hash;
std::string relpath;
std::string from;
};
std::list<index_entry> rom_index;
void replace_index(std::list<index_entry> new_index, const std::string& source)
{
std::list<index_entry> tmp_index;
for(auto i = rom_index.begin(); i != rom_index.end(); i++) {
if(i->from != source)
tmp_index.push_back(*i);
}
for(auto i = new_index.begin(); i != new_index.end(); i++) {
tmp_index.push_back(*i);
}
rom_index = new_index;
}
}
void load_index_file(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
{
std::istream& s = open_file_relative(filename, "");
try {
std::list<index_entry> partial_index;
std::string line;
while(std::getline(s, line)) {
index_entry e;
if(line == "")
continue;
tokensplitter t(line);
e.hash = static_cast<std::string>(t);
e.relpath = t.tail();
e.from = filename;
if(e.hash.length() != 64 || e.relpath == "")
throw std::runtime_error("Bad index file");
partial_index.push_back(e);
}
replace_index(partial_index, filename);
} catch(...) {
delete &s;
throw;
}
delete &s;
}
std::string lookup_file_by_sha256(const std::string& hash) throw(std::bad_alloc, std::runtime_error)
{
if(hash == "")
return "";
for(auto i = rom_index.begin(); i != rom_index.end(); i++) {
if(i->hash != hash)
continue;
try {
std::istream& o = open_file_relative(i->relpath, i->from);
delete &o;
return resolve_file_relative(i->relpath, i->from);
} catch(...) {
continue;
}
}
throw std::runtime_error("No file with hash '" + hash + "' found in known indices");
}
std::string name_subrom(enum rom_type major, unsigned romnumber) throw(std::bad_alloc)
{
if(romnumber == 0)
return "ROM";
else if(romnumber == 1)
return "ROM XML";
else if(major == ROMTYPE_BSX && romnumber == 2)
return "BSX ROM";
else if(major == ROMTYPE_BSX && romnumber == 3)
return "BSX XML";
else if(major == ROMTYPE_BSXSLOTTED && romnumber == 2)
return "BSX ROM";
else if(major == ROMTYPE_BSXSLOTTED && romnumber == 3)
return "BSX XML";
else if(major == ROMTYPE_SGB && romnumber == 2)
return "DMG ROM";
else if(major == ROMTYPE_SGB && romnumber == 3)
return "DMG XML";
else if(major == ROMTYPE_SUFAMITURBO && romnumber == 2)
return "SLOT A ROM";
else if(major == ROMTYPE_SUFAMITURBO && romnumber == 3)
return "SLOT A XML";
else if(major == ROMTYPE_SUFAMITURBO && romnumber == 4)
return "SLOT B ROM";
else if(major == ROMTYPE_SUFAMITURBO && romnumber == 5)
return "SLOT B XML";
else if(romnumber % 2)
return "UNKNOWN XML";
else
return "UNKNOWN ROM";
}
int recognize_commandline_rom(enum rom_type major, const std::string& romname) throw(std::bad_alloc)
{
if(romname == romtypes_to_recognize[0])
return 0;
else if(romname == romtypes_to_recognize[6])
return 1;
else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[1])
return 2;
else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[7])
return 3;
else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[2])
return 2;
else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[8])
return 3;
else if(major == ROMTYPE_SGB && romname == romtypes_to_recognize[3])
return 2;
else if(major == ROMTYPE_SGB && romname == romtypes_to_recognize[9])
return 3;
else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[4])
return 2;
else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[10])
return 3;
else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[5])
return 4;
else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[11])
return 5;
else
return -1;
}
rom_type recognize_platform(unsigned long flags) throw(std::bad_alloc, std::runtime_error)
{
if((flags & 07700) >> 6 & ~(flags & 077))
throw std::runtime_error("SubROM XML specified without corresponding subROM");
if((flags & 1) == 0)
throw std::runtime_error("No SNES main cartridge ROM specified");
if((flags & 077) == 1)
return ROMTYPE_SNES;
if((flags & 077) == 3)
return ROMTYPE_BSX;
if((flags & 077) == 5)
return ROMTYPE_BSXSLOTTED;
if((flags & 077) == 9)
return ROMTYPE_SGB;
if((flags & 060) != 0 && (flags & 017) == 1)
return ROMTYPE_SUFAMITURBO;
throw std::runtime_error("Not valid combination of rom/bsx/bsxslotted/dmg/slot-a/slot-b");
}