/*
 *	mbc.c
 *
 *	GameBoy/EMU
 *
 *	* Thu Mar 03 20:00:00 JST 2005 Naoyuki Sawa
 *	- 作成開始。
 */
//#include "app.h"

/****************************************************************************
 *	
 ****************************************************************************/

typedef struct _MBC {
	int rom_bank_no;
	int ram_batt_size;			/* バッテリバックアップRAMのバイト数 */
	/* MBC1 */
	int mbc1_memory_model;			/* 0 = 16Mbit ROM/8KByte RAM, 1 = 4Mbit ROM/32KByte RAM */
	/* MBC3 */
	unsigned char mbc3_rtc_latch[5];	/* [0] = Sec, [1] = Min, [2] = DayLo, [3] = DayHi */
#ifdef PIECE
	char save_fname[MAXFILENAME];
#else /*PIECE*/
	char save_fname[_MAX_PATH];
#endif /*PIECE*/
} MBC;

MBC mbc;

/****************************************************************************
 *	
 ****************************************************************************/

void
mbc_set_rom_bank(int no)
{
	ROM_BANK = &rom[0x4000 * no - 0x4000];	/* $4000-$7FFF: ROM bank #1 */
}

void
mbc_set_ram_bank(int no)
{
	RAM_BANK = &ram[0x2000 * no - 0xa000];	/* $A000-$BFFF: RAM bank #0 */
}

