/***************************************************************************
 *   Copyright (C) 2007-2010 by Sindre Aamås                               *
 *   sinamas@users.sourceforge.net                                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License version 2 as     *
 *   published by the Free Software Foundation.                            *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License version 2 for more details.                *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   version 2 along with this program; if not, write to the               *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "cartridge.h"
#include "file/file.h"
#include "../savestate.h"
#include "pakinfo_internal.h"
#include <cstring>
#include <fstream>

//
// Modified 2012-07-10 to 2012-07-14 by H. Ilari Liusvaara
//	- Make it rerecording-friendly.

namespace gambatte {

namespace {

static unsigned toMulti64Rombank(unsigned rombank) {
	return (rombank >> 1 & 0x30) | (rombank & 0xF);
}

class DefaultMbc : public Mbc {
public:
	virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const {
		return (addr< 0x4000) == (bank == 0);
	}
	void loadOrSave(loadsave& state) {
	}
};

class Mbc0 : public DefaultMbc {
public:
	explicit Mbc0(MemPtrs &memptrs)
	: memptrs_(memptrs)
	, enableRam_(false)
	{
	}

	virtual void romWrite(unsigned const p, unsigned const data) {
		if (p < 0x2000) {
			enableRam_ = (data & 0xF) == 0xA;
			memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0, 0);
		}
	}

	virtual void saveState(SaveState::Mem &ss) const {
		ss.enableRam = enableRam_;
	}

	virtual void loadState(SaveState::Mem const &ss) {
		enableRam_ = ss.enableRam;
		memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0, 0);
	}
	void loadOrSave(loadsave& state) {
		state(enableRam_);
	}

private:
	MemPtrs &memptrs_;
	bool enableRam_;
};

static inline unsigned rambanks(MemPtrs const &memptrs) {
	return std::size_t(memptrs.rambankdataend() - memptrs.rambankdata()) / 0x2000;
}

static inline unsigned rombanks(MemPtrs const &memptrs) {
	return std::size_t(memptrs.romdataend()     - memptrs.romdata()    ) / 0x4000;
}

class Mbc1 : public DefaultMbc {
public:
	explicit Mbc1(MemPtrs &memptrs)
	: memptrs_(memptrs)
	, rombank_(1)
	, rambank_(0)
	, enableRam_(false)
	, rambankMode_(false)
	{
	}

	virtual void romWrite(unsigned const p, unsigned const data) {
		switch (p >> 13 & 3) {
		case 0:
			enableRam_ = (data & 0xF) == 0xA;
			setRambank();
			break;
		case 1:
			rombank_ = rambankMode_ ? data & 0x1F : (rombank_ & 0x60) | (data & 0x1F);
			setRombank();
			break;
		case 2:
			if (rambankMode_) {
				rambank_ = data & 3;
				setRambank();
			} else {
				rombank_ = (data << 5 & 0x60) | (rombank_ & 0x1F);
				setRombank();
			}

			break;
		case 3:
			// Pretty sure this should take effect immediately, but I have a policy not to change old behavior
			// unless I have something (eg. a verified test or a game) that justifies it.
			rambankMode_ = data & 1;
			break;
		}
	}

	virtual void saveState(SaveState::Mem &ss) const {
		ss.rombank = rombank_;
		ss.rambank = rambank_;
		ss.enableRam = enableRam_;
		ss.rambankMode = rambankMode_;
	}

	virtual void loadState(SaveState::Mem const &ss) {
		rombank_ = ss.rombank;
		rambank_ = ss.rambank;
		enableRam_ = ss.enableRam;
		rambankMode_ = ss.rambankMode;
		setRambank();
		setRombank();
	}

	void loadOrSave(loadsave& state) {
		state(rombank_);
		state(rambank_);
		state(enableRam_);
		state(rambankMode_);
	}
private:
	MemPtrs &memptrs_;
	unsigned char rombank_;
	unsigned char rambank_;
	bool enableRam_;
	bool rambankMode_;

	static unsigned adjustedRombank(unsigned bank) { return bank & 0x1F ? bank : bank | 1; }

	void setRambank() const {
		memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0,
		                    rambank_ & (rambanks(memptrs_) - 1));
	}

	void setRombank() const { memptrs_.setRombank(adjustedRombank(rombank_) & (rombanks(memptrs_) - 1)); }
};

class Mbc1Multi64 : public Mbc {
public:
	explicit Mbc1Multi64(MemPtrs &memptrs)
	: memptrs_(memptrs)
	, rombank_(1)
	, enableRam_(false)
	, rombank0Mode_(false)
	{
	}

	virtual void romWrite(unsigned const p, unsigned const data) {
		switch (p >> 13 & 3) {
		case 0:
			enableRam_ = (data & 0xF) == 0xA;
			memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0, 0);
			break;
		case 1:
			rombank_ = (rombank_   & 0x60) | (data    & 0x1F);
			memptrs_.setRombank(rombank0Mode_
				? adjustedRombank(toMulti64Rombank(rombank_))
				: adjustedRombank(rombank_) & (rombanks(memptrs_) - 1));
			break;
		case 2:
			rombank_ = (data << 5 & 0x60) | (rombank_ & 0x1F);
			setRombank();
			break;
		case 3:
			rombank0Mode_ = data & 1;
			setRombank();
			break;
		}
	}

	virtual void saveState(SaveState::Mem &ss) const {
		ss.rombank = rombank_;
		ss.enableRam = enableRam_;
		ss.rambankMode = rombank0Mode_;
	}

	virtual void loadState(SaveState::Mem const &ss) {
		rombank_ = ss.rombank;
		enableRam_ = ss.enableRam;
		rombank0Mode_ = ss.rambankMode;
		memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0, 0);
		setRombank();
	}

	virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const {
		return (addr < 0x4000) == ((bank & 0xF) == 0);
	}
	void loadOrSave(loadsave& state) {
		state(rombank_);
		state(enableRam_);
		state(rombank0Mode_);
	}

private:
	MemPtrs &memptrs_;
	unsigned char rombank_;
	bool enableRam_;
	bool rombank0Mode_;

	static unsigned adjustedRombank(unsigned bank) { return bank & 0x1F ? bank : bank | 1; }

	void setRombank() const {
		if (rombank0Mode_) {
			unsigned const rb = toMulti64Rombank(rombank_);
			memptrs_.setRombank0(rb & 0x30);
			memptrs_.setRombank(adjustedRombank(rb));
		} else {
			memptrs_.setRombank0(0);
			memptrs_.setRombank(adjustedRombank(rombank_) & (rombanks(memptrs_) - 1));
		}
	}
};

class Mbc2 : public DefaultMbc {
public:
	explicit Mbc2(MemPtrs &memptrs)
	: memptrs_(memptrs)
	, rombank_(1)
	, enableRam_(false)
	{
	}

	virtual void romWrite(unsigned const p, unsigned const data) {
		switch (p & 0x6100) {
		case 0x0000:
			enableRam_ = (data & 0xF) == 0xA;
			memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0, 0);
			break;
		case 0x2100:
			rombank_ = data & 0xF;
			memptrs_.setRombank(rombank_ & (rombanks(memptrs_) - 1));
			break;
		}
	}

	virtual void saveState(SaveState::Mem &ss) const {
		ss.rombank = rombank_;
		ss.enableRam = enableRam_;
	}

	virtual void loadState(SaveState::Mem const &ss) {
		rombank_ = ss.rombank;
		enableRam_ = ss.enableRam;
		memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0, 0);
		memptrs_.setRombank(rombank_ & (rombanks(memptrs_) - 1));
	}
	void loadOrSave(loadsave& state) {
		state(rombank_);
		state(enableRam_);
	}

private:
	MemPtrs &memptrs_;
	unsigned char rombank_;
	bool enableRam_;
};

class Mbc3 : public DefaultMbc {
public:
	Mbc3(MemPtrs &memptrs, Rtc *const rtc)
	: memptrs_(memptrs)
	, rtc_(rtc)
	, rombank_(1)
	, rambank_(0)
	, enableRam_(false)
	{
	}

	virtual void romWrite(unsigned const p, unsigned const data) {
		unsigned abank;
		switch (p >> 13 & 3) {
		case 0:
			enableRam_ = (data & 0xF) == 0xA;
			setRambank();
			break;
		case 1:
			abank = rombank_ = data & 0x7F;
			if(!abank) abank = 1;  //Fix pokemon Red.
			memptrs_.setRombank(abank & (rombanks(memptrs_) - 1));
			break;
		case 2:
			rambank_ = data;
			setRambank();
			break;
		case 3:
			if (rtc_)
				rtc_->latch(data);

			break;
		}
	}

	virtual void saveState(SaveState::Mem &ss) const {
		ss.rombank = rombank_;
		ss.rambank = rambank_;
		ss.enableRam = enableRam_;
	}

	virtual void loadState(SaveState::Mem const &ss) {
		unsigned abank;
		rombank_ = ss.rombank;
		rambank_ = ss.rambank;
		enableRam_ = ss.enableRam;
		setRambank();
		abank = rombank_;
		if(!abank) abank = 1;  //Fix pokemon Red.
		memptrs_.setRombank(abank & (rombanks(memptrs_) - 1));
	}
	void loadOrSave(loadsave& state) {
		if(rtc_) rtc_->loadOrSave(state);
		state(rombank_);
		state(rambank_);
		state(enableRam_);
	}
private:
	MemPtrs &memptrs_;
	Rtc *const rtc_;
	unsigned char rombank_;
	unsigned char rambank_;
	bool enableRam_;

	void setRambank() const {
		unsigned flags = enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0;

		if (rtc_) {
			rtc_->set(enableRam_, rambank_);

			if (rtc_->activeData())
				flags |= MemPtrs::rtc_en;
		}

		memptrs_.setRambank(flags, rambank_ & (rambanks(memptrs_) - 1));
	}
};

class HuC1 : public DefaultMbc {
public:
	explicit HuC1(MemPtrs &memptrs)
	: memptrs_(memptrs)
	, rombank_(1)
	, rambank_(0)
	, enableRam_(false)
	, rambankMode_(false)
	{
	}

	virtual void romWrite(unsigned const p, unsigned const data) {
		switch (p >> 13 & 3) {
		case 0:
			enableRam_ = (data & 0xF) == 0xA;
			setRambank();
			break;
		case 1:
			rombank_ = data & 0x3F;
			setRombank();
			break;
		case 2:
			rambank_ = data & 3;
			rambankMode_ ? setRambank() : setRombank();
			break;
		case 3:
			rambankMode_ = data & 1;
			setRambank();
			setRombank();
			break;
		}
	}

	virtual void saveState(SaveState::Mem &ss) const {
		ss.rombank = rombank_;
		ss.rambank = rambank_;
		ss.enableRam = enableRam_;
		ss.rambankMode = rambankMode_;
	}

	virtual void loadState(SaveState::Mem const &ss) {
		rombank_ = ss.rombank;
		rambank_ = ss.rambank;
		enableRam_ = ss.enableRam;
		rambankMode_ = ss.rambankMode;
		setRambank();
		setRombank();
	}
	void loadOrSave(loadsave& state) {
		state(rombank_);
		state(rambank_);
		state(enableRam_);
		state(rambankMode_);
	}
private:
	MemPtrs &memptrs_;
	unsigned char rombank_;
	unsigned char rambank_;
	bool enableRam_;
	bool rambankMode_;

	void setRambank() const {
		memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : MemPtrs::read_en,
		                    rambankMode_ ? rambank_ & (rambanks(memptrs_) - 1) : 0);
	}

	void setRombank() const {
		memptrs_.setRombank((rambankMode_ ? rombank_ : rambank_ << 6 | rombank_)
		                  & (rombanks(memptrs_) - 1));
	}
};

class Mbc5 : public DefaultMbc {
public:
	explicit Mbc5(MemPtrs &memptrs)
	: memptrs_(memptrs)
	, rombank_(1)
	, rambank_(0)
	, enableRam_(false)
	{
	}

	virtual void romWrite(unsigned const p, unsigned const data) {
		switch (p >> 13 & 3) {
		case 0:
			enableRam_ = (data & 0xF) == 0xA;
			setRambank();
			break;
		case 1:
			rombank_ = p < 0x3000
			         ? (rombank_  & 0x100) |  data
			         : (data << 8 & 0x100) | (rombank_ & 0xFF);
			setRombank();
			break;
		case 2:
			rambank_ = data & 0xF;
			setRambank();
			break;
		case 3:
			break;
		}
	}

	virtual void saveState(SaveState::Mem &ss) const {
		ss.rombank = rombank_;
		ss.rambank = rambank_;
		ss.enableRam = enableRam_;
	}

	virtual void loadState(SaveState::Mem const &ss) {
		rombank_ = ss.rombank;
		rambank_ = ss.rambank;
		enableRam_ = ss.enableRam;
		setRambank();
		setRombank();
	}
	void loadOrSave(loadsave& state) {
		state(rombank_);
		state(rambank_);
		state(enableRam_);
	}

private:
	MemPtrs &memptrs_;
	unsigned short rombank_;
	unsigned char rambank_;
	bool enableRam_;

	static unsigned adjustedRombank(unsigned bank) { return bank ? bank : 1; }

	void setRambank() const {
		memptrs_.setRambank(enableRam_ ? MemPtrs::read_en | MemPtrs::write_en : 0,
		                    rambank_ & (rambanks(memptrs_) - 1));
	}

	void setRombank() const { memptrs_.setRombank(adjustedRombank(rombank_) & (rombanks(memptrs_) - 1)); }
};

static bool hasRtc(unsigned headerByte0x147) {
	switch (headerByte0x147) {
	case 0x0F:
	case 0x10: return true;
	default: return false;
	}
}

}

void Cartridge::setStatePtrs(SaveState &state) {
	state.mem.vram.set(memptrs_.vramdata(), memptrs_.vramdataend() - memptrs_.vramdata());
	state.mem.sram.set(memptrs_.rambankdata(), memptrs_.rambankdataend() - memptrs_.rambankdata());
	state.mem.wram.set(memptrs_.wramdata(0), memptrs_.wramdataend() - memptrs_.wramdata(0));
}

void Cartridge::saveState(SaveState &state) const {
	mbc_->saveState(state.mem);
	rtc_.saveState(state);
}

void Cartridge::loadState(SaveState const &state) {
	rtc_.loadState(state);
	mbc_->loadState(state.mem);
}

static std::string const stripExtension(std::string const &str) {
	std::string::size_type const lastDot = str.find_last_of('.');
	std::string::size_type const lastSlash = str.find_last_of('/');

	if (lastDot != std::string::npos && (lastSlash == std::string::npos || lastSlash < lastDot))
		return str.substr(0, lastDot);

	return str;
}

static std::string const stripDir(std::string const &str) {
	std::string::size_type const lastSlash = str.find_last_of('/');
	if (lastSlash != std::string::npos)
		return str.substr(lastSlash + 1);

	return str;
}

std::string const Cartridge::saveBasePath() const {
	return saveDir_.empty()
	     ? defaultSaveBasePath_
	     : saveDir_ + stripDir(defaultSaveBasePath_);
}

void Cartridge::setSaveDir(std::string const &dir) {
	saveDir_ = dir;
	if (!saveDir_.empty() && saveDir_[saveDir_.length() - 1] != '/')
		saveDir_ += '/';
}

static void enforce8bit(unsigned char *data, std::size_t size) {
	if (static_cast<unsigned char>(0x100))
		while (size--)
			*data++ &= 0xFF;
}

static unsigned pow2ceil(unsigned n) {
	--n;
	n |= n >> 1;
	n |= n >> 2;
	n |= n >> 4;
	n |= n >> 8;
	++n;

	return n;
}

static bool presumedMulti64Mbc1(unsigned char const header[], unsigned rombanks) {
	return header[0x147] == 1 && header[0x149] == 0 && rombanks == 64;
}

LoadRes Cartridge::loadROM(std::string const &romfile,
                           bool const forceDmg,
                           bool const multicartCompat)
{
	scoped_ptr<File> const rom(newFileInstance(romfile));
	return loadROM(rom.get(), forceDmg, multicartCompat, romfile, 0);
}

LoadRes Cartridge::loadROM(const unsigned char* image, size_t isize, const bool forceDmg, const bool multicartCompat,
	size_t boot_rom_size) {
	const scoped_ptr<File> rom(newFileInstance(image, isize));
	return loadROM(rom.get(), forceDmg, multicartCompat, "", boot_rom_size);
}

LoadRes Cartridge::loadROM(File* rom, const bool forceDmg, const bool multicartCompat, const std::string& filename,
	size_t boot_rom_size) {
	if (rom->fail())
		return LOADRES_IO_ERROR;

	enum Cartridgetype { type_plain,
	                     type_mbc1,
	                     type_mbc2,
	                     type_mbc3,
	                     type_mbc5,
	                     type_huc1 };
	Cartridgetype type = type_plain;
	unsigned rambanks = 1;
	unsigned rombanks = 2;
	bool cgb = false;

	{
		unsigned char header[0x150];
		rom->read(reinterpret_cast<char *>(header), sizeof header);

		switch (header[0x0147]) {
		case 0x00: type = type_plain; break;
		case 0x01:
		case 0x02:
		case 0x03: type = type_mbc1; break;
		case 0x05:
		case 0x06: type = type_mbc2; break;
		case 0x08:
		case 0x09: type = type_plain; break;
		case 0x0B:
		case 0x0C:
		case 0x0D: return LOADRES_UNSUPPORTED_MBC_MMM01;
		case 0x0F:
		case 0x10:
		case 0x11:
		case 0x12:
		case 0x13: type = type_mbc3; break;
		case 0x15:
		case 0x16:
		case 0x17: return LOADRES_UNSUPPORTED_MBC_MBC4;
		case 0x19:
		case 0x1A:
		case 0x1B:
		case 0x1C:
		case 0x1D:
		case 0x1E: type = type_mbc5; break;
		case 0xFC: return LOADRES_UNSUPPORTED_MBC_POCKET_CAMERA;
		case 0xFD: return LOADRES_UNSUPPORTED_MBC_TAMA5;
		case 0xFE: return LOADRES_UNSUPPORTED_MBC_HUC3;
		case 0xFF: type = type_huc1; break;
		default:   return LOADRES_BAD_FILE_OR_UNKNOWN_MBC;
		}

		/*switch (header[0x0148]) {
		case 0x00: rombanks = 2; break;
		case 0x01: rombanks = 4; break;
		case 0x02: rombanks = 8; break;
		case 0x03: rombanks = 16; break;
		case 0x04: rombanks = 32; break;
		case 0x05: rombanks = 64; break;
		case 0x06: rombanks = 128; break;
		case 0x07: rombanks = 256; break;
		case 0x08: rombanks = 512; break;
		case 0x52: rombanks = 72; break;
		case 0x53: rombanks = 80; break;
		case 0x54: rombanks = 96; break;
		default: return -1;
		}*/

		rambanks = numRambanksFromH14x(header[0x147], header[0x149]);
		cgb = header[0x0143] >> 7 & (1 ^ forceDmg);
	}

	//Force DMG/CGB if from boot ROM (hack).
	if(boot_rom_size > 0)
		cgb = (boot_rom_size > 256);

	std::size_t const filesize = rom->size();
	rombanks = std::max(pow2ceil(filesize / 0x4000), 2u);

	defaultSaveBasePath_.clear();
	ggUndoList_.clear();
	mbc_.reset();
	memptrs_.reset(rombanks, rambanks, cgb ? 8 : 2);
	rtc_.set(false, 0);

	rom->rewind();
	rom->read(reinterpret_cast<char*>(memptrs_.romdata()), filesize / 0x4000 * 0x4000ul);
	std::memset(memptrs_.romdata() + filesize / 0x4000 * 0x4000ul,
	            0xFF,
	            (rombanks - filesize / 0x4000) * 0x4000ul);
	enforce8bit(memptrs_.romdata(), rombanks * 0x4000ul);

	if (rom->fail())
		return LOADRES_IO_ERROR;

	if(filename != "") {
		defaultSaveBasePath_ = stripExtension(filename);
		memoryCartridge = false;
	} else {
		defaultSaveBasePath_ = "";
		memoryCartridge = true;
	}
	clearMemorySavedData();

	switch (type) {
	case type_plain: mbc_.reset(new Mbc0(memptrs_)); break;
	case type_mbc1:
		if (multicartCompat && presumedMulti64Mbc1(memptrs_.romdata(), rombanks)) {
			mbc_.reset(new Mbc1Multi64(memptrs_));
		} else
			mbc_.reset(new Mbc1(memptrs_));

		break;
	case type_mbc2: mbc_.reset(new Mbc2(memptrs_)); break;
	case type_mbc3:
		mbc_.reset(new Mbc3(memptrs_, hasRtc(memptrs_.romdata()[0x147]) ? &rtc_ : 0));
		break;
	case type_mbc5: mbc_.reset(new Mbc5(memptrs_)); break;
	case type_huc1: mbc_.reset(new HuC1(memptrs_)); break;
	}

	return LOADRES_OK;
}

