/*
	Extremely primitive bios replacement

	Many thanks to Lars Olsson (jlo@ludd.luth.se) for bios decompile work
		http://www.ludd.luth.se/~jlo/dc/bootROM.c
		http://www.ludd.luth.se/~jlo/dc/bootROM.h
		http://www.ludd.luth.se/~jlo/dc/security_stuff.c
	Bits and pieces from redream (https://github.com/inolen/redream)
*/

#include "reios.h"

#include "reios_elf.h"

#include "gdrom_hle.h"
#include "descrambl.h"

#include "hw/sh4/sh4_if.h"
#include "hw/sh4/sh4_mem.h"
#include "hw/holly/sb.h"
#include "hw/naomi/naomi_cart.h"
#include "hw/aica/aica.h"
#include "hw/aica/aica_if.h"
#include "hw/pvr/pvr_regs.h"
#include "imgread/common.h"
#include "imgread/isofs.h"
#include "hw/sh4/sh4_mmr.h"
#include "oslib/resources.h"
#include "oslib/oslib.h"
#include "oslib/i18n.h"

#include <map>

#define debugf(...) DEBUG_LOG(REIOS, __VA_ARGS__)

#define dc_bios_syscall_system				0x8C0000B0
#define dc_bios_syscall_font				0x8C0000B4
#define dc_bios_syscall_flashrom			0x8C0000B8
#define dc_bios_syscall_gd					0x8C0000BC
#define dc_bios_syscall_gd2					0x8C0000C0
#define dc_bios_syscall_misc				0x8c0000E0

//At least one game (ooga) uses this directly
#define dc_bios_entrypoint_gd2	0x8c0010F0

#define SYSINFO_ID_ADDR 0x8C001010
#define FONT_TABLE_ADDR 0xa0100020

static MemChip *flashrom;
static u32 base_fad = 45150;
static bool descrambl = false;
static u32 bootSectors;
extern Disc *disc;

static void reios_pre_init()
{
	if (disc != nullptr)
	{
		base_fad = disc->GetBaseFAD();
		descrambl = disc->type != GdRom;
	}
}

static bool reios_locate_bootfile(const char* bootfile)
{
	if (disc == nullptr)
	{
		ERROR_LOG(REIOS, "No disk loaded");
		return false;
	}
	reios_pre_init();

	// Load IP.BIN bootstrap
	libGDR_ReadSector(GetMemPtr(0x8c008000, 0), base_fad, 16, 2048);

	IsoFs isofs(disc);
	std::unique_ptr<IsoFs::Directory> root(isofs.getRoot());
	if (root == nullptr)
	{
		ERROR_LOG(REIOS, "ISO file system root not found");
		return false;
	}
	std::unique_ptr<IsoFs::Entry> bootEntry(root->getEntry(trim_trailing_ws(bootfile)));
	if (bootEntry == nullptr || bootEntry->isDirectory())
	{
		ERROR_LOG(REIOS, "Boot file '%s' not found", bootfile);
		return false;
	}
	IsoFs::File *bootFile = (IsoFs::File *)bootEntry.get();

	u32 offset = 0;
	u32 size = bootFile->getSize();
	if (ip_meta.wince == '1' && !descrambl)
	{
		bootFile->read(GetMemPtr(0x8ce01000, 2048), 2048);
		offset = 2048;
		size -= offset;
	}
	bootSectors = size / 2048;

	u8 *dest = GetMemPtr(0x8c010000, size);
	if (dest == nullptr) {
		ERROR_LOG(REIOS, "Boot file too large: %d", size);
		return false;
	}
	if (descrambl)
	{
		std::vector<u8> buf(size);
		bootFile->read(buf.data(), size, offset);
		descrambl_buffer(buf.data(), dest, size);
	}
	else
	{
		bootFile->read(dest, size, offset);
	}

	u8 data[24] = {0};
	// system id
	for (u32 j = 0; j < 8; j++)
		data[j] = addrspace::read8(0x0021a056 + j);

	// system properties
	for (u32 j = 0; j < 5; j++)
		data[8 + j] = addrspace::read8(0x0021a000 + j);

	// system settings
	flash_syscfg_block syscfg{};
	int rc = static_cast<DCFlashChip*>(flashrom)->ReadBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg);
	if (rc == 0)
		WARN_LOG(REIOS, "Can't read system settings from flash");
	else
		memcpy(&data[16], &syscfg.time_lo, 8);

	memcpy(GetMemPtr(0x8c000068, sizeof(data)), data, sizeof(data));

	return true;
}

