Since window is singleton anyway, get rid of window* parameters
[lsnes.git] / rom.cpp
blob5352a15a3f09647cbe4de09255aaa7dfbd1fc68f
1 #include "lsnes.hpp"
2 #include <snes/snes.hpp>
3 using SNES::config;
4 using SNES::System;
5 using SNES::Cartridge;
6 using SNES::Interface;
7 using SNES::cartridge;
9 #include <boost/iostreams/categories.hpp>
10 #include <boost/iostreams/copy.hpp>
11 #include <boost/iostreams/stream.hpp>
12 #include <boost/iostreams/stream_buffer.hpp>
13 #include <boost/iostreams/filter/symmetric.hpp>
14 #include <boost/iostreams/filter/zlib.hpp>
15 #include <boost/iostreams/filtering_stream.hpp>
16 #include <boost/iostreams/device/back_inserter.hpp>
17 #include "window.hpp"
18 #include "rom.hpp"
19 #include "command.hpp"
20 #include "fieldsplit.hpp"
21 #include "zip.hpp"
22 #include "misc.hpp"
23 #include "memorymanip.hpp"
24 #include <stdexcept>
25 #include <sstream>
26 #include <iomanip>
27 #include <cstdint>
28 #include <set>
29 typedef uint8_t uint8;
30 typedef uint16_t uint16;
31 typedef uint32_t uint32;
32 typedef int8_t int8;
33 typedef int16_t int16;
34 typedef int32_t int32;
35 #include <nall/platform.hpp>
36 #include <nall/endian.hpp>
37 #include <nall/varint.hpp>
38 #include <nall/bit.hpp>
39 #include <nall/serializer.hpp>
40 #include <nall/property.hpp>
41 using namespace nall;
42 #include <ui-libsnes/libsnes.hpp>
44 //Some anti-typo defs.
45 #define SNES_TYPE "snes"
46 #define SNES_PAL "snes_pal"
47 #define SNES_NTSC "snes_ntsc"
48 #define BSX "bsx"
49 #define BSXSLOTTED "bsxslotted"
50 #define SUFAMITURBO "sufamiturbo"
51 #define SGB_TYPE "SGB"
52 #define SGB_PAL "sgb_pal"
53 #define SGB_NTSC "sgb_ntsc"
55 void strip_CR(std::string& x);
57 std::string gtype::tostring(rom_type rtype, rom_region region) throw(std::bad_alloc, std::runtime_error)
59 switch(rtype) {
60 case ROMTYPE_SNES:
61 switch(region) {
62 case REGION_AUTO: return "snes";
63 case REGION_NTSC: return "snes_ntsc";
64 case REGION_PAL: return "snes_pal";
66 case ROMTYPE_SGB:
67 switch(region) {
68 case REGION_AUTO: return "sgb";
69 case REGION_NTSC: return "sgb_ntsc";
70 case REGION_PAL: return "sgb_pal";
72 case ROMTYPE_BSX: return "bsx";
73 case ROMTYPE_BSXSLOTTED: return "bsxslotted";
74 case ROMTYPE_SUFAMITURBO: return "sufamiturbo";
75 default: throw std::runtime_error("tostring: ROMTYPE_NONE");
79 std::string gtype::tostring(gametype_t gametype) throw(std::bad_alloc, std::runtime_error)
81 switch(gametype) {
82 case GT_SNES_NTSC: return "snes_ntsc";
83 case GT_SNES_PAL: return "snes_pal";
84 case GT_SGB_NTSC: return "sgb_ntsc";
85 case GT_SGB_PAL: return "sgb_pal";
86 case GT_BSX: return "bsx";
87 case GT_BSX_SLOTTED: return "bsxslotted";
88 case GT_SUFAMITURBO: return "sufamiturbo";
89 default: throw std::runtime_error("tostring: GT_INVALID");
93 gametype_t gtype::togametype(rom_type rtype, rom_region region) throw(std::bad_alloc, std::runtime_error)
95 switch(rtype) {
96 case ROMTYPE_SNES:
97 switch(region) {
98 case REGION_AUTO: return GT_SGB_NTSC;
99 case REGION_NTSC: return GT_SNES_NTSC;
100 case REGION_PAL: return GT_SNES_PAL;
102 case ROMTYPE_SGB:
103 switch(region) {
104 case REGION_AUTO: return GT_SGB_NTSC;
105 case REGION_NTSC: return GT_SGB_NTSC;
106 case REGION_PAL: return GT_SGB_PAL;
108 case ROMTYPE_BSX: return GT_BSX;
109 case ROMTYPE_BSXSLOTTED: return GT_BSX_SLOTTED;
110 case ROMTYPE_SUFAMITURBO: return GT_SUFAMITURBO;
111 default: throw std::runtime_error("togametype: ROMTYPE_NONE");
115 gametype_t gtype::togametype(const std::string& gametype) throw(std::bad_alloc, std::runtime_error)
117 if(gametype == "snes_ntsc")
118 return GT_SNES_NTSC;
119 if(gametype == "snes_pal")
120 return GT_SNES_PAL;
121 if(gametype == "sgb_ntsc")
122 return GT_SGB_NTSC;
123 if(gametype == "sgb_pal")
124 return GT_SGB_PAL;
125 if(gametype == "bsx")
126 return GT_BSX;
127 if(gametype == "bsxslotted")
128 return GT_BSX_SLOTTED;
129 if(gametype == "sufamiturbo")
130 return GT_SUFAMITURBO;
131 throw std::runtime_error("Unknown game type '" + gametype + "'");
134 rom_type gtype::toromtype(const std::string& gametype) throw(std::bad_alloc, std::runtime_error)
136 if(gametype == "snes_ntsc")
137 return ROMTYPE_SNES;
138 if(gametype == "snes_pal")
139 return ROMTYPE_SNES;
140 if(gametype == "snes")
141 return ROMTYPE_SNES;
142 if(gametype == "sgb_ntsc")
143 return ROMTYPE_SGB;
144 if(gametype == "sgb_pal")
145 return ROMTYPE_SGB;
146 if(gametype == "sgb")
147 return ROMTYPE_SGB;
148 if(gametype == "bsx")
149 return ROMTYPE_BSX;
150 if(gametype == "bsxslotted")
151 return ROMTYPE_BSXSLOTTED;
152 if(gametype == "sufamiturbo")
153 return ROMTYPE_SUFAMITURBO;
154 throw std::runtime_error("Unknown game type '" + gametype + "'");
157 rom_type gtype::toromtype(gametype_t gametype) throw()
159 switch(gametype) {
160 case GT_SNES_NTSC: return ROMTYPE_SNES;
161 case GT_SNES_PAL: return ROMTYPE_SNES;
162 case GT_SGB_NTSC: return ROMTYPE_SGB;
163 case GT_SGB_PAL: return ROMTYPE_SGB;
164 case GT_BSX: return ROMTYPE_BSX;
165 case GT_BSX_SLOTTED: return ROMTYPE_BSXSLOTTED;
166 case GT_SUFAMITURBO: return ROMTYPE_SUFAMITURBO;
167 case GT_INVALID: throw std::runtime_error("toromtype: GT_INVALID");
171 rom_region gtype::toromregion(const std::string& gametype) throw(std::bad_alloc, std::runtime_error)
173 if(gametype == "snes_ntsc")
174 return REGION_NTSC;
175 if(gametype == "snes_pal")
176 return REGION_PAL;
177 if(gametype == "snes")
178 return REGION_AUTO;
179 if(gametype == "sgb_ntsc")
180 return REGION_NTSC;
181 if(gametype == "sgb_pal")
182 return REGION_PAL;
183 if(gametype == "sgb")
184 return REGION_AUTO;
185 if(gametype == "bsx")
186 return REGION_NTSC;
187 if(gametype == "bsxslotted")
188 return REGION_NTSC;
189 if(gametype == "sufamiturbo")
190 return REGION_NTSC;
191 throw std::runtime_error("Unknown game type '" + gametype + "'");
194 rom_region gtype::toromregion(gametype_t gametype) throw()
196 switch(gametype) {
197 case GT_SNES_NTSC: return REGION_NTSC;
198 case GT_SNES_PAL: return REGION_PAL;
199 case GT_SGB_NTSC: return REGION_NTSC;
200 case GT_SGB_PAL: return REGION_PAL;
201 case GT_BSX: return REGION_NTSC;
202 case GT_BSX_SLOTTED: return REGION_NTSC;
203 case GT_SUFAMITURBO: return REGION_NTSC;
204 case GT_INVALID: throw std::runtime_error("toromregion: GT_INVALID");
209 namespace
211 bool option_set(const std::vector<std::string>& cmdline, const std::string& option)
213 for(auto i = cmdline.begin(); i != cmdline.end(); i++)
214 if(*i == option)
215 return true;
216 return false;
219 const char* romtypes_to_recognize[] = {
220 "rom", "bsx", "bsxslotted", "dmg", "slot-a", "slot-b",
221 "rom-xml", "bsx-xml", "bsxslotted-xml", "dmg-xml", "slot-a-xml", "slot-b-xml"
224 enum rom_type current_rom_type = ROMTYPE_NONE;
225 enum rom_region current_region = REGION_NTSC;
227 uint64_t readval(const std::vector<char>& patch, size_t offset, size_t vsize) throw(std::runtime_error)
229 if(offset >= patch.size() || offset + vsize > patch.size())
230 throw std::runtime_error("IPS file corrupt");
231 uint64_t val = 0;
232 for(size_t i = 0; i < vsize; i++)
233 val = (val << 8) | static_cast<uint8_t>(patch[offset + i]);
234 return val;
237 std::string findoption(const std::vector<std::string>& cmdline, const std::string& option)
239 std::string value;
240 for(auto i = cmdline.begin(); i != cmdline.end(); ++i) {
241 std::string arg = *i;
242 if(arg.length() < 3 + option.length())
243 continue;
244 if(arg[0] != '-' || arg[1] != '-' || arg.substr(2, option.length()) != option ||
245 arg[2 + option.length()] != '=')
246 continue;
247 if(value == "")
248 value = arg.substr(3 + option.length());
249 else
250 std::cerr << "WARNING: Ignored duplicate option for '" << option << "'." << std::endl;
251 if(value == "")
252 throw std::runtime_error("Empty value for '" + option + "' is not allowed");
254 return value;
259 loaded_slot::loaded_slot() throw(std::bad_alloc)
261 valid = false;
264 loaded_slot::loaded_slot(const std::string& filename, const std::string& base, bool xml_flag) throw(std::bad_alloc,
265 std::runtime_error)
267 xml = xml_flag;
268 if(filename == "") {
269 valid = false;
270 return;
272 valid = true;
273 data = read_file_relative(filename, base);
274 sha256 = sha256::hash(data);
275 if(xml) {
276 size_t osize = data.size();
277 data.resize(osize + 1);
278 data[osize] = 0;
282 void loaded_slot::patch(const std::vector<char>& patch, int32_t offset) throw(std::bad_alloc, std::runtime_error)
284 bool warned_extend = false;
285 bool warned_negative = false;
286 size_t poffset = 0;
287 if(xml && valid)
288 data.resize(data.size() - 1);
289 if(readval(patch, poffset, 5) != 0x5041544348)
290 throw std::runtime_error("Bad IPS file magic");
291 poffset += 5;
292 while(1) {
293 uint64_t addr = readval(patch, poffset, 3);
294 if(addr == 0x454F46)
295 break;
296 uint64_t len = readval(patch, poffset + 3, 2);
297 size_t readstride;
298 size_t roffset;
299 size_t opsize;
300 if(len) {
301 //Verbatim block.
302 readstride = 1;
303 roffset = poffset + 5;
304 opsize = 5 + len;
305 } else {
306 //RLE block. Read real size first.
307 len = readval(patch, poffset + 5, 2);
308 readstride = 0;
309 roffset = poffset + 7;
310 opsize = 8;
312 for(uint64_t i = 0; i < len; i++) {
313 int64_t baddr = addr + i + offset;
314 if(baddr < 0) {
315 if(!warned_negative)
316 std::cerr << "WARNING: IPS patch tries to modify negative offset. "
317 << "Bad patch or offset?" << std::endl;
318 warned_negative = true;
319 continue;
320 } else if(baddr >= static_cast<int64_t>(data.size())) {
321 if(!warned_extend)
322 std::cerr << "WARNING: IPS patch tries to extend the ROM. "
323 << "Bad patch or offset? " << std::endl;
324 warned_extend = true;
325 size_t oldsize = data.size();
326 data.resize(baddr + 1);
327 for(size_t j = oldsize; j <= static_cast<uint64_t>(baddr); j++)
328 data[j] = 0;
330 size_t srcoff = roffset + readstride * i;
331 if(srcoff >= patch.size())
332 throw std::runtime_error("Corrupt IPS patch");
333 data[baddr] = static_cast<uint8_t>(patch[srcoff]);
335 poffset += opsize;
337 //Mark the slot as valid and update hash.
338 valid = true;
339 sha256 = sha256::hash(data);
340 if(xml) {
341 size_t osize = data.size();
342 data.resize(osize + 1);
343 data[osize] = 0;
347 rom_files::rom_files() throw()
349 rtype = ROMTYPE_NONE;
350 region = REGION_AUTO;
354 rom_files::rom_files(const std::vector<std::string>& cmdline) throw(std::bad_alloc, std::runtime_error)
356 rom = rom_xml = slota = slota_xml = slotb = slotb_xml = "";
357 std::string arr[sizeof(romtypes_to_recognize) / sizeof(romtypes_to_recognize[0])];
358 unsigned long flags = 0;
359 for(size_t i = 0; i < sizeof(romtypes_to_recognize) / sizeof(romtypes_to_recognize[0]); i++) {
360 arr[i] = findoption(cmdline, romtypes_to_recognize[i]);
361 if(arr[i] != "")
362 flags |= (1L << i);
364 rtype = recognize_platform(flags);
365 for(size_t i = 0; i < sizeof(romtypes_to_recognize) / sizeof(romtypes_to_recognize[0]); i++) {
366 if(arr[i] != "")
367 switch(recognize_commandline_rom(rtype, romtypes_to_recognize[i])) {
368 case 0: rom = arr[i]; break;
369 case 1: rom_xml = arr[i]; break;
370 case 2: slota = arr[i]; break;
371 case 3: slota_xml = arr[i]; break;
372 case 4: slotb = arr[i]; break;
373 case 5: slotb_xml = arr[i]; break;
376 region = (rtype == ROMTYPE_SGB || rtype == ROMTYPE_SNES) ? REGION_AUTO : REGION_NTSC;
377 if(option_set(cmdline, "--ntsc"))
378 region = REGION_NTSC;
379 else if(option_set(cmdline, "--pal"))
380 region = REGION_PAL;
382 base_file = "";
385 void rom_files::resolve_relative() throw(std::bad_alloc, std::runtime_error)
387 rom = resolve_file_relative(rom, base_file);
388 rom_xml = resolve_file_relative(rom_xml, base_file);
389 slota = resolve_file_relative(slota, base_file);
390 slota_xml = resolve_file_relative(slota_xml, base_file);
391 slotb = resolve_file_relative(slotb, base_file);
392 slotb_xml = resolve_file_relative(slotb_xml, base_file);
393 base_file = "";
397 std::pair<enum rom_type, enum rom_region> get_current_rom_info() throw()
399 return std::make_pair(current_rom_type, current_region);
402 loaded_rom::loaded_rom() throw()
404 rtype = ROMTYPE_NONE;
405 region = orig_region = REGION_AUTO;
408 loaded_rom::loaded_rom(const rom_files& files) throw(std::bad_alloc, std::runtime_error)
410 std::string _slota = files.slota;
411 std::string _slota_xml = files.slota_xml;
412 std::string _slotb = files.slotb;
413 std::string _slotb_xml = files.slotb_xml;
414 if(files.rtype == ROMTYPE_NONE) {
415 rtype = ROMTYPE_NONE;
416 region = orig_region = files.region;
417 return;
419 if((_slota != "" || _slota_xml != "") && files.rtype == ROMTYPE_SNES) {
420 window::out() << "WARNING: SNES takes only 1 ROM image" << std::endl;
421 _slota = "";
422 _slota_xml = "";
424 if((_slotb != "" || _slotb_xml != "") && files.rtype != ROMTYPE_SUFAMITURBO) {
425 window::out() << "WARNING: Only Sufami Turbo takes 3 ROM images" << std::endl;
426 _slotb = "";
427 _slotb_xml = "";
429 if(files.rom_xml != "" && files.rom == "")
430 window::out() << "WARNING: " << name_subrom(files.rtype, 0) << " specified without corresponding "
431 << name_subrom(files.rtype, 1) << std::endl;
432 if(_slota_xml != "" && _slota == "")
433 window::out() << "WARNING: " << name_subrom(files.rtype, 2) << " specified without corresponding "
434 << name_subrom(files.rtype, 3) << std::endl;
435 if(_slotb_xml != "" && _slotb == "")
436 window::out() << "WARNING: " << name_subrom(files.rtype, 4) << " specified without corresponding "
437 << name_subrom(files.rtype, 5) << std::endl;
439 rtype = files.rtype;
440 rom = loaded_slot(files.rom, files.base_file);
441 rom_xml = loaded_slot(files.rom_xml, files.base_file, true);
442 slota = loaded_slot(_slota, files.base_file);
443 slota_xml = loaded_slot(_slota_xml, files.base_file, true);
444 slotb = loaded_slot(_slotb, files.base_file);
445 slotb_xml = loaded_slot(_slotb_xml, files.base_file, true);
446 orig_region = region = files.region;
449 void loaded_rom::load() throw(std::bad_alloc, std::runtime_error)
451 current_rom_type = ROMTYPE_NONE;
452 if(region == REGION_AUTO && orig_region != REGION_AUTO)
453 region = orig_region;
454 if(region != orig_region && orig_region != REGION_AUTO)
455 throw std::runtime_error("Trying to force incompatible region");
456 if(rtype == ROMTYPE_NONE)
457 throw std::runtime_error("Can't insert cartridge of type NONE!");
458 switch(region) {
459 case REGION_AUTO:
460 config.region = System::Region::Autodetect;
461 break;
462 case REGION_NTSC:
463 config.region = System::Region::NTSC;
464 break;
465 case REGION_PAL:
466 config.region = System::Region::PAL;
467 break;
468 default:
469 throw std::runtime_error("Trying to force unknown region");
471 switch(rtype) {
472 case ROMTYPE_SNES:
473 if(!snes_load_cartridge_normal(rom_xml, rom, rom))
474 throw std::runtime_error("Can't load cartridge ROM");
475 break;
476 case ROMTYPE_BSX:
477 if(region == REGION_PAL)
478 throw std::runtime_error("BSX can't be PAL");
479 if(!snes_load_cartridge_bsx(rom_xml, rom, rom, slota_xml, slota, slota))
480 throw std::runtime_error("Can't load cartridge ROM");
481 break;
482 case ROMTYPE_BSXSLOTTED:
483 if(region == REGION_PAL)
484 throw std::runtime_error("Slotted BSX can't be PAL");
485 if(!snes_load_cartridge_bsx_slotted(rom_xml, rom, rom, slota_xml, slota, slota))
486 throw std::runtime_error("Can't load cartridge ROM");
487 break;
488 case ROMTYPE_SGB:
489 if(!snes_load_cartridge_super_game_boy(rom_xml, rom, rom, slota_xml, slota, slota))
490 throw std::runtime_error("Can't load cartridge ROM");
491 break;
492 case ROMTYPE_SUFAMITURBO:
493 if(region == REGION_PAL)
494 throw std::runtime_error("Sufami Turbo can't be PAL");
495 if(!snes_load_cartridge_sufami_turbo(rom_xml, rom, rom, slota_xml, slota, slota, slotb_xml, slotb,
496 slotb))
497 throw std::runtime_error("Can't load cartridge ROM");
498 break;
499 default:
500 throw std::runtime_error("Unknown cartridge type");
502 if(region == REGION_AUTO)
503 region = snes_get_region() ? REGION_PAL : REGION_NTSC;
504 snes_power();
505 current_rom_type = rtype;
506 current_region = region;
507 refresh_cart_mappings();
510 void loaded_rom::do_patch(const std::vector<std::string>& cmdline) throw(std::bad_alloc,
511 std::runtime_error)
513 int32_t offset = 0;
514 for(auto i = cmdline.begin(); i != cmdline.end(); i++) {
515 std::string opt = *i;
516 if(opt.length() >= 13 && opt.substr(0, 13) == "--ips-offset=") {
517 try {
518 offset = parse_value<int32_t>(opt.substr(13));
519 } catch(std::exception& e) {
520 throw std::runtime_error("Invalid IPS offset option '" + opt + "': " + e.what());
522 continue;
524 if(opt.length() < 6 || opt.substr(0, 6) != "--ips-")
525 continue;
526 size_t split = opt.find_first_of("=");
527 if(split > opt.length())
528 throw std::runtime_error("Invalid IPS patch argument '" + opt + "'");
529 std::string kind = opt.substr(6, split - 6);
530 std::string filename = opt.substr(split + 1);
531 window::out() << "Patching " << kind << " using '" << filename << "'" << std::endl;
532 std::vector<char> ips;
533 try {
534 ips = read_file_relative(filename, "");
535 } catch(std::bad_alloc& e) {
536 OOM_panic();
537 } catch(std::exception& e) {
538 throw std::runtime_error("Can't read IPS '" + filename + "': " + e.what());
540 try {
541 switch(recognize_commandline_rom(rtype, kind)) {
542 case 0: rom.patch(ips, offset); break;
543 case 1: rom_xml.patch(ips, offset); break;
544 case 2: slota.patch(ips, offset); break;
545 case 3: slota_xml.patch(ips, offset); break;
546 case 4: slotb.patch(ips, offset); break;
547 case 5: slotb_xml.patch(ips, offset); break;
548 default:
549 throw std::runtime_error("Invalid subROM '" + kind + "' to patch");
551 } catch(std::bad_alloc& e) {
552 OOM_panic();
553 } catch(std::exception& e) {
554 throw std::runtime_error("Can't Patch with IPS '" + filename + "': " + e.what());
559 namespace
561 std::string sram_name(const nall::string& _id, Cartridge::Slot slotname)
563 std::string id(_id, _id.length());
564 if(slotname == Cartridge::Slot::SufamiTurboA)
565 return "slota." + id.substr(1);
566 if(slotname == Cartridge::Slot::SufamiTurboB)
567 return "slotb." + id.substr(1);
568 return id.substr(1);
572 std::map<std::string, std::vector<char>> save_sram() throw(std::bad_alloc)
574 std::map<std::string, std::vector<char>> out;
575 for(unsigned i = 0; i < cartridge.nvram.size(); i++) {
576 Cartridge::NonVolatileRAM& r = cartridge.nvram[i];
577 std::string savename = sram_name(r.id, r.slot);
578 std::vector<char> x;
579 x.resize(r.size);
580 memcpy(&x[0], r.data, r.size);
581 out[savename] = x;
583 return out;
586 void load_sram(std::map<std::string, std::vector<char>>& sram) throw(std::bad_alloc)
588 std::set<std::string> used;
589 if(sram.empty())
590 return;
591 for(unsigned i = 0; i < cartridge.nvram.size(); i++) {
592 Cartridge::NonVolatileRAM& r = cartridge.nvram[i];
593 std::string savename = sram_name(r.id, r.slot);
594 if(sram.count(savename)) {
595 std::vector<char>& x = sram[savename];
596 if(r.size != x.size())
597 window::out() << "WARNING: SRAM '" << savename << "': Loaded " << x.size()
598 << " bytes, but the SRAM is " << r.size << "." << std::endl;
599 memcpy(r.data, &x[0], (r.size < x.size()) ? r.size : x.size());
600 used.insert(savename);
601 } else
602 window::out() << "WARNING: SRAM '" << savename << ": No data." << std::endl;
604 for(auto i = sram.begin(); i != sram.end(); ++i)
605 if(!used.count(i->first))
606 window::out() << "WARNING: SRAM '" << i->first << ": Not found on cartridge." << std::endl;
609 std::map<std::string, std::vector<char>> load_sram_commandline(const std::vector<std::string>& cmdline)
610 throw(std::bad_alloc, std::runtime_error)
612 std::map<std::string, std::vector<char>> ret;
613 for(auto i = cmdline.begin(); i != cmdline.end(); i++) {
614 std::string opt = *i;
615 if(opt.length() >= 11 && opt.substr(0, 11) == "--continue=") {
616 size_t split = opt.find_first_of("=");
617 if(split > opt.length() - 1)
618 throw std::runtime_error("Bad SRAM option '" + opt + "'");
619 std::string file = opt.substr(split + 1);
620 zip_reader r(file);
621 for(auto j = r.begin(); j != r.end(); j++) {
622 std::string fname = *j;
623 if(fname.length() < 6 || fname.substr(0, 5) != "sram.")
624 continue;
625 std::istream& x = r[fname];
626 try {
627 std::vector<char> out;
628 boost::iostreams::back_insert_device<std::vector<char>> rd(out);
629 boost::iostreams::copy(x, rd);
630 delete &x;
631 ret[fname.substr(5, split - 5)] = out;
632 } catch(...) {
633 delete &x;
634 throw;
637 continue;
639 if(opt.length() < 8 || opt.substr(0, 7) != "--sram-")
640 continue;
641 size_t split = opt.find_first_of("=");
642 if(split > opt.length() - 1)
643 throw std::runtime_error("Bad SRAM option '" + opt + "'");
644 std::string kind = opt.substr(7, split - 7);
645 std::string file = opt.substr(split + 1);
646 if(kind == "")
647 throw std::runtime_error("Bad SRAM option '" + opt + "'");
648 try {
649 ret[kind] = read_file_relative(file, "");
650 } catch(std::bad_alloc& e) {
651 throw;
652 } catch(std::runtime_error& e) {
653 throw std::runtime_error("Can't load SRAM '" + kind + "': " + e.what());
656 return ret;
659 std::vector<char> save_core_state() throw(std::bad_alloc)
661 SNES::system.runtosave();
662 std::vector<char> ret;
663 serializer s = SNES::system.serialize();
664 ret.resize(s.size());
665 memcpy(&ret[0], s.data(), s.size());
666 size_t offset = ret.size();
667 unsigned char tmp[32];
668 sha256::hash(tmp, ret);
669 ret.resize(offset + 32);
670 memcpy(&ret[offset], tmp, 32);
671 return ret;
674 void load_core_state(const std::vector<char>& buf) throw(std::runtime_error)
676 if(buf.size() < 32)
677 throw std::runtime_error("Savestate corrupt");
678 unsigned char tmp[32];
679 sha256::hash(tmp, reinterpret_cast<const uint8_t*>(&buf[0]), buf.size() - 32);
680 if(memcmp(tmp, &buf[buf.size() - 32], 32))
681 throw std::runtime_error("Savestate corrupt");
682 serializer s(reinterpret_cast<const uint8_t*>(&buf[0]), buf.size() - 32);
683 if(!SNES::system.unserialize(s))
684 throw std::runtime_error("SNES core rejected savestate");
687 namespace
689 struct index_entry
691 std::string hash;
692 std::string relpath;
693 std::string from;
695 std::list<index_entry> rom_index;
697 void replace_index(std::list<index_entry> new_index, const std::string& source)
699 std::list<index_entry> tmp_index;
700 for(auto i = rom_index.begin(); i != rom_index.end(); i++) {
701 if(i->from != source)
702 tmp_index.push_back(*i);
704 for(auto i = new_index.begin(); i != new_index.end(); i++) {
705 tmp_index.push_back(*i);
707 rom_index = new_index;
711 void load_index_file(const std::string& filename) throw(std::bad_alloc, std::runtime_error)
713 std::istream& s = open_file_relative(filename, "");
715 try {
716 std::list<index_entry> partial_index;
717 std::string line;
718 while(std::getline(s, line)) {
719 index_entry e;
720 if(line == "")
721 continue;
722 tokensplitter t(line);
723 e.hash = static_cast<std::string>(t);
724 e.relpath = t.tail();
725 e.from = filename;
726 if(e.hash.length() != 64 || e.relpath == "")
727 throw std::runtime_error("Bad index file");
728 partial_index.push_back(e);
730 replace_index(partial_index, filename);
731 } catch(...) {
732 delete &s;
733 throw;
735 delete &s;
738 std::string lookup_file_by_sha256(const std::string& hash) throw(std::bad_alloc, std::runtime_error)
740 if(hash == "")
741 return "";
742 for(auto i = rom_index.begin(); i != rom_index.end(); i++) {
743 if(i->hash != hash)
744 continue;
745 try {
746 std::istream& o = open_file_relative(i->relpath, i->from);
747 delete &o;
748 return resolve_file_relative(i->relpath, i->from);
749 } catch(...) {
750 continue;
753 throw std::runtime_error("No file with hash '" + hash + "' found in known indices");
756 std::string name_subrom(enum rom_type major, unsigned romnumber) throw(std::bad_alloc)
758 if(romnumber == 0)
759 return "ROM";
760 else if(romnumber == 1)
761 return "ROM XML";
762 else if(major == ROMTYPE_BSX && romnumber == 2)
763 return "BSX ROM";
764 else if(major == ROMTYPE_BSX && romnumber == 3)
765 return "BSX XML";
766 else if(major == ROMTYPE_BSXSLOTTED && romnumber == 2)
767 return "BSX ROM";
768 else if(major == ROMTYPE_BSXSLOTTED && romnumber == 3)
769 return "BSX XML";
770 else if(major == ROMTYPE_SGB && romnumber == 2)
771 return "DMG ROM";
772 else if(major == ROMTYPE_SGB && romnumber == 3)
773 return "DMG XML";
774 else if(major == ROMTYPE_SUFAMITURBO && romnumber == 2)
775 return "SLOT A ROM";
776 else if(major == ROMTYPE_SUFAMITURBO && romnumber == 3)
777 return "SLOT A XML";
778 else if(major == ROMTYPE_SUFAMITURBO && romnumber == 4)
779 return "SLOT B ROM";
780 else if(major == ROMTYPE_SUFAMITURBO && romnumber == 5)
781 return "SLOT B XML";
782 else if(romnumber % 2)
783 return "UNKNOWN XML";
784 else
785 return "UNKNOWN ROM";
789 int recognize_commandline_rom(enum rom_type major, const std::string& romname) throw(std::bad_alloc)
791 if(romname == romtypes_to_recognize[0])
792 return 0;
793 else if(romname == romtypes_to_recognize[6])
794 return 1;
795 else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[1])
796 return 2;
797 else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[7])
798 return 3;
799 else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[2])
800 return 2;
801 else if(major == ROMTYPE_BSX && romname == romtypes_to_recognize[8])
802 return 3;
803 else if(major == ROMTYPE_SGB && romname == romtypes_to_recognize[3])
804 return 2;
805 else if(major == ROMTYPE_SGB && romname == romtypes_to_recognize[9])
806 return 3;
807 else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[4])
808 return 2;
809 else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[10])
810 return 3;
811 else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[5])
812 return 4;
813 else if(major == ROMTYPE_SUFAMITURBO && romname == romtypes_to_recognize[11])
814 return 5;
815 else
816 return -1;
819 rom_type recognize_platform(unsigned long flags) throw(std::bad_alloc, std::runtime_error)
821 if((flags & 07700) >> 6 & ~(flags & 077))
822 throw std::runtime_error("SubROM XML specified without corresponding subROM");
823 if((flags & 1) == 0)
824 throw std::runtime_error("No SNES main cartridge ROM specified");
825 if((flags & 077) == 1)
826 return ROMTYPE_SNES;
827 if((flags & 077) == 3)
828 return ROMTYPE_BSX;
829 if((flags & 077) == 5)
830 return ROMTYPE_BSXSLOTTED;
831 if((flags & 077) == 9)
832 return ROMTYPE_SGB;
833 if((flags & 060) != 0 && (flags & 017) == 1)
834 return ROMTYPE_SUFAMITURBO;
835 throw std::runtime_error("Not valid combination of rom/bsx/bsxslotted/dmg/slot-a/slot-b");