static bool hasBattery(unsigned char headerByte0x147) {
	switch (headerByte0x147) {
	case 0x03:
	case 0x06:
	case 0x09:
	case 0x0F:
	case 0x10:
	case 0x13:
	case 0x1B:
	case 0x1E:
	case 0xFF: return true;
	default: return false;
	}
}

void Cartridge::loadSavedata() {
	std::string const &sbp = saveBasePath();

	if (hasBattery(memptrs_.romdata()[0x147])) {
		if(memoryCartridge) {
			if(memoryCartridgeSram.size())
				memcpy(memptrs_.rambankdata(), &memoryCartridgeSram[0], memptrs_.rambankdataend() - memptrs_.rambankdata());
		} else {
			std::ifstream file((sbp + ".sav").c_str(), std::ios::binary | std::ios::in);

			if (file.is_open()) {
				file.read(reinterpret_cast<char*>(memptrs_.rambankdata()),
				          memptrs_.rambankdataend() - memptrs_.rambankdata());
				enforce8bit(memptrs_.rambankdata(), memptrs_.rambankdataend() - memptrs_.rambankdata());
			}
		}
	}

	if (hasRtc(memptrs_.romdata()[0x147])) {
		if(memoryCartridge) {
			rtc_.setBaseTime(memoryCartridgeRtcBase);
		} else {
			std::ifstream file((sbp + ".rtc").c_str(), std::ios::binary | std::ios::in);
			if (file) {
				unsigned basetime =    file.get() & 0xFF;
				basetime = basetime << 8 | (file.get() & 0xFF);
				basetime = basetime << 8 | (file.get() & 0xFF);
				basetime = basetime << 8 | (file.get() & 0xFF);
				rtc_.setBaseTime(basetime);
			}
		}
	}
}