ip_meta_t ip_meta;

void reios_disk_id()
{
	if (!gdr::isLoaded())
	{
		memset(&ip_meta, 0, sizeof(ip_meta));
		return;
	}
	reios_pre_init();

	u8 buf[2048];
	libGDR_ReadSector(buf, base_fad, 1, sizeof(buf));
	memcpy(&ip_meta, buf, sizeof(ip_meta));
	INFO_LOG(REIOS, "hardware %.16s maker %.16s ks %.5s type %.6s num %.5s area %.8s ctrl %.4s dev %c vga %c wince %c "
			"product %.10s version %.6s date %.8s boot %.16s softco %.16s name %.128s",
			ip_meta.hardware_id, ip_meta.maker_id, ip_meta.ks, ip_meta.disk_type, ip_meta.disk_num, ip_meta.area_symbols,
			ip_meta.ctrl, ip_meta.dev, ip_meta.vga, ip_meta.wince,
			ip_meta.product_number, ip_meta.product_version,
			ip_meta.release_date, ip_meta.boot_filename, ip_meta.software_company, ip_meta.software_name);
}

static void reios_sys_system() {
	u32 cmd = p_sh4rcb->cntx.r[7];

	switch (cmd)
	{
	case 0:	//SYSINFO_INIT
		{
			debugf("reios_sys_system: SYSINFO_INIT");
			// 0x00-0x07: system_id
			// 0x08-0x0c: system_props
			// 0x0d-0x17: padding (zeroed out)
			u8 data[24] = {0};

			// read system_id from 0x0001a056
			for (u32 i  = 0; i < 8; i++)
				data[i] = flashrom->Read8(0x1a056 + i);

			// read system_props from 0x0001a000
			for (u32 i  = 0; i < 5; i++)
				data[8 + i] = flashrom->Read8(0x1a000 + i);

			memcpy(GetMemPtr(0x8c000068, sizeof(data)), data, sizeof(data));

			p_sh4rcb->cntx.r[0] = 0;
		}
		break;

	case 2: //SYSINFO_ICON
		debugf("reios_sys_system: SYSINFO_ICON");
		// r4 = icon number (0-9, but only 5-9 seems to really be icons)
		// r5 = destination buffer (704 bytes in size)
		p_sh4rcb->cntx.r[0] = p_sh4rcb->cntx.r[4] > 9 ? -1 : 704;
		break;

	case 3: //SYSINFO_ID
		debugf("reios_sys_system: SYSINFO_ID");
		p_sh4rcb->cntx.r[0] = 0x8c000068;
		break;

	default:
		WARN_LOG(REIOS, "reios_sys_system: unhandled cmd %d", cmd);
		p_sh4rcb->cntx.r[0] = -1;
		break;
	}
}

static void reios_sys_font() {
	u32 cmd = p_sh4rcb->cntx.r[1];

	switch (cmd)
	{
	case 0:		// FONTROM_ADDRESS
		debugf("FONTROM_ADDRESS");
		p_sh4rcb->cntx.r[0] = FONT_TABLE_ADDR;	// in ROM
		break;

	case 1:		// FONTROM_LOCK
		debugf("FONTROM_LOCK");
		p_sh4rcb->cntx.r[0] = 0;
		break;

	case 2:		// FONTROM_UNLOCK
		debugf("FONTROM_UNLOCK");
		p_sh4rcb->cntx.r[0] = 0;
		break;

	default:
		WARN_LOG(REIOS, "reios_sys_font cmd %x", cmd);
		break;
	}
}