int
mbc_init(const char* rom_fname)
{
	FILE* fp;
	char* p;

	/* MBC設定をクリアします。 */
	memset(&mbc, 0, sizeof mbc);

	/* MBCの種類を確認します。 */
	switch(rom[0x147]) {
	case 0x00: /* ROM ONLY */
	case 0x01: /* ROM+MBC1 */
	case 0x02: /* ROM+MBC1+RAM */
	case 0x03: /* ROM+MBC1+RAM+BATT */
	case 0x05: /* ROM+MBC2 */
	case 0x06: /* ROM+MBC2+BATTERY */
	case 0x08: /* ROM+RAM */
	case 0x09: /* ROM+RAM+BATTERY */
	case 0x0b: /* ROM+MMM01 */		// MBC1と同じ?
	case 0x0c: /* ROM+MMM01+SRAM */		// MBC1と同じ?
	case 0x0d: /* ROM+MMM01+SRAM+BATT */	// MBC1と同じ?
	case 0x0f: /* ROM+MBC3+TIMER+BATT */
	case 0x10: /* ROM+MBC3+TIMER+RAM+BATT */
	case 0x11: /* ROM+MBC3 */
	case 0x12: /* ROM+MBC3+RAM */
	case 0x13: /* ROM+MBC3+RAM+BATT */
	case 0x19: /* ROM+MBC5 */
	case 0x1a: /* ROM+MBC5+RAM */
	case 0x1b: /* ROM+MBC5+RAM+BATT */
	case 0x1c: /* ROM+MBC5+RUMBLE */
	case 0x1d: /* ROM+MBC5+RUMBLE+SRAM */
	case 0x1e: /* ROM+MBC5+RUMBLE+SRAM+BATT */
	//case 0x1f: /* Pocket Camera */	// 未対応
	//case 0xfd: /* Bandai TAMA5 */		// 未対応
	case 0xfe: /* Hudson HuC-3 */		// MBC3と同じ?
	case 0xff: /* Hudson HuC-1 */		// MBC1と同じ?
		break;
	default:
		return -1; /* 未対応 */
	}

	/* ROM容量は検査しません。
	 * P/ECEのフラッシュメモリに置いたまま利用するので、論理上限無しです。
	 */

	/* RAM容量を取得します。
	 * - RAM容量不明時は、RAM容量=0で強行することにします。
	 *   本プログラムは、バッテリバックアップのためだけにRAM容量を参照します。
	 *   RAM容量不明でも、実行中のRAM使用はできるので、上手く行けば動作するかも…
	 */
	switch(rom[0x0149]) {
	//case 0: /* None */
	case 1: /*  16kBit =   2kB =  1 bank  */
		mbc.ram_batt_size = 1024 * 2;
		break;
	case 2: /*  64kBit =   8kB =  1 bank  */
		mbc.ram_batt_size = 1024 * 8;
		break;
	case 3: /* 256kBit =  32kB =  4 banks */
		mbc.ram_batt_size = 1024 * 32;
		break;
	case 4: /*   1MBit = 128kB = 16 banks */
		mbc.ram_batt_size = 1024 * 128;
		break;
	}
	switch(rom[0x147]) { /* MBC2特例 */
	case 0x05: /* ROM+MBC2 */
	case 0x06: /* ROM+MBC2+BATTERY */
		/* 注意!!
		 * 「8bit×256バイト」ではありません!!「4bit×512バイト」です!!
		 * 既存のGameBoyエミュレータでも間違っているものがあり、(VisualBoyAdvanceなど)
		 * それらのエミュレータでは、魔界塔士Sa・Gaの継続プレイができません。
		 */
		mbc.ram_batt_size = 512;
		break;
	}
	if(mbc.ram_batt_size > sizeof ram) {
		return -1;
	}

	/* バッテリバックアップを持たないMBCならば、RAM容量をキャンセルします。 */
	switch(rom[0x147]) {
	//case 0x00: /* ROM ONLY */
	//case 0x01: /* ROM+MBC1 */
	//case 0x02: /* ROM+MBC1+RAM */
	case 0x03: /* ROM+MBC1+RAM+BATT */
	//case 0x05: /* ROM+MBC2 */
	case 0x06: /* ROM+MBC2+BATTERY */
	//case 0x08: /* ROM+RAM */
	case 0x09: /* ROM+RAM+BATTERY */
	//case 0x0b: /* ROM+MMM01 */
	//case 0x0c: /* ROM+MMM01+SRAM */
	case 0x0d: /* ROM+MMM01+SRAM+BATT */
	case 0x0f: /* ROM+MBC3+TIMER+BATT */
	case 0x10: /* ROM+MBC3+TIMER+RAM+BATT */
	//case 0x11: /* ROM+MBC3 */
	//case 0x12: /* ROM+MBC3+RAM */
	case 0x13: /* ROM+MBC3+RAM+BATT */
	//case 0x19: /* ROM+MBC5 */
	//case 0x1a: /* ROM+MBC5+RAM */
	case 0x1b: /* ROM+MBC5+RAM+BATT */
	//case 0x1c: /* ROM+MBC5+RUMBLE */
	//case 0x1d: /* ROM+MBC5+RUMBLE+SRAM */
	case 0x1e: /* ROM+MBC5+RUMBLE+SRAM+BATT */
	//case 0x1f: /* Pocket Camera */
	//case 0xfd: /* Bandai TAMA5 */
	//case 0xfe: /* Hudson HuC-3 */
	//case 0xff: /* Hudson HuC-1 */
		break;
	default:
		mbc.ram_batt_size = 0;
		break;
	}

	/* メインメモリ、RAMバンクをクリアします。 */
	memset(dmgmem, 0, 0x10000);
	memset(ram, 0, sizeof ram);

	/* ROMバンク、RAMバンクを初期化します。(共通) */
	memcpy(dmgmem, rom, 0x4000);		/* $0000-$3FFF: ROM bank #0 */
	mbc_set_rom_bank(1);			/* $4000-$7FFF: ROM bank #1 */
	mbc_set_ram_bank(0);			/* $A000-$BFFF: RAM bank #0 */

	/* バッテリバックアップRAMを復元します。 */
	if(mbc.ram_batt_size) {
#ifdef PIECE
		strcpy(mbc.save_fname, rom_fname);
#else /*PIECE*/
		_splitpath(rom_fname, NULL, NULL, mbc.save_fname, NULL); /* 現在のフォルダにセーブ */
#endif /*PIECE*/
		p = strrchr(mbc.save_fname, '.');
		if(p) {
			*p = '\0';
		}
		strcat(mbc.save_fname, ".sav");
		fp = fopen(mbc.save_fname, "rb");
		if(fp) {
			fread(ram, 1, mbc.ram_batt_size, fp);
			fclose(fp);
		}
		memcpy(old_ram, ram, mbc.ram_batt_size); /* セーブ前比較用に保存 */
	}

	return 0;
}