void Cartridge::clearMemorySavedData()
{
	memoryCartridgeRtcBase = 0;
	memoryCartridgeSram.resize(0);
}

void Cartridge::saveSavedata() {
	std::string const &sbp = saveBasePath();

	if (hasBattery(memptrs_.romdata()[0x147])) {
		if(memoryCartridge) {
			memoryCartridgeSram.resize(memptrs_.rambankdataend() - memptrs_.rambankdata());
			memcpy(&memoryCartridgeSram[0], memptrs_.rambankdata(), memptrs_.rambankdataend() - memptrs_.rambankdata());
		} else {
			std::ofstream file((sbp + ".sav").c_str(), std::ios::binary | std::ios::out);
			file.write(reinterpret_cast<const char*>(memptrs_.rambankdata()),
			           memptrs_.rambankdataend() - memptrs_.rambankdata());
		}
	}

	if (hasRtc(memptrs_.romdata()[0x147])) {
		if(memoryCartridge) {
			memoryCartridgeRtcBase = rtc_.getBaseTime();
		} else {
			std::ofstream file((sbp + ".rtc").c_str(), std::ios::binary | std::ios::out);
			const unsigned basetime = rtc_.getBaseTime();
			file.put(basetime >> 24 & 0xFF);
			file.put(basetime >> 16 & 0xFF);
			file.put(basetime >>  8 & 0xFF);
			file.put(basetime       & 0xFF);
		}
	}
}