static void reios_sys_flashrom() {
	u32 cmd = p_sh4rcb->cntx.r[7];

	switch (cmd)
	{
		case 0: // FLASHROM_INFO
			{
				/*
					r4 = partition number(0 - 4)
					r5 = pointer to two 32 bit integers to receive the result.
						The first will be the offset of the partition start, in bytes from the start of the flashrom.
						The second will be the size of the partition, in bytes.
					Returns:
					r0 = 0 if successful, -1 if no such partition exists
				 */

				u32 part = p_sh4rcb->cntx.r[4];
				u32 dest = p_sh4rcb->cntx.r[5];
				debugf("reios_sys_flashrom: FLASHROM_INFO part %d dest %08x", part, dest);

				if (part < FLASH_PT_NUM)
				{
					int offset, size;
					static_cast<DCFlashChip*>(flashrom)->GetPartitionInfo(part, &offset, &size);
					WriteMem32(dest, offset);
					WriteMem32(dest + 4, size);

					p_sh4rcb->cntx.r[0] = 0;
				}
				else {
					p_sh4rcb->cntx.r[0] = -1;
				}
			}
			break;

		case 1:	//FLASHROM_READ
			{
				/*
					r4 = read start position, in bytes from the start of the flashrom
					r5 = pointer to destination buffer
					r6 = number of bytes to read
					Returns:
					r0 = 0 if successful, -1 if read failed
				*/
				u32 offset = p_sh4rcb->cntx.r[4];
				u32 dest = p_sh4rcb->cntx.r[5];
				u32 size = p_sh4rcb->cntx.r[6];

				debugf("reios_sys_flashrom: FLASHROM_READ offs %x dest %08x size %x", offset, dest, size);
				for (u32 i = 0; i < size; i++)
					WriteMem8(dest++, flashrom->Read8(offset + i));

				p_sh4rcb->cntx.r[0] = 0;
			}
			break;

			
		case 2:	//FLASHROM_WRITE
			{
				/*
					r4 = write start position, in bytes from the start of the flashrom
					r5 = pointer to source buffer
					r6 = number of bytes to write
					Returns:
					r0 = number of written bytes if successful, -1 if write failed
				*/

				u32 offs = p_sh4rcb->cntx.r[4];
				u32 src = p_sh4rcb->cntx.r[5];
				u32 size = p_sh4rcb->cntx.r[6];

				debugf("reios_sys_flashrom: FLASHROM_WRITE offs %x src %08x size %x", offs, src, size);

				for (u32 i = 0; i < size; i++)
					flashrom->data[offs + i] &= ReadMem8(src + i);

				p_sh4rcb->cntx.r[0] = size;
			}
			break;

		case 3:	//FLASHROM_DELETE
			{			
				/*
				   r4 = offset of the start of the partition you want to delete, in bytes from the start of the flashrom
				   Returns:
				   r0 = zero if successful, -1 if delete failed
				*/
				u32 offset = p_sh4rcb->cntx.r[4];

				debugf("reios_sys_flashrom: FLASHROM_DELETE offs %x", offset);

				bool found = false;

				for (int part = 0; part < FLASH_PT_NUM; part++)
				{
					int part_offset;
					int size;
					static_cast<DCFlashChip*>(flashrom)->GetPartitionInfo(part, &part_offset, &size);
					if (offset == (u32)part_offset)
					{
						found = true;
						memset(flashrom->data + offset, 0xFF, size);
					}
				}

				p_sh4rcb->cntx.r[0] = found ? 0 : -1;
			}
			break;

		default:
			WARN_LOG(REIOS, "reios_sys_flashrom: not handled, %d", cmd);
			break;
	}
}