void
mbc_exit()
{
#ifdef PIECE
	FILEACC fa;
	int size;
	int len;
	int sct;
	int turbo_save;
#else /*PIECE*/
	FILE* fp;
#endif /*PIECE*/

	/* バッテリバックアップRAMを保存します。 */
	if(mbc.ram_batt_size) {
		/* 内容が変わっている場合のみ、更新します。 */
		if(memcmp(old_ram, ram, mbc.ram_batt_size) != 0) {
#ifdef PIECE
			turbo_save = turbo(0); /* 必要!! */
			if(pceFileCreate(mbc.save_fname, mbc.ram_batt_size) == 0) {
				if(pceFileOpen(&fa, mbc.save_fname, FOMD_WR) == 0) {
					render_string_framed(&render, (DISP_X - 5 * 15) / 2, (DISP_Y - 10) / 2,
					//	 012345678901234
						"セーブ中です...",
						0x80, 0x330);
					pceLCDTrans();
					//
					size = mbc.ram_batt_size;
					sct = 0;
					while(size) {
						len = size;
						if(len > BLOCKSIZE) {
							len = BLOCKSIZE;
						}
						pceFileWriteSct(&fa, &ram[BLOCKSIZE * sct], sct, len);
						size -= len;
						sct++;
					}
					pceFileClose(&fa);
				}
			}
			turbo(turbo_save); /* 必要!! */
#else /*PIECE*/
			fp = fopen(mbc.save_fname, "wb");
			if(fp) {
				fwrite(ram, 1, mbc.ram_batt_size, fp);
				fclose(fp);
			}
#endif /*PIECE*/
		}
	}
}

/* RAMバンクのプロテクションには対応していません。
 * RAMバンクのプロテクションに依存した動作を行うゲームは、たぶん無いと思います。
 */