static int asHex(char c) {
	return c >= 'A' ? c - 'A' + 0xA : c - '0';
}

void Cartridge::applyGameGenie(std::string const &code) {
	if (6 < code.length()) {
		unsigned const val = (asHex(code[0]) << 4 | asHex(code[1])) & 0xFF;
		unsigned const addr = (    asHex(code[2])        <<  8
		                        |  asHex(code[4])        <<  4
		                        |  asHex(code[5])
		                        | (asHex(code[6]) ^ 0xF) << 12) & 0x7FFF;
		unsigned cmp = 0xFFFF;
		if (10 < code.length()) {
			cmp = (asHex(code[8]) << 4 | asHex(code[10])) ^ 0xFF;
			cmp = ((cmp >> 2 | cmp << 6) ^ 0x45) & 0xFF;
		}

		for (unsigned bank = 0; bank < std::size_t(memptrs_.romdataend() - memptrs_.romdata()) / 0x4000; ++bank) {
			if (mbc_->isAddressWithinAreaRombankCanBeMappedTo(addr, bank)
					&& (cmp > 0xFF || memptrs_.romdata()[bank * 0x4000ul + (addr & 0x3FFF)] == cmp)) {
				ggUndoList_.push_back(AddrData(bank * 0x4000ul + (addr & 0x3FFF),
				                      memptrs_.romdata()[bank * 0x4000ul + (addr & 0x3FFF)]));
				memptrs_.romdata()[bank * 0x4000ul + (addr & 0x3FFF)] = val;
			}
		}
	}
}