static void reios_sys_misc()
{
	INFO_LOG(REIOS, "reios_sys_misc - r7: 0x%08X, r4 0x%08X, r5 0x%08X, r6 0x%08X", p_sh4rcb->cntx.r[7], p_sh4rcb->cntx.r[4], p_sh4rcb->cntx.r[5], p_sh4rcb->cntx.r[6]);
	switch (p_sh4rcb->cntx.r[4])
	{
	case 0: // normal init
		SB_GDSTARD = 0xc010000 + bootSectors * 2048;
		SB_IML2NRM = 0;
		p_sh4rcb->cntx.r[0] = 0xc0bebc;
		VO_BORDER_COL.full = p_sh4rcb->cntx.r[0];
		break;

	case 1:	// Exit to BIOS menu
		{
			WARN_LOG(REIOS, "SYS_MISC 1");
			if (gdr::isLoaded()) {
				// just restart the game
				p_sh4rcb->cntx.pc = 0xa0000000;
				os_notify(i18n::T("Reboot to BIOS"), 5000);
			}
			else {
				throw FlycastException(i18n::Ts("Reboot to BIOS"));
			}
		}
		break;

	case 2:	// check disk
		{
			u32 diskType = libGDR_GetDiscType();
			if (diskType == NoDisk || diskType == Open) {
				p_sh4rcb->cntx.r[0] = -1;
			}
			else {
				p_sh4rcb->cntx.r[0] = 0;
				// Reload part of IP.BIN bootstrap
				libGDR_ReadSector(GetMemPtr(0x8c008100, 0), base_fad, 7, 2048);
			}
		}
		break;

	case 3: // Exit to CD menu
		WARN_LOG(REIOS, "SYS_MISC 3");
		break;

	default:
		WARN_LOG(REIOS, "Unknown SYS_MISC call: %d", p_sh4rcb->cntx.r[4]);
		break;
	}
}

typedef void hook_fp();

static void setup_syscall(u32 hook_addr, u32 syscall_addr)
{
	WriteMem32(syscall_addr, hook_addr);
	WriteMem16(hook_addr, REIOS_OPCODE);

	debugf("Patching syscall vector %08X, points to %08X", syscall_addr, hook_addr);
	debugf(" - address %08X: data %04X [%04X]", hook_addr, ReadMem16(hook_addr), REIOS_OPCODE);
}

static void reios_setup_state(u32 boot_addr)
{
	// San Francisco Rush checksum
	short *p = (short *)GetMemPtr(0x8c0010f0, 2);
	int chksum = (int)0xFFF937D1;
	for (int i = 0; i < 10; i++)
		chksum -= *p++;
	p += 0xee - 1;
	for (int i = 0; i < 3; i++)
		chksum += *p++;
	p += 0x347 - 1;
	for (int i = 0; i < 11; i++)
		chksum -= *p++;
	p += 0xbf8 - 1;
	for (int i = 0; i < 98; i++)
	{
		short v = chksum < 0 ? std::min(-chksum, 32767) : std::max(-chksum, -32768);
		*p = v;
		chksum += *p++;
	}

	// Set up AICA interrupt masks
	aica::writeAicaReg(SCIEB_addr, (u16)0x48);
	aica::writeAicaReg(SCILV0_addr, (u8)0x18);
	aica::writeAicaReg(SCILV1_addr, (u8)0x50);
	aica::writeAicaReg(SCILV2_addr, (u8)0x08);

	// KOS seems to expect this
	DMAC_DMAOR.full = 0x8201;

	// WinCE needs this to detect PAL
	if (config::Broadcast == 1)
		BSC_PDTRA.full = 4;
	BSC_PCTRA.full = 0x000A03F0;

	/*
	Post Boot registers from actual bios boot
	r
	[0x00000000]	0xac0005d8
	[0x00000001]	0x00000009
	[0x00000002]	0xac00940c
	[0x00000003]	0x00000000
	[0x00000004]	0xac008300
	[0x00000005]	0xf4000000
	[0x00000006]	0xf4002000
	[0x00000007]	0x00000070
	[0x00000008]	0x00000000
	[0x00000009]	0x00000000
	[0x0000000a]	0x00000000
	[0x0000000b]	0x00000000
	[0x0000000c]	0x00000000
	[0x0000000d]	0x00000000
	[0x0000000e]	0x00000000
	[0x0000000f]	0x8d000000
	mac
	l	0x5bfcb024
	h	0x00000000
	r_bank
	[0x00000000]	0xdfffffff
	[0x00000001]	0x500000f1
	[0x00000002]	0x00000000
	[0x00000003]	0x00000000
	[0x00000004]	0x00000000
	[0x00000005]	0x00000000
	[0x00000006]	0x00000000
	[0x00000007]	0x00000000
	gbr	0x8c000000
	ssr	0x40000001
	spc	0x8c000776
	sgr	0x8d000000
	dbr	0x8c000010
	vbr	0x8c000000
	pr	0xac00043c
	fpul	0x00000000
	pc	0xac008300

	+		sr	{T=1 status = 0x400000f0}
	+		fpscr	{full=0x00040001}
	+		old_sr	{T=1 status=0x400000f0}
	+		old_fpscr	{full=0x00040001}

	*/

	//Setup registers to imitate a normal boot
	Sh4Context& ctx = p_sh4rcb->cntx;
	ctx.r[15] = 0x8d000000;

	ctx.gbr = 0x8c000000;
	ctx.ssr = 0x40000001;
	ctx.spc = 0x8c000776;
	ctx.sgr = 0x8d000000;
	ctx.dbr = 0x8c000010;
	ctx.vbr = 0x8c000000;
	ctx.pr = 0xac00043c;
	ctx.fpul = 0x00000000;
	ctx.pc = boot_addr;

	ctx.sr.status = 0x400000f0;
	ctx.sr.T = 1;

	ctx.old_sr.status = 0x400000f0;

	ctx.fpscr.full = 0x00040001;
	ctx.old_fpscr.full = 0x00040001;
}