void
mbc_write(int addr, int data)
{
	time_t t;
	struct tm* tm;

	switch(rom[0x147]) {
	case 0x01: /* ROM+MBC1 */
	case 0x02: /* ROM+MBC1+RAM */
	case 0x03: /* ROM+MBC1+RAM+BATT */
	case 0x0b: /* ROM+MMM01 */		// MBC1と同じ?
	case 0x0c: /* ROM+MMM01+SRAM */		// MBC1と同じ?
	case 0x0d: /* ROM+MMM01+SRAM+BATT */	// MBC1と同じ?
	case 0xff: /* Hudson HuC-1 */		// MBC1と同じ?
		{
			/* $0000-$1FFF */
			if(addr < 0x2000) {
				/** no job **/

			/* $2000-$3FFF */
			} else if(addr < 0x4000) {
				data &= 0x1f; /* xxxBBBBB */
				if(!mbc.mbc1_memory_model) { /* 0 = 16Mbit ROM/8KByte RAM */
					mbc.rom_bank_no &= ~0x1f;
					mbc.rom_bank_no |=  data;
				} else { /* 1 = 4Mbit ROM/32KByte RAM */
					mbc.rom_bank_no = data;
				}
				mbc_set_rom_bank(mbc.rom_bank_no ? mbc.rom_bank_no : 1);

			/* $4000-$5FFF */
			} else if(addr < 0x6000) {
				data &= 0x03; /* xxxxxxBB */
				if(!mbc.mbc1_memory_model) { /* 0 = 16Mbit ROM/8KByte RAM */
					mbc.rom_bank_no &= ~(0x03 << 5);
					mbc.rom_bank_no |=  (data << 5);
					mbc_set_rom_bank(mbc.rom_bank_no ? mbc.rom_bank_no : 1);
				} else { /* 1 = 4Mbit ROM/32KByte RAM */
					mbc_set_ram_bank(data);
				}

			/* $6000-$7FFF */
			} else { /* addr < 0x8000 */
				mbc.mbc1_memory_model = data & 1; /* xxxxxxxS */
			}
			return;
		}
	case 0x05: /* ROM+MBC2 */
	case 0x06: /* ROM+MBC2+BATTERY */
		{
			/* $0000-$1FFF */
			if(addr < 0x2000) {
				/** no job **/

			/* $2000-$3FFF */
			} else if(addr < 0x4000) {
				data &= 0x0f; /* xxxxBBBB */
				mbc.rom_bank_no = data;
				mbc_set_rom_bank(mbc.rom_bank_no ? mbc.rom_bank_no : 1);
			}
			return;
		}
	case 0x0f: /* ROM+MBC3+TIMER+BATT */
	case 0x10: /* ROM+MBC3+TIMER+RAM+BATT */
	case 0x11: /* ROM+MBC3 */
	case 0x12: /* ROM+MBC3+RAM */
	case 0x13: /* ROM+MBC3+RAM+BATT */
	case 0xfe: /* Hudson HuC-3 */		// MBC3と同じ?
		{
			/* $0000-$1FFF */
			if(addr < 0x2000) {
				/** no job **/

			/* $2000-$3FFF */
			} else if(addr < 0x4000) {
				data &= 0x7f; /* xBBBBBBB */
				mbc.rom_bank_no = data;
				mbc_set_rom_bank(mbc.rom_bank_no ? mbc.rom_bank_no : 1);

			/* $4000-$5FFF */
			} else if(addr < 0x6000) {
				if(data < 8) {
					mbc_set_ram_bank(data);
				} else if(data < 13) {
					/* メインメモリの$A000-$BFFFは使わないので、RTCバンクとして流用します。 */
					//memset(&dmgmem[0xa000], mbc.mbc3_rtc_latch[data - 8], 0x2000);
					//↓本当は上のようにすべきですが、高速化のために先頭1バイトだけ設定します。
					//↓わざわざ$A000以外の場所からRTC値を読み出すゲームは無い、と推測します。
					dmgmem[0xa000] = mbc.mbc3_rtc_latch[data - 8];
					RAM_BANK = &dmgmem[0xa000 - 0xa000];	/* $A000-$BFFF: RTC bank #8-12 */
				}

			/* $6000-$7FFF */
			} else { /* addr < 0x8000 */
				/* 本当は0->1エッジでラッチするのですけれど、この挙動に依存するゲームは無い、と推測します。
				 * $6000-$7FFFに何か書き込んだら、常にラッチすることにします。
				 */
				time(&t);
				tm = localtime(&t);
				mbc.mbc3_rtc_latch[0] = tm->tm_sec;			/* RAM Bank# 8 */
				mbc.mbc3_rtc_latch[1] = tm->tm_min;			/* RAM Bank# 9 */
				mbc.mbc3_rtc_latch[2] = tm->tm_hour;			/* RAM Bank#10 */
				mbc.mbc3_rtc_latch[3] = (tm->tm_yday >> 0) & 0xff;	/* RAM Bank#11 */
				mbc.mbc3_rtc_latch[4] = (tm->tm_yday >> 8) & 0xff;	/* RAM Bank#12 */
			}
			return;
		}
	case 0x19: /* ROM+MBC5 */
	case 0x1a: /* ROM+MBC5+RAM */
	case 0x1b: /* ROM+MBC5+RAM+BATT */
	case 0x1c: /* ROM+MBC5+RUMBLE */
	case 0x1d: /* ROM+MBC5+RUMBLE+SRAM */
	case 0x1e: /* ROM+MBC5+RUMBLE+SRAM+BATT */
		{
			if(addr < 0x2000) {
				/** no job **/
			} else if(addr < 0x3000) {
				//data &= 0xff; /* BBBBBBBB */
				mbc.rom_bank_no &= ~0xff;
				mbc.rom_bank_no |=  data;
				mbc_set_rom_bank(mbc.rom_bank_no); /* Bank#0 可 */
			} else if(addr < 0x4000) {
				data &= 0x01; /* xxxxxxxB */
				mbc.rom_bank_no &= ~(0x01 << 8);
				mbc.rom_bank_no |=  (data << 8);
				mbc_set_rom_bank(mbc.rom_bank_no); /* Bank#0 可 */
			} else if(addr < 0x6000) {
				if(rom[0x147] < 0x1c) {
				//case 0x19: /* ROM+MBC5 */
				//case 0x1a: /* ROM+MBC5+RAM */
				//case 0x1b: /* ROM+MBC5+RAM+BATT */
					data &= 0x0f; /* xxxxBBBB */
				} else {
				//case 0x1c: /* ROM+MBC5+RUMBLE */
				//case 0x1d: /* ROM+MBC5+RUMBLE+SRAM */
				//case 0x1e: /* ROM+MBC5+RUMBLE+SRAM+BATT */
					data &= 0x07; /* xxxxMBBB */
				}
				mbc_set_ram_bank(data);
			}
			return;
		}
	}
}