void Cartridge::setGameGenie(std::string const &codes) {
	if (loaded()) {
		for (std::vector<AddrData>::reverse_iterator it =
				ggUndoList_.rbegin(), end = ggUndoList_.rend(); it != end; ++it) {
			if (memptrs_.romdata() + it->addr < memptrs_.romdataend())
				memptrs_.romdata()[it->addr] = it->data;
		}

		ggUndoList_.clear();

		std::string code;
		for (std::size_t pos = 0; pos < codes.length(); pos += code.length() + 1) {
			code = codes.substr(pos, codes.find(';', pos) - pos);
			applyGameGenie(code);
		}
	}
}

PakInfo const Cartridge::pakInfo(bool const multipakCompat) const {
	if (loaded()) {
		unsigned const rombs = rombanks(memptrs_);
		return PakInfo(multipakCompat && presumedMulti64Mbc1(memptrs_.romdata(), rombs),
		               rombs,
		               memptrs_.romdata());
	}

	return PakInfo();
}

std::pair<unsigned char*, size_t> Cartridge::getSaveRam() {
	size_t sramsize = memptrs_.rambankdataend() - memptrs_.rambankdata();
	return std::make_pair(memptrs_.rambankdata(), sramsize);
}

std::pair<unsigned char*, size_t> Cartridge::getVideoRam() {
	size_t vramsize = memptrs_.vramdataend() - memptrs_.vramdata();
	return std::make_pair(memptrs_.vramdata(), vramsize);
}

std::pair<unsigned char*, size_t> Cartridge::getWorkRam() {
	size_t worksize = memptrs_.wramdataend() - memptrs_.wramdata(0);
	return std::make_pair(memptrs_.wramdata(0), worksize);
}

std::pair<unsigned char*, size_t> Cartridge::getCartRom()
{
	size_t worksize = memptrs_.romdataend() - memptrs_.romdata(0);
	return std::make_pair(memptrs_.romdata(0), worksize);
}

Cartridge::Cartridge(time_t (**_getCurrentTime)())
	: rtc_(_getCurrentTime) {
	memoryCartridge = true;
}

void Cartridge::loadOrSave(loadsave& state) {
	memptrs_.loadOrSave(state);
	rtc_.loadOrSave(state);
	mbc_->loadOrSave(state);
	unsigned ggsize = ggUndoList_.size();
	state(ggsize);
	if(!state.saving())
		ggUndoList_.resize(ggsize);
	for(size_t i = 0; i < ggsize; i++)
		ggUndoList_[i].loadOrSave(state);
}

}