static void reios_setup_naomi(u32 boot_addr) {
	/*
		SR 0x60000000 0x00000001
		FPSRC 0x00040001

		-		xf,fr	0x13e1fe40	float [32]
		[0x0]	1.00000000	float
		[0x1]	0.000000000	float
		[0x2]	0.000000000	float
		[0x3]	0.000000000	float
		[0x4]	0.000000000	float
		[0x5]	1.00000000	float
		[0x6]	0.000000000	float
		[0x7]	0.000000000	float
		[0x8]	0.000000000	float
		[0x9]	0.000000000	float
		[0xa]	1.00000000	float
		[0xb]	0.000000000	float
		[0xc]	0.000000000	float
		[0xd]	0.000000000	float
		[0xe]	0.000000000	float
		[0xf]	1.00000000	float
		[0x10]	1.00000000	float
		[0x11]	2.14748365e+009	float
		[0x12]	0.000000000	float
		[0x13]	480.000000	float
		[0x14]	9.99999975e-006	float
		[0x15]	0.000000000	float
		[0x16]	0.00208333321	float
		[0x17]	0.000000000	float
		[0x18]	0.000000000	float
		[0x19]	2.14748365e+009	float
		[0x1a]	1.00000000	float
		[0x1b]	-1.00000000	float
		[0x1c]	0.000000000	float
		[0x1d]	0.000000000	float
		[0x1e]	0.000000000	float
		[0x1f]	0.000000000	float
		
		-		r	0x13e1fec0	unsigned int [16]
		[0x0]	0x0c021000	unsigned int
		[0x1]	0x0c01f820	unsigned int
		[0x2]	0xa0710004	unsigned int
		[0x3]	0x0c01f130	unsigned int
		[0x4]	0x5bfccd08	unsigned int
		[0x5]	0xa05f7000	unsigned int
		[0x6]	0xa05f7008	unsigned int
		[0x7]	0x00000007	unsigned int
		[0x8]	0x00000000	unsigned int
		[0x9]	0x00002000	unsigned int
		[0xa]	0xffffffff	unsigned int
		[0xb]	0x0c0e0000	unsigned int
		[0xc]	0x00000000	unsigned int
		[0xd]	0x00000000	unsigned int
		[0xe]	0x00000000	unsigned int
		[0xf]	0x0cc00000	unsigned int

		-		mac	{full=0x0000000000002000 l=0x00002000 h=0x00000000 }	Sh4Context::<unnamed-tag>::<unnamed-tag>::<unnamed-type-mac>
		full	0x0000000000002000	unsigned __int64
		l	0x00002000	unsigned int
		h	0x00000000	unsigned int
		
		-		r_bank	0x13e1ff08	unsigned int [8]
		[0x0]	0x00000000	unsigned int
		[0x1]	0x00000000	unsigned int
		[0x2]	0x00000000	unsigned int
		[0x3]	0x00000000	unsigned int
		[0x4]	0x00000000	unsigned int
		[0x5]	0x00000000	unsigned int
		[0x6]	0x00000000	unsigned int
		[0x7]	0x00000000	unsigned int
		gbr	0x0c2abcc0	unsigned int
		ssr	0x60000000	unsigned int
		spc	0x0c041738	unsigned int
		sgr	0x0cbfffb0	unsigned int
		dbr	0x00000fff	unsigned int
		vbr	0x0c000000	unsigned int
		pr	0xac0195ee	unsigned int
		fpul	0x000001e0	unsigned int
		pc	0x0c021000	unsigned int
		jdyn	0x0c021000	unsigned int

	*/

	//Setup registers to imitate a normal boot
	Sh4Context& ctx = p_sh4rcb->cntx;
	ctx.r[0] = 0x0c021000;
	ctx.r[1] = 0x0c01f820;
	ctx.r[2] = 0xa0710004;
	ctx.r[3] = 0x0c01f130;
	ctx.r[4] = 0x5bfccd08;
	ctx.r[5] = 0xa05f7000;
	ctx.r[6] = 0xa05f7008;
	ctx.r[7] = 0x00000007;
	ctx.r[8] = 0x00000000;
	ctx.r[9] = 0x00002000;
	ctx.r[10] = 0xffffffff;
	ctx.r[11] = 0x0c0e0000;
	ctx.r[12] = 0x00000000;
	ctx.r[13] = 0x00000000;
	ctx.r[14] = 0x00000000;
	ctx.r[15] = 0x0cc00000;

	ctx.gbr = 0x0c2abcc0;
	ctx.ssr = 0x60000000;
	ctx.spc = 0x0c041738;
	ctx.sgr = 0x0cbfffb0;
	ctx.dbr = 0x00000fff;
	ctx.vbr = 0x0c000000;
	ctx.pr = 0xac0195ee;
	ctx.fpul = 0x000001e0;
	ctx.pc = boot_addr;

	ctx.sr.status = 0x60000000;
	ctx.sr.T = 1;

	ctx.old_sr.status = 0x60000000;

	ctx.fpscr.full = 0x00040001;
	ctx.old_fpscr.full = 0x00040001;
}

static void reios_boot()
{
	NOTICE_LOG(REIOS, "-----------------");
	NOTICE_LOG(REIOS, "REIOS: Booting up");
	NOTICE_LOG(REIOS, "-----------------");
	//setup syscalls
	//find boot file
	//boot it

	memset(GetMemPtr(0x8C000000, 0), 0xFF, 64_KB);

	setup_syscall(0x8C001000, dc_bios_syscall_system);
	setup_syscall(0x8C001002, dc_bios_syscall_font);
	setup_syscall(0x8C001004, dc_bios_syscall_flashrom);
	setup_syscall(0x8C001006, dc_bios_syscall_gd);
	setup_syscall(dc_bios_entrypoint_gd2, dc_bios_syscall_gd2);
	setup_syscall(0x8C001008, dc_bios_syscall_misc);

	//Infinite loop for arm !
	WriteMem32(0x80800000, 0xEAFFFFFE);

	std::string extension = get_file_extension(settings.content.path);
	if (extension == "elf")
	{
		if (!reios_loadElf(settings.content.path))
			throw FlycastException(strprintf(i18n::T("Failed to open ELF %s"), settings.content.path.c_str()));
		reios_setup_state(0x8C010000);
	}
	else {
		if (settings.platform.isConsole())
		{
			char bootfile[sizeof(ip_meta.boot_filename) + 1] = {0};
			memcpy(bootfile, ip_meta.boot_filename, sizeof(ip_meta.boot_filename));
			if (bootfile[0] == '\0' || !reios_locate_bootfile(bootfile))
				throw FlycastException(strprintf(i18n::T("Failed to locate bootfile %s"), bootfile));
			reios_setup_state(0xac008300);
		}
		else {
			verify(settings.platform.isNaomi());
			if (CurrentCartridge == NULL)
			{
				WARN_LOG(REIOS, "No cartridge loaded");
				return;
			}
			u32 data_size = 4;
			u32* sz = (u32*)CurrentCartridge->GetPtr(0x368, data_size);
			if (sz == nullptr || data_size != 4)
				throw FlycastException(i18n::Ts("Naomi boot failure"));

			const u32 size = *sz;

			data_size = 1;
			if (size > RAM_SIZE || CurrentCartridge->GetPtr(size - 1, data_size) == nullptr)
				throw FlycastException(i18n::Ts("Invalid cart size"));

			data_size = size;
			WriteMemBlock_nommu_ptr(0x0c020000, (u32*)CurrentCartridge->GetPtr(0, data_size), size);

			reios_setup_naomi(0x0c021000);
		}
	}
}

#define SYSCALL_ADDR(addr) (((addr) & 0x1FFFFFFF) | 0x80000000)

static const std::map<u32, hook_fp*> hooks = {
		{ SYSCALL_ADDR(0xA0000000), reios_boot },

		{ SYSCALL_ADDR(0x8C001000), reios_sys_system },
		{ SYSCALL_ADDR(0x8C001002), reios_sys_font },
		{ SYSCALL_ADDR(0x8C001004), reios_sys_flashrom },
		{ SYSCALL_ADDR(0x8C001006), gdrom_hle_op },
		{ SYSCALL_ADDR(0x8C001008), reios_sys_misc },

		{ SYSCALL_ADDR(dc_bios_entrypoint_gd2), gdrom_hle_op },
};

void DYNACALL reios_trap(Sh4Context *ctx, u32 op)
{
	verify(op == REIOS_OPCODE);
	u32 pc = ctx->pc - 2;

	u32 mapd = SYSCALL_ADDR(pc);

	//debugf("dispatch %08X -> %08X", pc, mapd);

	auto it = hooks.find(mapd);
	if (it == hooks.end()) {
		ERROR_LOG(REIOS, "Unknown trap vector %08x pc %08x", mapd, pc);
		return;
	}

	it->second();

	// Return from syscall, except if pc was modified
	if (pc == ctx->pc - 2)
		ctx->pc = ctx->pr;
}

void reios_set_flash(MemChip* flash) {
	flashrom = flash;
}

void reios_reset(u8* rom)
{
	memset(rom, 0x00, BIOS_SIZE);
	memset(GetMemPtr(0x8C000000, 0), 0, RAM_SIZE);

	u16* rom16 = (u16*)rom;

	rom16[0] = REIOS_OPCODE;

	// The Grinch game bug
	*(u32 *)&rom[0x44c] = 0xe303d463;
	// Jeremy McGrath game bug
	*(u32 *)&rom[0x1c] = 0x71294118;
	// Rent a Hero 1 game bug
	*(u32 *)&rom[0x8] = 0x44094409;

	u8 *pFont = rom + (FONT_TABLE_ADDR % BIOS_SIZE);

	// 288 12 × 24 pixels (36 bytes) characters
	// 7078 24 × 24 pixels (72 bytes) characters
	// 129 32 × 32 pixels (128 bytes) characters
	memset(pFont, 0, 536496);
	size_t size;
	std::unique_ptr<u8[]> fontData = resource::load("fonts/biosfont.bin", size);
	memcpy(pFont, fontData.get(), size);

	gdrom_hle_reset();
}
