#include <stddef.h>
#include <stdlib.h>
#include <exception>
#include <boost/filesystem/path.hpp>
#include "string_format.h"
#include "PS2OS.h"
#include "StdStream.h"
#ifdef __ANDROID__
#include "android/AssetStream.h"
#endif
#include "PtrMacro.h"
#include "../Ps2Const.h"
#include "../DiskUtils.h"
#include "../ElfFile.h"
#include "../COP_SCU.h"
#include "../uint128.h"
#include "../Log.h"
#include "../iop/IopBios.h"
#include "DMAC.h"
#include "INTC.h"
#include "SIF.h"
#include "EEAssembler.h"
#include "PathUtils.h"
#include "xml/Node.h"
#include "xml/Parser.h"
#include "xml/FilteringNodeIterator.h"
#include "StdStreamUtils.h"

// PS2OS Memory Allocation
// Start		End				Description
// 0x80000010	0x80000014		Current Thread ID
// 0x80008000	0x8000A000		DECI2 Handlers
// 0x8000A000	0x8000C000		INTC Handlers
// 0x8000C000	0x8000E000		DMAC Handlers
// 0x8000E000	0x80010000		Semaphores
// 0x80010000	0x80010800		Custom System Call addresses (0x200 entries)
// 0x80010800	0x80011000		Alarms
// 0x80011000	0x80020000		Threads
// 0x80020000	0x80030000		Kernel Stack
// 0x80030000	0x80032000		Thread Linked List

// BIOS area
// Start		End				Description
// 0x1FC00100	0x1FC00200		Custom System Call handling code
// 0x1FC00200	0x1FC01000		Interrupt Handler
// 0x1FC01000	0x1FC02000		DMAC Handler
// 0x1FC02000	0x1FC03000		INTC Handler
// 0x1FC03000	0x1FC03100		Thread epilogue
// 0x1FC03100	0x1FC03200		Wait Thread Proc
// 0x1FC03200	0x1FC03300		Alarm Handler

#define BIOS_ADDRESS_KERNELSTACK_TOP		0x00030000
#define BIOS_ADDRESS_IDLE_THREAD_ID			0x00000010
#define BIOS_ADDRESS_CURRENT_THREAD_ID		0x00000014
#define BIOS_ADDRESS_VSYNCFLAG_VALUE1PTR	0x00000018
#define BIOS_ADDRESS_VSYNCFLAG_VALUE2PTR	0x0000001C
#define BIOS_ADDRESS_THREADSCHEDULE_BASE	0x00000020
#define BIOS_ADDRESS_INTCHANDLERQUEUE_BASE	0x00000024
#define BIOS_ADDRESS_DMACHANDLERQUEUE_BASE	0x00000028
#define BIOS_ADDRESS_LASTSIFDMA_TIME		0x0000002C
#define BIOS_ADDRESS_INTCHANDLER_BASE		0x0000A000
#define BIOS_ADDRESS_DMACHANDLER_BASE		0x0000C000
#define BIOS_ADDRESS_SEMAPHORE_BASE			0x0000E000
#define BIOS_ADDRESS_ALARM_BASE				0x00010800
#define BIOS_ADDRESS_THREAD_BASE			0x00011000

#define BIOS_ADDRESS_BASE				0x1FC00000
#define BIOS_ADDRESS_INTERRUPTHANDLER	0x1FC00200
#define BIOS_ADDRESS_DMACHANDLER		0x1FC01000
#define BIOS_ADDRESS_INTCHANDLER		0x1FC02000
#define BIOS_ADDRESS_THREADEPILOG		0x1FC03000
#define BIOS_ADDRESS_IDLETHREADPROC		0x1FC03100
#define BIOS_ADDRESS_ALARMHANDLER		0x1FC03200

#define BIOS_ID_BASE					1

#define PATCHESFILENAME		"patches.xml"
#define LOG_NAME			("ps2os")

#define SYSCALL_NAME_EXIT					"osExit"
#define SYSCALL_NAME_LOADEXECPS2			"osLoadExecPS2"
#define SYSCALL_NAME_EXECPS2				"osExecPS2"
#define SYSCALL_NAME_ADDINTCHANDLER			"osAddIntcHandler"
#define SYSCALL_NAME_REMOVEINTCHANDLER		"osRemoveIntcHandler"
#define SYSCALL_NAME_ADDDMACHANDLER			"osAddDmacHandler"
#define SYSCALL_NAME_REMOVEDMACHANDLER		"osRemoveDmacHandler"
#define SYSCALL_NAME_ENABLEINTC				"osEnableIntc"
#define SYSCALL_NAME_DISABLEINTC			"osDisableIntc"
#define SYSCALL_NAME_ENABLEDMAC				"osEnableDmac"
#define SYSCALL_NAME_DISABLEDMAC			"osDisableDmac"
#define SYSCALL_NAME_SETALARM				"osSetAlarm"
#define SYSCALL_NAME_IENABLEINTC			"osiEnableIntc"
#define SYSCALL_NAME_IDISABLEINTC			"osiDisableIntc"
#define SYSCALL_NAME_IENABLEDMAC			"osiEnableDmac"
#define SYSCALL_NAME_IDISABLEDMAC			"osiDisableDmac"
#define SYSCALL_NAME_IRELEASEALARM			"osiReleaseAlarm"
#define SYSCALL_NAME_CREATETHREAD			"osCreateThread"
#define SYSCALL_NAME_DELETETHREAD			"osDeleteThread"
#define SYSCALL_NAME_STARTTHREAD			"osStartThread"
#define SYSCALL_NAME_EXITTHREAD				"osExitThread"
#define SYSCALL_NAME_EXITDELETETHREAD		"osExitDeleteThread"
#define SYSCALL_NAME_TERMINATETHREAD		"osTerminateThread"
#define SYSCALL_NAME_CHANGETHREADPRIORITY	"osChangeThreadPriority"
#define SYSCALL_NAME_ICHANGETHREADPRIORITY	"osiChangeThreadPriority"
#define SYSCALL_NAME_ROTATETHREADREADYQUEUE	"osRotateThreadReadyQueue"
#define SYSCALL_NAME_GETTHREADID			"osGetThreadId"
#define SYSCALL_NAME_REFERTHREADSTATUS		"osReferThreadStatus"
#define SYSCALL_NAME_IREFERTHREADSTATUS		"osiReferThreadStatus"
#define SYSCALL_NAME_SLEEPTHREAD			"osSleepThread"
#define SYSCALL_NAME_WAKEUPTHREAD			"osWakeupThread"
#define SYSCALL_NAME_IWAKEUPTHREAD			"osiWakeupThread"
#define SYSCALL_NAME_CANCELWAKEUPTHREAD		"osCancelWakeupThread"
#define SYSCALL_NAME_ICANCELWAKEUPTHREAD	"osiCancelWakeupThread"
#define SYSCALL_NAME_SUSPENDTHREAD			"osSuspendThread"
#define SYSCALL_NAME_ISUSPENDTHREAD			"osiSuspendThread"
#define SYSCALL_NAME_RESUMETHREAD			"osResumeThread"
#define SYSCALL_NAME_ENDOFHEAP				"osEndOfHeap"
#define SYSCALL_NAME_CREATESEMA				"osCreateSema"
#define SYSCALL_NAME_DELETESEMA				"osDeleteSema"
#define SYSCALL_NAME_SIGNALSEMA				"osSignalSema"
#define SYSCALL_NAME_ISIGNALSEMA			"osiSignalSema"
#define SYSCALL_NAME_WAITSEMA				"osWaitSema"
#define SYSCALL_NAME_POLLSEMA				"osPollSema"
#define SYSCALL_NAME_IPOLLSEMA				"osiPollSema"
#define SYSCALL_NAME_REFERSEMASTATUS		"osReferSemaStatus"
#define SYSCALL_NAME_IREFERSEMASTATUS		"osiReferSemaStatus"
#define SYSCALL_NAME_FLUSHCACHE				"osFlushCache"
#define SYSCALL_NAME_SIFSTOPDMA				"osSifStopDma"
#define SYSCALL_NAME_GSGETIMR				"osGsGetIMR"
#define SYSCALL_NAME_GSPUTIMR				"osGsPutIMR"
#define SYSCALL_NAME_SETVSYNCFLAG			"osSetVSyncFlag"
#define SYSCALL_NAME_SETSYSCALL				"osSetSyscall"
#define SYSCALL_NAME_SIFDMASTAT				"osSifDmaStat"
#define SYSCALL_NAME_SIFSETDMA				"osSifSetDma"
#define SYSCALL_NAME_SIFSETDCHAIN			"osSifSetDChain"
#define SYSCALL_NAME_DECI2CALL				"osDeci2Call"
#define SYSCALL_NAME_MACHINETYPE			"osMachineType"

#ifdef DEBUGGER_INCLUDED

const CPS2OS::SYSCALL_NAME	CPS2OS::g_syscallNames[] =
{
	{	0x0004,		SYSCALL_NAME_EXIT					},
	{	0x0006,		SYSCALL_NAME_LOADEXECPS2			},
	{	0x0007,		SYSCALL_NAME_EXECPS2				},
	{	0x0010,		SYSCALL_NAME_ADDINTCHANDLER			},
	{	0x0011,		SYSCALL_NAME_REMOVEINTCHANDLER		},
	{	0x0012,		SYSCALL_NAME_ADDDMACHANDLER			},
	{	0x0013,		SYSCALL_NAME_REMOVEDMACHANDLER		},
	{	0x0014,		SYSCALL_NAME_ENABLEINTC				},
	{	0x0015,		SYSCALL_NAME_DISABLEINTC			},
	{	0x0016,		SYSCALL_NAME_ENABLEDMAC				},
	{	0x0017,		SYSCALL_NAME_DISABLEDMAC			},
	{	0x0018,		SYSCALL_NAME_SETALARM				},
	{	0x001A,		SYSCALL_NAME_IENABLEINTC			},
	{	0x001B,		SYSCALL_NAME_IDISABLEINTC			},
	{	0x001C,		SYSCALL_NAME_IENABLEDMAC			},
	{	0x001D,		SYSCALL_NAME_IDISABLEDMAC			},
	{	0x001F,		SYSCALL_NAME_IRELEASEALARM			},
	{	0x0020,		SYSCALL_NAME_CREATETHREAD			},
	{	0x0021,		SYSCALL_NAME_DELETETHREAD			},
	{	0x0022,		SYSCALL_NAME_STARTTHREAD			},
	{	0x0023,		SYSCALL_NAME_EXITTHREAD				},
	{	0x0024,		SYSCALL_NAME_EXITDELETETHREAD		},
	{	0x0025,		SYSCALL_NAME_TERMINATETHREAD		},
	{	0x0029,		SYSCALL_NAME_CHANGETHREADPRIORITY	},
	{	0x002A,		SYSCALL_NAME_ICHANGETHREADPRIORITY	},
	{	0x002B,		SYSCALL_NAME_ROTATETHREADREADYQUEUE	},
	{	0x002F,		SYSCALL_NAME_GETTHREADID			},
	{	0x0030,		SYSCALL_NAME_REFERTHREADSTATUS		},
	{	0x0031,		SYSCALL_NAME_IREFERTHREADSTATUS		},
	{	0x0032,		SYSCALL_NAME_SLEEPTHREAD			},
	{	0x0033,		SYSCALL_NAME_WAKEUPTHREAD			},
	{	0x0034,		SYSCALL_NAME_IWAKEUPTHREAD			},
	{	0x0035,		SYSCALL_NAME_CANCELWAKEUPTHREAD		},
	{	0x0036,		SYSCALL_NAME_ICANCELWAKEUPTHREAD	},
	{	0x0037,		SYSCALL_NAME_SUSPENDTHREAD			},
	{	0x0038,		SYSCALL_NAME_ISUSPENDTHREAD			},
	{	0x0039,		SYSCALL_NAME_RESUMETHREAD			},
	{	0x003E,		SYSCALL_NAME_ENDOFHEAP				},
	{	0x0040,		SYSCALL_NAME_CREATESEMA				},
	{	0x0041,		SYSCALL_NAME_DELETESEMA				},
	{	0x0042,		SYSCALL_NAME_SIGNALSEMA				},
	{	0x0043,		SYSCALL_NAME_ISIGNALSEMA			},
	{	0x0044,		SYSCALL_NAME_WAITSEMA				},
	{	0x0045,		SYSCALL_NAME_POLLSEMA				},
	{	0x0046,		SYSCALL_NAME_IPOLLSEMA				},
	{	0x0047,		SYSCALL_NAME_REFERSEMASTATUS		},
	{	0x0048,		SYSCALL_NAME_IREFERSEMASTATUS		},
	{	0x0064,		SYSCALL_NAME_FLUSHCACHE				},
	{	0x006B,		SYSCALL_NAME_SIFSTOPDMA				},
	{	0x0070,		SYSCALL_NAME_GSGETIMR				},
	{	0x0071,		SYSCALL_NAME_GSPUTIMR				},
	{	0x0073,		SYSCALL_NAME_SETVSYNCFLAG			},
	{	0x0074,		SYSCALL_NAME_SETSYSCALL				},
	{	0x0076,		SYSCALL_NAME_SIFDMASTAT				},
	{	0x0077,		SYSCALL_NAME_SIFSETDMA				},
	{	0x0078,		SYSCALL_NAME_SIFSETDCHAIN			},
	{	0x007C,		SYSCALL_NAME_DECI2CALL				},
	{	0x007E,		SYSCALL_NAME_MACHINETYPE			},
	{	0x0000,		NULL								}
};

#endif

namespace filesystem = boost::filesystem;

CPS2OS::CPS2OS(CMIPS& ee, uint8* ram, uint8* bios, uint8* spr, CGSHandler*& gs, CSIF& sif, CIopBios& iopBios) 
: m_ee(ee)
, m_gs(gs)
, m_elf(nullptr)
, m_ram(ram)
, m_bios(bios)
, m_spr(spr)
, m_sif(sif)
, m_iopBios(iopBios)
, m_threads(reinterpret_cast<THREAD*>(m_ram + BIOS_ADDRESS_THREAD_BASE), BIOS_ID_BASE, MAX_THREAD)
, m_semaphores(reinterpret_cast<SEMAPHORE*>(m_ram + BIOS_ADDRESS_SEMAPHORE_BASE), BIOS_ID_BASE, MAX_SEMAPHORE)
, m_intcHandlers(reinterpret_cast<INTCHANDLER*>(m_ram + BIOS_ADDRESS_INTCHANDLER_BASE), BIOS_ID_BASE, MAX_INTCHANDLER)
, m_dmacHandlers(reinterpret_cast<DMACHANDLER*>(m_ram + BIOS_ADDRESS_DMACHANDLER_BASE), BIOS_ID_BASE, MAX_DMACHANDLER)
, m_alarms(reinterpret_cast<ALARM*>(m_ram + BIOS_ADDRESS_ALARM_BASE), BIOS_ID_BASE, MAX_ALARM)
, m_currentThreadId(reinterpret_cast<uint32*>(m_ram + BIOS_ADDRESS_CURRENT_THREAD_ID))
, m_idleThreadId(reinterpret_cast<uint32*>(m_ram + BIOS_ADDRESS_IDLE_THREAD_ID))
, m_lastSifDmaTime(reinterpret_cast<uint32*>(m_ram + BIOS_ADDRESS_LASTSIFDMA_TIME))
, m_threadSchedule(m_threads, reinterpret_cast<uint32*>(m_ram + BIOS_ADDRESS_THREADSCHEDULE_BASE))
, m_intcHandlerQueue(m_intcHandlers, reinterpret_cast<uint32*>(m_ram + BIOS_ADDRESS_INTCHANDLERQUEUE_BASE))
, m_dmacHandlerQueue(m_dmacHandlers, reinterpret_cast<uint32*>(m_ram + BIOS_ADDRESS_DMACHANDLERQUEUE_BASE))
{

}

CPS2OS::~CPS2OS()
{
	Release();
}

void CPS2OS::Initialize()
{
	m_elf = nullptr;

	m_semaWaitId = -1;
	m_semaWaitCount = 0;
	m_semaWaitCaller = 0;
	m_semaWaitThreadId = -1;

	SetVsyncFlagPtrs(0, 0);

	AssembleCustomSyscallHandler();
	AssembleInterruptHandler();
	AssembleDmacHandler();
	AssembleIntcHandler();
	AssembleThreadEpilog();
	AssembleIdleThreadProc();
	AssembleAlarmHandler();
	CreateIdleThread();

	m_ee.m_State.nPC = BIOS_ADDRESS_IDLETHREADPROC;
	m_ee.m_State.nCOP0[CCOP_SCU::STATUS] |= CMIPS::STATUS_IE;
}

void CPS2OS::Release()
{
	UnloadExecutable();
}

bool CPS2OS::IsIdle() const
{
	return 
		(m_currentThreadId == m_idleThreadId) ||
		(m_currentThreadId == m_semaWaitThreadId);
}

void CPS2OS::DumpIntcHandlers()
{
	printf("INTC Handlers Information\r\n");
	printf("-------------------------\r\n");

	for(unsigned int i = 0; i < MAX_INTCHANDLER; i++)
	{
		auto handler = m_intcHandlers[i + 1];
		if(handler == nullptr) continue;

		printf("ID: %0.2i, Line: %i, Address: 0x%0.8X.\r\n", \
			i + 1,
			handler->cause,
			handler->address);
	}
}

void CPS2OS::DumpDmacHandlers()
{
	printf("DMAC Handlers Information\r\n");
	printf("-------------------------\r\n");

	for(unsigned int i = 0; i < MAX_DMACHANDLER; i++)
	{
		auto handler = m_dmacHandlers[i + 1];
		if(handler == nullptr) continue;

		printf("ID: %0.2i, Channel: %i, Address: 0x%0.8X.\r\n", \
			i + 1,
			handler->channel,
			handler->address);
	}
}

void CPS2OS::BootFromFile(const char* sPath)
{
	filesystem::path execPath(sPath);
	Framework::CStdStream stream(fopen(execPath.string().c_str(), "rb"));
	LoadELF(stream, execPath.filename().string().c_str(), ArgumentList());
}

void CPS2OS::BootFromVirtualPath(const char* executablePath, const ArgumentList& arguments)
{
	auto ioman = m_iopBios.GetIoman();

	uint32 handle = ioman->Open(Iop::Ioman::CDevice::OPEN_FLAG_RDONLY, executablePath);
	if(static_cast<int32>(handle) < 0)
	{
		throw std::runtime_error("Couldn't open executable specified by virtual path.");
	}

	try
	{
		const char* executableName = strchr(executablePath, ':') + 1;
		if(executableName[0] == '/' || executableName[0] == '\\') executableName++;
		Framework::CStream* file(ioman->GetFileStream(handle));
		LoadELF(*file, executableName, arguments);
	}
	catch(...)
	{
		throw std::runtime_error("Error occured while reading ELF executable from virtual path.");
	}
	ioman->Close(handle);
}

void CPS2OS::BootFromCDROM()
{
	std::string executablePath;
	auto ioman = m_iopBios.GetIoman();

	{
		uint32 handle = ioman->Open(Iop::Ioman::CDevice::OPEN_FLAG_RDONLY, "cdrom0:SYSTEM.CNF");
		if(static_cast<int32>(handle) < 0)
		{
			throw std::runtime_error("No 'SYSTEM.CNF' file found on the cdrom0 device.");
		}

		{
			Framework::CStream* file(ioman->GetFileStream(handle));
			auto systemConfig = DiskUtils::ParseSystemConfigFile(file);
			auto bootItemIterator = systemConfig.find("BOOT2");
			if(bootItemIterator != std::end(systemConfig))
			{
				executablePath = bootItemIterator->second;
			}
		}

		ioman->Close(handle);
	}

	if(executablePath.length() == 0)
	{
		throw std::runtime_error("Error parsing 'SYSTEM.CNF' for a BOOT2 value.");
	}

	BootFromVirtualPath(executablePath.c_str(), ArgumentList());
}

CELF* CPS2OS::GetELF()
{
	return m_elf;
}

const char* CPS2OS::GetExecutableName() const
{
	return m_executableName.c_str();
}

std::pair<uint32, uint32> CPS2OS::GetExecutableRange() const
{
	uint32 minAddr = 0xFFFFFFF0;
	uint32 maxAddr = 0x00000000;
	const ELFHEADER& header = m_elf->GetHeader();

	for(unsigned int i = 0; i < header.nProgHeaderCount; i++)
	{
		ELFPROGRAMHEADER* p = m_elf->GetProgram(i);
		if(p != NULL)
		{
			//Wild Arms: Alter Code F has zero sized program headers
			if(p->nFileSize == 0) continue;
			if(!(p->nFlags & CELF::PF_X)) continue;
			uint32 end = p->nVAddress + p->nFileSize;
			if(end >= PS2::EE_RAM_SIZE) continue;
			minAddr = std::min<uint32>(minAddr, p->nVAddress);
			maxAddr = std::max<uint32>(maxAddr, end);
		}
	}

	return std::pair<uint32, uint32>(minAddr, maxAddr);
}

void CPS2OS::LoadELF(Framework::CStream& stream, const char* sExecName, const ArgumentList& arguments)
{
	CELF* elf(new CElfFile(stream));

	const auto& header = elf->GetHeader();

	//Check for MIPS CPU
	if(header.nCPU != CELF::EM_MIPS)
	{
		DELETEPTR(elf);
		throw std::runtime_error("Invalid target CPU. Must be MIPS.");
	}

	if(header.nType != CELF::ET_EXEC)
	{
		DELETEPTR(elf);
		throw std::runtime_error("Not an executable ELF file.");
	}
	
	UnloadExecutable();

	m_elf = elf;

	m_executableName = sExecName;
	m_currentArguments = arguments;

	LoadExecutableInternal();
	ApplyPatches();

	OnExecutableChange();

	CLog::GetInstance().Print(LOG_NAME, "Loaded '%s' executable file.\r\n", sExecName);
}

void CPS2OS::LoadExecutableInternal()
{
	//Copy program in main RAM
	const ELFHEADER& header = m_elf->GetHeader();
	for(unsigned int i = 0; i < header.nProgHeaderCount; i++)
	{
		auto p = m_elf->GetProgram(i);
		if(p != nullptr)
		{
			if(p->nVAddress >= PS2::EE_RAM_SIZE)
			{
				assert(false);
				continue;
			}
			memcpy(m_ram + p->nVAddress, m_elf->GetContent() + p->nOffset, p->nFileSize);
		}
	}

	m_ee.m_State.nPC = header.nEntryPoint;
	
#ifdef DEBUGGER_INCLUDED
	std::pair<uint32, uint32> executableRange = GetExecutableRange();
	uint32 minAddr = executableRange.first;
	uint32 maxAddr = executableRange.second & ~0x03;

	m_ee.m_analysis->Clear();
	m_ee.m_analysis->Analyse(minAddr, maxAddr, header.nEntryPoint);

	//Tag system calls
	for(uint32 address = minAddr; address < maxAddr; address += 4)
	{
		//Check for SYSCALL opcode
		uint32 opcode = *reinterpret_cast<uint32*>(m_ram + address);
		if(opcode == 0x0000000C)
		{
			//Check the opcode before and after it
			uint32 addiu	= *reinterpret_cast<uint32*>(m_ram + address - 4);
			uint32 jr		= *reinterpret_cast<uint32*>(m_ram + address + 4);
			if(
				(jr == 0x03E00008) && 
				(addiu & 0xFFFF0000) == 0x24030000
				)
			{
				//We have it!
				int16 syscallId = static_cast<int16>(addiu);
				if(syscallId & 0x8000)
				{
					syscallId = 0 - syscallId;
				}
				char syscallName[256];
				int syscallNameIndex = -1;
				for(int i = 0; g_syscallNames[i].name != NULL; i++)
				{
					if(g_syscallNames[i].id == syscallId)
					{
						syscallNameIndex = i;
						break;
					}
				}
				if(syscallNameIndex != -1)
				{
					strncpy(syscallName, g_syscallNames[syscallNameIndex].name, 256);
				}
				else
				{
					sprintf(syscallName, "syscall_%0.4X", syscallId);
				}
				m_ee.m_Functions.InsertTag(address - 4, syscallName);
			}
		}
	}

#endif
}

void CPS2OS::UnloadExecutable()
{
	if(m_elf == NULL) return;

	OnExecutableUnloading();

	DELETEPTR(m_elf);
}

uint32 CPS2OS::LoadExecutable(const char* path, const char* section)
{
	auto ioman = m_iopBios.GetIoman();

	uint32 handle = ioman->Open(Iop::Ioman::CDevice::OPEN_FLAG_RDONLY, path);
	if(static_cast<int32>(handle) < 0)
	{
		return -1;
	}

	uint32 result = 0;

	//We don't support loading anything else than all sections
	assert(strcmp(section, "all") == 0);

	auto fileStream(ioman->GetFileStream(handle));
	
	//Load all program sections
	{
		CElfFile executable(*fileStream);
		const auto& header = executable.GetHeader();
		for(unsigned int i = 0; i < header.nProgHeaderCount; i++)
		{
			auto p = executable.GetProgram(i);
			if(p)
			{
				memcpy(m_ram + p->nVAddress, executable.GetContent() + p->nOffset, p->nFileSize);
			}
		}

		result = executable.GetHeader().nEntryPoint;
	}

	//Flush all instruction cache
	OnRequestInstructionCacheFlush();

	ioman->Close(handle);

	return result;
}

void CPS2OS::ApplyPatches()
{
	std::unique_ptr<Framework::Xml::CNode> document;
	try
	{
#ifdef __ANDROID__
		Framework::Android::CAssetStream patchesStream(PATCHESFILENAME);
#else
		auto patchesPath = Framework::PathUtils::GetAppResourcesPath() / PATCHESFILENAME;
		Framework::CStdStream patchesStream(Framework::CreateInputStdStream(patchesPath.native()));
#endif
		document = std::unique_ptr<Framework::Xml::CNode>(Framework::Xml::CParser::ParseDocument(patchesStream));
		if(!document) return;
	}
	catch(const std::exception& exception)
	{
		CLog::GetInstance().Print(LOG_NAME, "Failed to open patch definition file: %s.\r\n", exception.what());
		return;
	}

	auto patchesNode = document->Select("Patches");
	if(patchesNode == NULL)
	{
		return;
	}

	for(Framework::Xml::CFilteringNodeIterator itNode(patchesNode, "Executable"); !itNode.IsEnd(); itNode++)
	{
		auto executableNode = (*itNode);

		const char* name = executableNode->GetAttribute("Name");
		if(name == NULL) continue;

		if(!strcmp(name, GetExecutableName()))
		{
			//Found the right executable
			unsigned int patchCount = 0;

			for(Framework::Xml::CFilteringNodeIterator itNode(executableNode, "Patch"); !itNode.IsEnd(); itNode++)
			{
				auto patch = (*itNode);
				
				const char* addressString	= patch->GetAttribute("Address");
				const char* valueString		= patch->GetAttribute("Value");

				if(addressString == nullptr) continue;
				if(valueString == nullptr) continue;

				uint32 value = 0, address = 0;
				if(sscanf(addressString, "%x", &address) == 0) continue;
				if(sscanf(valueString, "%x", &value) == 0) continue;

				*(uint32*)&m_ram[address] = value;

				patchCount++;
			}

			CLog::GetInstance().Print(LOG_NAME, "Applied %i patch(es).\r\n", patchCount);

			break;
		}
	}
}

void CPS2OS::AssembleCustomSyscallHandler()
{
	CMIPSAssembler assembler((uint32*)&m_bios[0x100]);

	//Epilogue
	assembler.ADDIU(CMIPS::SP, CMIPS::SP, 0xFFF0);
	assembler.SD(CMIPS::RA, 0x0000, CMIPS::SP);
	
	//Load the function address off the table at 0x80010000
	assembler.SLL(CMIPS::T0, CMIPS::V1, 2);
	assembler.LUI(CMIPS::T1, 0x8001);
	assembler.ADDU(CMIPS::T0, CMIPS::T0, CMIPS::T1);
	assembler.LW(CMIPS::T0, 0x0000, CMIPS::T0);
	
	//And the address with 0x1FFFFFFF
	assembler.LUI(CMIPS::T1, 0x1FFF);
	assembler.ORI(CMIPS::T1, CMIPS::T1, 0xFFFF);
	assembler.AND(CMIPS::T0, CMIPS::T0, CMIPS::T1);

	//Jump to the system call address
	assembler.JALR(CMIPS::T0);
	assembler.NOP();

	//Prologue
	assembler.LD(CMIPS::RA, 0x0000, CMIPS::SP);
	assembler.ADDIU(CMIPS::SP, CMIPS::SP, 0x0010);
	assembler.ERET();
}

void CPS2OS::AssembleInterruptHandler()
{
	CEEAssembler assembler(reinterpret_cast<uint32*>(&m_bios[BIOS_ADDRESS_INTERRUPTHANDLER - BIOS_ADDRESS_BASE]));

	const uint32 stackFrameSize = 0x230;

	//Epilogue (allocate stackFrameSize bytes)
	assembler.LI(CMIPS::K0, BIOS_ADDRESS_KERNELSTACK_TOP);
	assembler.ADDIU(CMIPS::K0, CMIPS::K0, 0x10000 - stackFrameSize);
	
	//Save context
	for(unsigned int i = 0; i < 32; i++)
	{
		assembler.SQ(i, (i * 0x10), CMIPS::K0);
	}

	assembler.MFLO(CMIPS::V0);
	assembler.MFLO1(CMIPS::V1);
	assembler.SD(CMIPS::V0, 0x0200, CMIPS::K0);
	assembler.SD(CMIPS::V1, 0x0208, CMIPS::K0);

	assembler.MFHI(CMIPS::V0);
	assembler.MFHI1(CMIPS::V1);
	assembler.SD(CMIPS::V0, 0x0210, CMIPS::K0);
	assembler.SD(CMIPS::V1, 0x0218, CMIPS::K0);

	//Save EPC
	assembler.MFC0(CMIPS::T0, CCOP_SCU::EPC);
	assembler.SW(CMIPS::T0, 0x0220, CMIPS::K0);

	//Set SP
	assembler.ADDU(CMIPS::SP, CMIPS::K0, CMIPS::R0);

	//Disable interrupts. Used by some games (ie.: RE4) to check interrupt context.
	assembler.MFC0(CMIPS::T0, CCOP_SCU::STATUS);
	assembler.LI(CMIPS::T1, ~CMIPS::STATUS_IE);
	assembler.AND(CMIPS::T0, CMIPS::T0, CMIPS::T1);
	assembler.MTC0(CMIPS::T0, CCOP_SCU::STATUS);

	//Get INTC status
	assembler.LI(CMIPS::T0, CINTC::INTC_STAT);
	assembler.LW(CMIPS::S0, 0x0000, CMIPS::T0);

	//Get INTC mask
	assembler.LI(CMIPS::T1, CINTC::INTC_MASK);
	assembler.LW(CMIPS::S1, 0x0000, CMIPS::T1);

	//Get cause
	assembler.AND(CMIPS::S0, CMIPS::S0, CMIPS::S1);

	//Clear cause
	//assembler.SW(CMIPS::S0, 0x0000, CMIPS::T0);
	assembler.NOP();

	static const auto generateIntHandler = 
		[](CMIPSAssembler& assembler, uint32 line)
		{
			auto skipIntHandlerLabel = assembler.CreateLabel();

			//Check cause
			assembler.ANDI(CMIPS::T0, CMIPS::S0, (1 << line));
			assembler.BEQ(CMIPS::R0, CMIPS::T0, skipIntHandlerLabel);
			assembler.NOP();

			//Process handlers
			assembler.ADDIU(CMIPS::A0, CMIPS::R0, line);
			assembler.JAL(BIOS_ADDRESS_INTCHANDLER);
			assembler.NOP();

			assembler.MarkLabel(skipIntHandlerLabel);
		};

	generateIntHandler(assembler, CINTC::INTC_LINE_GS);
	
	{
		auto skipIntHandlerLabel = assembler.CreateLabel();

		//Check if INT1 (DMAC)
		assembler.ANDI(CMIPS::T0, CMIPS::S0, (1 << CINTC::INTC_LINE_DMAC));
		assembler.BEQ(CMIPS::R0, CMIPS::T0, skipIntHandlerLabel);
		assembler.NOP();

		//Go to DMAC interrupt handler
		assembler.JAL(BIOS_ADDRESS_DMACHANDLER);
		assembler.NOP();

		assembler.MarkLabel(skipIntHandlerLabel);
	}

	generateIntHandler(assembler, CINTC::INTC_LINE_VBLANK_START);
	generateIntHandler(assembler, CINTC::INTC_LINE_VBLANK_END);
	generateIntHandler(assembler, CINTC::INTC_LINE_VIF1);
	generateIntHandler(assembler, CINTC::INTC_LINE_IPU);
	generateIntHandler(assembler, CINTC::INTC_LINE_TIMER0);
	generateIntHandler(assembler, CINTC::INTC_LINE_TIMER1);
	generateIntHandler(assembler, CINTC::INTC_LINE_TIMER2);
	generateIntHandler(assembler, CINTC::INTC_LINE_TIMER3);

	assembler.JAL(BIOS_ADDRESS_ALARMHANDLER);
	assembler.NOP();

	//Make sure interrupts are enabled (This is needed by some games that play
	//with the status register in interrupt handlers and is done by the EE BIOS)
	assembler.MFC0(CMIPS::T0, CCOP_SCU::STATUS);
	assembler.ORI(CMIPS::T0, CMIPS::T0, CMIPS::STATUS_IE);
	assembler.MTC0(CMIPS::T0, CCOP_SCU::STATUS);

	//Move back SP into K0 before restoring state
	assembler.ADDIU(CMIPS::K0, CMIPS::SP, CMIPS::R0);

	//Restore EPC
	assembler.LW(CMIPS::T0, 0x0220, CMIPS::K0);
	assembler.MTC0(CMIPS::T0, CCOP_SCU::EPC);

	//Restore Context
	assembler.LD(CMIPS::V0, 0x0210, CMIPS::K0);
	assembler.LD(CMIPS::V1, 0x0218, CMIPS::K0);
	assembler.MTHI(CMIPS::V0);
	assembler.MTHI1(CMIPS::V1);

	assembler.LD(CMIPS::V0, 0x0200, CMIPS::K0);
	assembler.LD(CMIPS::V1, 0x0208, CMIPS::K0);
	assembler.MTLO(CMIPS::V0);
	assembler.MTLO1(CMIPS::V1);

	for(unsigned int i = 0; i < 32; i++)
	{
		assembler.LQ(i, (i * 0x10), CMIPS::K0);
	}

	//Prologue
	assembler.ERET();
}

void CPS2OS::AssembleDmacHandler()
{
	CMIPSAssembler assembler(reinterpret_cast<uint32*>(&m_bios[BIOS_ADDRESS_DMACHANDLER - BIOS_ADDRESS_BASE]));

	auto checkHandlerLabel = assembler.CreateLabel();
	auto testChannelLabel = assembler.CreateLabel();
	auto skipChannelLabel = assembler.CreateLabel();

	auto channelCounterRegister = CMIPS::S0;
	auto interruptStatusRegister = CMIPS::S1;
	auto nextIdPtrRegister = CMIPS::S2;

	auto nextIdRegister = CMIPS::T2;

	assembler.ADDIU(CMIPS::SP, CMIPS::SP, 0xFFE0);
	assembler.SD(CMIPS::RA, 0x0000, CMIPS::SP);
	assembler.SD(CMIPS::S0, 0x0008, CMIPS::SP);
	assembler.SD(CMIPS::S1, 0x0010, CMIPS::SP);
	assembler.SD(CMIPS::S2, 0x0018, CMIPS::SP);

	//Clear INTC cause
	assembler.LI(CMIPS::T1, CINTC::INTC_STAT);
	assembler.ADDIU(CMIPS::T0, CMIPS::R0, (1 << CINTC::INTC_LINE_DMAC));
	assembler.SW(CMIPS::T0, 0x0000, CMIPS::T1);

	//Load the DMA interrupt status
	assembler.LI(CMIPS::T0, CDMAC::D_STAT);
	assembler.LW(CMIPS::T0, 0x0000, CMIPS::T0);

	assembler.SRL(CMIPS::T1, CMIPS::T0, 16);
	assembler.AND(interruptStatusRegister, CMIPS::T0, CMIPS::T1);

	//Initialize channel counter
	assembler.ADDIU(channelCounterRegister, CMIPS::R0, 0x0009);

	assembler.MarkLabel(testChannelLabel);

	//Check if that specific DMA channel interrupt is the cause
	assembler.ORI(CMIPS::T0, CMIPS::R0, 0x0001);
	assembler.SLLV(CMIPS::T0, CMIPS::T0, CMIPS::S0);
	assembler.AND(CMIPS::T0, CMIPS::T0, CMIPS::S1);
	assembler.BEQ(CMIPS::T0, CMIPS::R0, skipChannelLabel);
	assembler.NOP();

	//Clear interrupt
	assembler.LI(CMIPS::T1, CDMAC::D_STAT);
	assembler.SW(CMIPS::T0, 0x0000, CMIPS::T1);

	//Initialize DMAC handler loop
	assembler.LI(nextIdPtrRegister, BIOS_ADDRESS_DMACHANDLERQUEUE_BASE);

	assembler.MarkLabel(checkHandlerLabel);

	//Check
	assembler.LW(nextIdRegister, 0, nextIdPtrRegister);
	assembler.BEQ(nextIdRegister, CMIPS::R0, skipChannelLabel);
	assembler.ADDIU(nextIdRegister, nextIdRegister, static_cast<uint16>(-1));

	//Get the address to the current DMACHANDLER structure
	assembler.ADDIU(CMIPS::T0, CMIPS::R0, sizeof(DMACHANDLER));
	assembler.MULTU(CMIPS::T0, nextIdRegister, CMIPS::T0);
	assembler.LI(CMIPS::T1, BIOS_ADDRESS_DMACHANDLER_BASE);
	assembler.ADDU(CMIPS::T0, CMIPS::T0, CMIPS::T1);

	//Adjust nextIdPtr
	assembler.ADDIU(nextIdPtrRegister, CMIPS::T0, offsetof(INTCHANDLER, nextId));

	//Check if the channel is good one
	assembler.LW(CMIPS::T1, offsetof(DMACHANDLER, channel), CMIPS::T0);
	assembler.BNE(channelCounterRegister, CMIPS::T1, checkHandlerLabel);
	assembler.NOP();

	//Load the necessary stuff
	assembler.LW(CMIPS::T1, offsetof(DMACHANDLER, address), CMIPS::T0);
	assembler.ADDU(CMIPS::A0, channelCounterRegister, CMIPS::R0);
	assembler.LW(CMIPS::A1, offsetof(DMACHANDLER, arg), CMIPS::T0);
	assembler.LW(CMIPS::GP, offsetof(DMACHANDLER, gp), CMIPS::T0);
	
	//Jump
	assembler.JALR(CMIPS::T1);
	assembler.NOP();

	assembler.BGEZ(CMIPS::V0, checkHandlerLabel);
	assembler.NOP();

	assembler.MarkLabel(skipChannelLabel);

	//Decrement channel counter and test
	assembler.ADDIU(channelCounterRegister, channelCounterRegister, 0xFFFF);
	assembler.BGEZ(channelCounterRegister, testChannelLabel);
	assembler.NOP();

	//Epilogue
	assembler.LD(CMIPS::RA, 0x0000, CMIPS::SP);
	assembler.LD(CMIPS::S0, 0x0008, CMIPS::SP);
	assembler.LD(CMIPS::S1, 0x0010, CMIPS::SP);
	assembler.LD(CMIPS::S2, 0x0018, CMIPS::SP);
	assembler.ADDIU(CMIPS::SP, CMIPS::SP, 0x20);
	assembler.JR(CMIPS::RA);
	assembler.NOP();
}

void CPS2OS::AssembleIntcHandler()
{
	CMIPSAssembler assembler(reinterpret_cast<uint32*>(&m_bios[BIOS_ADDRESS_INTCHANDLER - BIOS_ADDRESS_BASE]));

	auto checkHandlerLabel = assembler.CreateLabel();
	auto finishLoop = assembler.CreateLabel();

	auto nextIdPtrRegister = CMIPS::S0;
	auto causeRegister = CMIPS::S1;

	auto nextIdRegister = CMIPS::T2;

	//Prologue
	assembler.ADDIU(CMIPS::SP, CMIPS::SP, 0xFFE0);
	assembler.SD(CMIPS::RA, 0x0000, CMIPS::SP);
	assembler.SD(CMIPS::S0, 0x0008, CMIPS::SP);
	assembler.SD(CMIPS::S1, 0x0010, CMIPS::SP);

	//Clear INTC cause
	assembler.LI(CMIPS::T1, CINTC::INTC_STAT);
	assembler.ADDIU(CMIPS::T0, CMIPS::R0, 0x0001);
	assembler.SLLV(CMIPS::T0, CMIPS::T0, CMIPS::A0);
	assembler.SW(CMIPS::T0, 0x0000, CMIPS::T1);

	//Initialize INTC handler loop
	assembler.LI(nextIdPtrRegister, BIOS_ADDRESS_INTCHANDLERQUEUE_BASE);
	assembler.ADDU(causeRegister, CMIPS::A0, CMIPS::R0);

	assembler.MarkLabel(checkHandlerLabel);

	//Check
	assembler.LW(nextIdRegister, 0, nextIdPtrRegister);
	assembler.BEQ(nextIdRegister, CMIPS::R0, finishLoop);
	assembler.ADDIU(nextIdRegister, nextIdRegister, static_cast<uint16>(-1));

	//Get the address to the current INTCHANDLER structure
	assembler.ADDIU(CMIPS::T0, CMIPS::R0, sizeof(INTCHANDLER));
	assembler.MULTU(CMIPS::T0, nextIdRegister, CMIPS::T0);
	assembler.LI(CMIPS::T1, BIOS_ADDRESS_INTCHANDLER_BASE);
	assembler.ADDU(CMIPS::T0, CMIPS::T0, CMIPS::T1);

	//Adjust nextIdPtr
	assembler.ADDIU(nextIdPtrRegister, CMIPS::T0, offsetof(INTCHANDLER, nextId));

	//Check if the cause is good one
	assembler.LW(CMIPS::T1, offsetof(INTCHANDLER, cause), CMIPS::T0);
	assembler.BNE(causeRegister, CMIPS::T1, checkHandlerLabel);
	assembler.NOP();

	//Load the necessary stuff
	assembler.LW(CMIPS::T1, offsetof(INTCHANDLER, address), CMIPS::T0);
	assembler.ADDU(CMIPS::A0, causeRegister, CMIPS::R0);
	assembler.LW(CMIPS::A1, offsetof(INTCHANDLER, arg), CMIPS::T0);
	assembler.LW(CMIPS::GP, offsetof(INTCHANDLER, gp), CMIPS::T0);
	
	//Jump
	assembler.JALR(CMIPS::T1);
	assembler.NOP();

	assembler.BGEZ(CMIPS::V0, checkHandlerLabel);
	assembler.NOP();

	assembler.MarkLabel(finishLoop);

	//Epilogue
	assembler.LD(CMIPS::RA, 0x0000, CMIPS::SP);
	assembler.LD(CMIPS::S0, 0x0008, CMIPS::SP);
	assembler.LD(CMIPS::S1, 0x0010, CMIPS::SP);
	assembler.ADDIU(CMIPS::SP, CMIPS::SP, 0x20);
	assembler.JR(CMIPS::RA);
	assembler.NOP();
}

void CPS2OS::AssembleThreadEpilog()
{
	CMIPSAssembler assembler((uint32*)&m_bios[BIOS_ADDRESS_THREADEPILOG - BIOS_ADDRESS_BASE]);
	
	assembler.ADDIU(CMIPS::V1, CMIPS::R0, 0x23);
	assembler.SYSCALL();
}

void CPS2OS::AssembleIdleThreadProc()
{
	CMIPSAssembler assembler((uint32*)&m_bios[BIOS_ADDRESS_IDLETHREADPROC - BIOS_ADDRESS_BASE]);

	assembler.ADDIU(CMIPS::V1, CMIPS::R0, 0x666);
	assembler.SYSCALL();

	assembler.BEQ(CMIPS::R0, CMIPS::R0, 0xFFFD);
	assembler.NOP();
}

void CPS2OS::AssembleAlarmHandler()
{
	CMIPSAssembler assembler(reinterpret_cast<uint32*>(&m_bios[BIOS_ADDRESS_ALARMHANDLER - BIOS_ADDRESS_BASE]));

	auto checkHandlerLabel = assembler.CreateLabel();
	auto moveToNextHandler = assembler.CreateLabel();

	//Prologue
	//S0 -> Handler Counter

	assembler.ADDIU(CMIPS::SP, CMIPS::SP, 0xFFF0);
	assembler.SD(CMIPS::RA, 0x0000, CMIPS::SP);
	assembler.SD(CMIPS::S0, 0x0008, CMIPS::SP);

	//Initialize handler loop
	assembler.ADDU(CMIPS::S0, CMIPS::R0, CMIPS::R0);

	assembler.MarkLabel(checkHandlerLabel);

	//Get the address to the current INTCHANDLER structure
	assembler.ADDIU(CMIPS::T0, CMIPS::R0, sizeof(ALARM));
	assembler.MULTU(CMIPS::T0, CMIPS::S0, CMIPS::T0);
	assembler.LI(CMIPS::T1, BIOS_ADDRESS_ALARM_BASE);
	assembler.ADDU(CMIPS::T0, CMIPS::T0, CMIPS::T1);

	//Check validity
	assembler.LW(CMIPS::T1, offsetof(ALARM, isValid), CMIPS::T0);
	assembler.BEQ(CMIPS::T1, CMIPS::R0, moveToNextHandler);
	assembler.NOP();

	//Load the necessary stuff
	assembler.LW(CMIPS::T1, offsetof(ALARM, callback), CMIPS::T0);
	assembler.ADDIU(CMIPS::A0, CMIPS::S0, BIOS_ID_BASE);
	assembler.LW(CMIPS::A1, offsetof(ALARM, delay), CMIPS::T0);
	assembler.LW(CMIPS::A2, offsetof(ALARM, callbackParam), CMIPS::T0);
	assembler.LW(CMIPS::GP, offsetof(ALARM, gp), CMIPS::T0);
	
	//Jump
	assembler.JALR(CMIPS::T1);
	assembler.NOP();

	//Delete handler (call iReleaseAlarm)
	assembler.ADDIU(CMIPS::A0, CMIPS::S0, BIOS_ID_BASE);
	assembler.ADDIU(CMIPS::V1, CMIPS::R0, -0x1F);
	assembler.SYSCALL();

	assembler.MarkLabel(moveToNextHandler);

	//Increment handler counter and test
	assembler.ADDIU(CMIPS::S0, CMIPS::S0, 0x0001);
	assembler.ADDIU(CMIPS::T0, CMIPS::R0, MAX_ALARM - 1);
	assembler.BNE(CMIPS::S0, CMIPS::T0, checkHandlerLabel);
	assembler.NOP();

	//Epilogue
	assembler.LD(CMIPS::RA, 0x0000, CMIPS::SP);
	assembler.LD(CMIPS::S0, 0x0008, CMIPS::SP);
	assembler.ADDIU(CMIPS::SP, CMIPS::SP, 0x10);
	assembler.JR(CMIPS::RA);
	assembler.NOP();
}

uint32* CPS2OS::GetCustomSyscallTable()
{
	return (uint32*)&m_ram[0x00010000];
}

void CPS2OS::LinkThread(uint32 threadId)
{
	auto thread = m_threads[threadId];

	for(auto threadSchedulePair : m_threadSchedule)
	{
		auto scheduledThread = threadSchedulePair.second;
		if(scheduledThread->currPriority > thread->currPriority)
		{
			m_threadSchedule.AddBefore(threadSchedulePair.first, threadId);
			return;
		}
	}

	m_threadSchedule.PushBack(threadId);
}

void CPS2OS::UnlinkThread(uint32 threadId)
{
	m_threadSchedule.Unlink(threadId);
}

void CPS2OS::ThreadShakeAndBake()
{
	//Don't play with fire (don't switch if we're in exception mode)
	if(m_ee.m_State.nCOP0[CCOP_SCU::STATUS] & CMIPS::STATUS_EXL)
	{
		return;
	}

	//Don't switch if interrupts are disabled
	if((m_ee.m_State.nCOP0[CCOP_SCU::STATUS] & INTERRUPTS_ENABLED_MASK) != INTERRUPTS_ENABLED_MASK)
	{
		return;
	}

	//Select thread to execute
	{
		uint32 nextThreadId = 0;
		if(m_threadSchedule.IsEmpty())
		{
			//No thread ready to be executed
			nextThreadId = m_idleThreadId;
		}
		else
		{
			auto nextThreadPair = *m_threadSchedule.begin();
			nextThreadId = nextThreadPair.first;
			assert(nextThreadPair.second->status == THREAD_RUNNING);
		}
		ThreadSwitchContext(nextThreadId);
	}
}

void CPS2OS::ThreadSwitchContext(uint32 id)
{
	if(id == m_currentThreadId) return;

	//Save the context of the current thread
	{
		auto thread = m_threads[m_currentThreadId];
		assert(thread);
		thread->contextPtr = m_ee.m_State.nGPR[CMIPS::SP].nV0 - STACKRES;
		assert(thread->contextPtr >= thread->stackBase);

		auto context = reinterpret_cast<THREADCONTEXT*>(GetStructPtr(thread->contextPtr));

		//Save the context
		for(uint32 i = 0; i < 0x20; i++)
		{
			if(i == CMIPS::R0) continue;
			if(i == CMIPS::K0) continue;
			if(i == CMIPS::K1) continue;
			context->gpr[i] = m_ee.m_State.nGPR[i];
		}
		for(uint32 i = 0; i < 0x20; i++)
		{
			context->cop1[i] = m_ee.m_State.nCOP1[i];
		}
		auto& sa = context->gpr[CMIPS::R0].nV0;
		auto& hi = context->gpr[CMIPS::K0];
		auto& lo = context->gpr[CMIPS::K1];
		sa = m_ee.m_State.nSA >> 3;    //Act as if MFSA was used
		hi.nV[0] = m_ee.m_State.nHI[0];  hi.nV[1] = m_ee.m_State.nHI[1];
		hi.nV[2] = m_ee.m_State.nHI1[0]; hi.nV[3] = m_ee.m_State.nHI1[1];
		lo.nV[0] = m_ee.m_State.nLO[0];  lo.nV[1] = m_ee.m_State.nLO[1];
		lo.nV[2] = m_ee.m_State.nLO1[0]; lo.nV[3] = m_ee.m_State.nLO1[1];
		context->cop1a = m_ee.m_State.nCOP1A;
		context->fcsr = m_ee.m_State.nFCSR;

		thread->epc = m_ee.m_State.nPC;
	}

	m_currentThreadId = id;

	//Load the new context
	{
		auto thread = m_threads[m_currentThreadId];
		assert(thread);
		m_ee.m_State.nPC = thread->epc;

		//Idle thread doesn't have a context
		if(id != m_idleThreadId)
		{
			assert(thread->contextPtr != 0);
			auto context = reinterpret_cast<const THREADCONTEXT*>(GetStructPtr(thread->contextPtr));

			for(uint32 i = 0; i < 0x20; i++)
			{
				if(i == CMIPS::R0) continue;
				if(i == CMIPS::K0) continue;
				if(i == CMIPS::K1) continue;
				m_ee.m_State.nGPR[i] = context->gpr[i];
			}
			for(uint32 i = 0; i < 0x20; i++)
			{
				m_ee.m_State.nCOP1[i] = context->cop1[i];
			}
			auto& sa = context->gpr[CMIPS::R0].nV0;
			auto& hi = context->gpr[CMIPS::K0];
			auto& lo = context->gpr[CMIPS::K1];
			m_ee.m_State.nSA = (sa & 0x0F) << 3;    //Act as if MTSA was used
			m_ee.m_State.nHI[0]  = hi.nV[0]; m_ee.m_State.nHI[1]  = hi.nV[1];
			m_ee.m_State.nHI1[0] = hi.nV[2]; m_ee.m_State.nHI1[1] = hi.nV[3];
			m_ee.m_State.nLO[0]  = lo.nV[0]; m_ee.m_State.nLO[1]  = lo.nV[1];
			m_ee.m_State.nLO1[0] = lo.nV[2]; m_ee.m_State.nLO1[1] = lo.nV[3];
			m_ee.m_State.nCOP1A = context->cop1a;
			m_ee.m_State.nFCSR = context->fcsr;
		}
	}

	CLog::GetInstance().Print(LOG_NAME, "New thread elected (id = %i).\r\n", id);
}

void CPS2OS::ThreadReset(uint32 id)
{
	assert(m_currentThreadId != id);

	auto thread = m_threads[id];
	assert(thread->status == THREAD_ZOMBIE);

	uint32 stackTop = thread->stackBase + thread->stackSize;

	thread->contextPtr = stackTop - STACKRES;
	thread->currPriority = thread->initPriority;
	//Reset wakeup count?

	auto context = reinterpret_cast<THREADCONTEXT*>(GetStructPtr(thread->contextPtr));
	context->gpr[CMIPS::SP].nV0 = stackTop - STACK_FRAME_RESERVE_SIZE;
	context->gpr[CMIPS::FP].nV0 = stackTop - STACK_FRAME_RESERVE_SIZE;
	context->gpr[CMIPS::GP].nV0 = thread->gp;
	context->gpr[CMIPS::RA].nV0 = BIOS_ADDRESS_THREADEPILOG;
}

void CPS2OS::CheckLivingThreads()
{
	//Check if we have a living thread (this is needed for autotests to work properly)
	bool hasLiveThread = false;
	for(auto thread : m_threads)
	{
		if(!thread) continue;
		if(thread->status != THREAD_ZOMBIE)
		{
			hasLiveThread = true;
			break;
		}
	}

	if(!hasLiveThread)
	{
		OnRequestExit();
	}
}

bool CPS2OS::SemaReleaseSingleThread(uint32 semaId, bool cancelled)
{
	//Releases a single thread from a semaphore's queue
	//TODO: Implement an actual queue

	auto sema = m_semaphores[semaId];
	assert(sema);
	assert(sema->waitCount != 0);

	uint32 returnValue = cancelled ? -1 : semaId;
	bool changed = false;
	for(auto threadIterator = std::begin(m_threads); threadIterator != std::end(m_threads); threadIterator++)
	{
		auto thread = *threadIterator;
		if(!thread) continue;
		if((thread->status != THREAD_WAITING) && (thread->status != THREAD_SUSPENDED_WAITING)) continue;
		if(thread->semaWait != semaId) continue;

		switch(thread->status)
		{
		case THREAD_WAITING:
			thread->status = THREAD_RUNNING;
			LinkThread(threadIterator);
			break;
		case THREAD_SUSPENDED_WAITING:
			thread->status = THREAD_SUSPENDED;
			break;
		default:
			assert(0);
			break;
		}

		auto context = reinterpret_cast<THREADCONTEXT*>(GetStructPtr(thread->contextPtr));
		context->gpr[SC_RETURN].nD0 = static_cast<int32>(returnValue);

		sema->waitCount--;
		changed = true;
		break;
	}

	//Something went wrong if nothing changed
	assert(changed);
	return changed;
}

void CPS2OS::CreateIdleThread()
{
	m_idleThreadId = m_threads.Allocate();
	auto thread = m_threads[m_idleThreadId];
	thread->epc			= BIOS_ADDRESS_IDLETHREADPROC;
	thread->status		= THREAD_ZOMBIE;
}

std::pair<uint32, uint32> CPS2OS::GetVsyncFlagPtrs() const
{
	uint32 value1Ptr = *reinterpret_cast<uint32*>(m_ram + BIOS_ADDRESS_VSYNCFLAG_VALUE1PTR);
	uint32 value2Ptr = *reinterpret_cast<uint32*>(m_ram + BIOS_ADDRESS_VSYNCFLAG_VALUE2PTR);
	return std::make_pair(value1Ptr, value2Ptr);
}

void CPS2OS::SetVsyncFlagPtrs(uint32 value1Ptr, uint32 value2Ptr)
{
	*reinterpret_cast<uint32*>(m_ram + BIOS_ADDRESS_VSYNCFLAG_VALUE1PTR) = value1Ptr;
	*reinterpret_cast<uint32*>(m_ram + BIOS_ADDRESS_VSYNCFLAG_VALUE2PTR) = value2Ptr;
}

uint8* CPS2OS::GetStructPtr(uint32 address) const
{
	address = TranslateAddress(nullptr, address);
	uint8* memory = nullptr;
	if((address >= PS2::EE_SPR_ADDR) && (address < (PS2::EE_SPR_ADDR + PS2::EE_SPR_SIZE)))
	{
		address &= (PS2::EE_SPR_SIZE - 1);
		memory = m_spr;
	}
	else
	{
		address &= (PS2::EE_RAM_SIZE - 1);
		memory = m_ram;
	}
	return memory + address;
}

uint32 CPS2OS::GetNextAvailableDeci2HandlerId()
{
	for(uint32 i = 1; i < MAX_DECI2HANDLER; i++)
	{
		DECI2HANDLER* handler = GetDeci2Handler(i);
		if(handler->valid != 1)
		{
			return i;
		}
	}

	return 0xFFFFFFFF;
}

CPS2OS::DECI2HANDLER* CPS2OS::GetDeci2Handler(uint32 id)
{
	id--;
	return &((DECI2HANDLER*)&m_ram[0x00008000])[id];
}

void CPS2OS::HandleInterrupt()
{
	//Check if interrupts are enabled here because EIE bit isn't checked by CMIPS
	if((m_ee.m_State.nCOP0[CCOP_SCU::STATUS] & INTERRUPTS_ENABLED_MASK) != INTERRUPTS_ENABLED_MASK)
	{
		return;
	}

	m_semaWaitCount = 0;
	m_ee.GenerateInterrupt(0x1FC00200);
}

void CPS2OS::HandleReturnFromException()
{
	ThreadShakeAndBake();
}

bool CPS2OS::CheckVBlankFlag()
{
	bool changed = false;
	auto vsyncFlagPtrs = GetVsyncFlagPtrs();

	if(vsyncFlagPtrs.first != 0)
	{
		*reinterpret_cast<uint32*>(m_ram + vsyncFlagPtrs.first) = 1;
		changed = true;
	}
	if(vsyncFlagPtrs.second != 0)
	{
		*reinterpret_cast<uint64*>(m_ram + vsyncFlagPtrs.second) = m_gs->ReadPrivRegister(CGSHandler::GS_CSR);
		changed = true;
	}

	SetVsyncFlagPtrs(0, 0);
	return changed;
}

uint32 CPS2OS::TranslateAddress(CMIPS*, uint32 vaddrLo)
{
	if(vaddrLo >= 0x70000000 && vaddrLo <= 0x70003FFF)
	{
		//SPR memory access, the TLB entry for this should have the SPR bit set
		static const uint32 addressFixUp = 0x70000000 - PS2::EE_SPR_ADDR;
		return (vaddrLo - addressFixUp);
	}
	if(vaddrLo >= 0x30100000 && vaddrLo <= 0x31FFFFFF)
	{
		//RAM access, "uncached & accelerated" as per TLB entry
		return (vaddrLo - 0x30000000);
	}
	return vaddrLo & 0x1FFFFFFF;
}

//////////////////////////////////////////////////
//System Calls
//////////////////////////////////////////////////

void CPS2OS::sc_Unhandled()
{
	CLog::GetInstance().Print(LOG_NAME, "Unknown system call (0x%X) called from 0x%0.8X.\r\n", m_ee.m_State.nGPR[3].nV[0], m_ee.m_State.nPC);
}

//02
void CPS2OS::sc_GsSetCrt()
{
	bool isInterlaced			= (m_ee.m_State.nGPR[SC_PARAM0].nV[0] != 0);
	unsigned int mode			= m_ee.m_State.nGPR[SC_PARAM1].nV[0];
	bool isFrameMode			= (m_ee.m_State.nGPR[SC_PARAM2].nV[0] != 0);

	if(m_gs != NULL)
	{
		m_gs->SetCrt(isInterlaced, mode, isFrameMode);
	}
}


//04
void CPS2OS::sc_Exit()
{
	OnRequestExit();
}

//06
void CPS2OS::sc_LoadExecPS2()
{
	uint32 filePathPtr	= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 argCount		= m_ee.m_State.nGPR[SC_PARAM1].nV[0];
	uint32 argValuesPtr	= m_ee.m_State.nGPR[SC_PARAM2].nV[0];

	ArgumentList arguments;
	for(uint32 i = 0; i < argCount; i++)
	{
		uint32 argValuePtr = *reinterpret_cast<uint32*>(GetStructPtr(argValuesPtr + i * 4));
		arguments.push_back(reinterpret_cast<const char*>(GetStructPtr(argValuePtr)));
	}

	std::string filePath = reinterpret_cast<const char*>(GetStructPtr(filePathPtr));
	if(filePath.find(':') == std::string::npos)
	{
		//Unreal Tournament doesn't provide drive info in path
		filePath = "cdrom0:" + filePath;
	}
	OnRequestLoadExecutable(filePath.c_str(), arguments);
}

//07
void CPS2OS::sc_ExecPS2()
{
	uint32 pc			= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 gp			= m_ee.m_State.nGPR[SC_PARAM1].nV[0];
	uint32 argCount		= m_ee.m_State.nGPR[SC_PARAM2].nV[0];
	uint32 argValuesPtr	= m_ee.m_State.nGPR[SC_PARAM3].nV[0];

	//This is used by Gran Turismo 4. The game clears the program
	//loaded initially, so, we need to make sure there's no stale
	//threads or handlers

	sc_ExitDeleteThread();
	assert(m_currentThreadId == m_idleThreadId);

	//Clear all remaining INTC handlers
	std::vector<uint32> intcHandlerIds;
	for(const auto& intcHandlerPair : m_intcHandlerQueue)
	{
		intcHandlerIds.push_back(intcHandlerPair.first);
	}
	for(const auto& intcHandlerId : intcHandlerIds)
	{
		m_intcHandlerQueue.Unlink(intcHandlerId);
		m_intcHandlers.Free(intcHandlerId);
	}

	m_ee.m_State.nPC = pc;
	m_ee.m_State.nGPR[CMIPS::GP].nD0 = static_cast<int32>(gp);
	m_ee.m_State.nGPR[CMIPS::A0].nD0 = static_cast<int32>(argCount);
	m_ee.m_State.nGPR[CMIPS::A1].nD0 = static_cast<int32>(argValuesPtr);
}

//10
void CPS2OS::sc_AddIntcHandler()
{
	uint32 cause	= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 address	= m_ee.m_State.nGPR[SC_PARAM1].nV[0];
	uint32 next		= m_ee.m_State.nGPR[SC_PARAM2].nV[0];
	uint32 arg		= m_ee.m_State.nGPR[SC_PARAM3].nV[0];

	uint32 id = m_intcHandlers.Allocate();
	if(static_cast<int32>(id) == -1)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	auto handler = m_intcHandlers[id];
	handler->address	= address;
	handler->cause		= cause;
	handler->arg		= arg;
	handler->gp			= m_ee.m_State.nGPR[CMIPS::GP].nV[0];

	if(next == 0)
	{
		m_intcHandlerQueue.PushFront(id);
	}
	else if(static_cast<int32>(next) == -1)
	{
		m_intcHandlerQueue.PushBack(id);
	}
	else
	{
		m_intcHandlerQueue.AddBefore(next, id);
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);
}

//11
void CPS2OS::sc_RemoveIntcHandler()
{
	uint32 cause	= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 id		= m_ee.m_State.nGPR[SC_PARAM1].nV[0];

	auto handler = m_intcHandlers[id];
	if(!handler)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	m_intcHandlerQueue.Unlink(id);
	m_intcHandlers.Free(id);

	m_ee.m_State.nGPR[SC_RETURN].nD0 = 0;
}

//12
void CPS2OS::sc_AddDmacHandler()
{
	uint32 channel	= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 address	= m_ee.m_State.nGPR[SC_PARAM1].nV[0];
	uint32 next		= m_ee.m_State.nGPR[SC_PARAM2].nV[0];
	uint32 arg		= m_ee.m_State.nGPR[SC_PARAM3].nV[0];

	uint32 id = m_dmacHandlers.Allocate();
	if(static_cast<int32>(id) == -1)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	auto handler = m_dmacHandlers[id];
	handler->address	= address;
	handler->channel	= channel;
	handler->arg		= arg;
	handler->gp			= m_ee.m_State.nGPR[CMIPS::GP].nV[0];

	if(next == 0)
	{
		m_dmacHandlerQueue.PushFront(id);
	}
	else if(static_cast<int32>(next) == -1)
	{
		m_dmacHandlerQueue.PushBack(id);
	}
	else
	{
		m_dmacHandlerQueue.AddBefore(next, id);
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = id;
}

//13
void CPS2OS::sc_RemoveDmacHandler()
{
	uint32 channel	= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 id		= m_ee.m_State.nGPR[SC_PARAM1].nV[0];

	auto handler = m_dmacHandlers[id];
	if(!handler)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	m_dmacHandlerQueue.Unlink(id);
	m_dmacHandlers.Free(id);

	m_ee.m_State.nGPR[SC_RETURN].nD0 = 0;
}

//14
//1A
void CPS2OS::sc_EnableIntc()
{
	uint32 cause = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 mask = 1 << cause;
	bool changed = false;

	if(!(m_ee.m_pMemoryMap->GetWord(CINTC::INTC_MASK) & mask))
	{
		m_ee.m_pMemoryMap->SetWord(CINTC::INTC_MASK, mask);
		changed = true;
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = changed ? 1 : 0;
}

//15
//1B
void CPS2OS::sc_DisableIntc()
{
	uint32 cause = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 mask = 1 << cause;
	bool changed = false;

	if(m_ee.m_pMemoryMap->GetWord(CINTC::INTC_MASK) & mask)
	{
		m_ee.m_pMemoryMap->SetWord(CINTC::INTC_MASK, mask);
		changed = true;
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = changed ? 1 : 0;
}

//16
//1C
void CPS2OS::sc_EnableDmac()
{
	uint32 channel = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 registerId = 0x10000 << channel;

	if(!(m_ee.m_pMemoryMap->GetWord(CDMAC::D_STAT) & registerId))
	{
		m_ee.m_pMemoryMap->SetWord(CDMAC::D_STAT, registerId);
	}

	//Enable INT1
	if(!(m_ee.m_pMemoryMap->GetWord(CINTC::INTC_MASK) & 0x02))
	{
		m_ee.m_pMemoryMap->SetWord(CINTC::INTC_MASK, 0x02);
	}

	m_ee.m_State.nGPR[SC_RETURN].nV[0] = 1;
	m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
}

//17
//1D
void CPS2OS::sc_DisableDmac()
{
	uint32 channel = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 registerId = 0x10000 << channel;

	if(m_ee.m_pMemoryMap->GetWord(CDMAC::D_STAT) & registerId)
	{
		m_ee.m_pMemoryMap->SetWord(CDMAC::D_STAT, registerId);
		m_ee.m_State.nGPR[SC_RETURN].nD0 = 1;
	}
	else
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = 0;
	}
}

//18
void CPS2OS::sc_SetAlarm()
{
	uint32 delay			= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 callback			= m_ee.m_State.nGPR[SC_PARAM1].nV[0];
	uint32 callbackParam	= m_ee.m_State.nGPR[SC_PARAM2].nV[0];

	auto alarmId = m_alarms.Allocate();
	assert(alarmId != -1);
	if(alarmId == -1)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = -1;
		return;
	}

	auto alarm = m_alarms[alarmId];
	alarm->delay			= delay;
	alarm->callback			= callback;
	alarm->callbackParam	= callbackParam;
	alarm->gp				= m_ee.m_State.nGPR[CMIPS::GP].nV0;

	m_ee.m_State.nGPR[SC_RETURN].nD0 = alarmId;
}

//1F
void CPS2OS::sc_ReleaseAlarm()
{
	uint32 alarmId = m_ee.m_State.nGPR[SC_PARAM0].nV[0];

	auto alarm = m_alarms[alarmId];
	if(alarm == nullptr)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = -1;
		return;
	}

	m_alarms.Free(alarmId);
}

//20
void CPS2OS::sc_CreateThread()
{
	auto threadParam = reinterpret_cast<THREADPARAM*>(GetStructPtr(m_ee.m_State.nGPR[SC_PARAM0].nV0));

	uint32 id = m_threads.Allocate();
	if(static_cast<int32>(id) == -1)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);
		return;
	}

	auto parentThread = m_threads[m_currentThreadId];
	assert(parentThread);
	uint32 heapBase = parentThread->heapBase;

	assert(threadParam->initPriority < 128);

	auto thread = m_threads[id];
	thread->status			= THREAD_ZOMBIE;
	thread->stackBase		= threadParam->stackBase;
	thread->epc				= threadParam->threadProc;
	thread->threadProc		= threadParam->threadProc;
	thread->initPriority	= threadParam->initPriority;
	thread->heapBase		= heapBase;
	thread->wakeUpCount		= 0;
	thread->gp				= threadParam->gp;
	thread->stackSize		= threadParam->stackSize;

	ThreadReset(id);

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);
}

//21
void CPS2OS::sc_DeleteThread()
{
	uint32 id = m_ee.m_State.nGPR[SC_PARAM0].nV[0];

	if(id == m_currentThreadId)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	if(id >= MAX_THREAD)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	auto thread = m_threads[id];
	if(!thread)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	if(thread->status != THREAD_ZOMBIE)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	m_threads.Free(id);

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);
}

//22
void CPS2OS::sc_StartThread()
{
	uint32 id	= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 arg	= m_ee.m_State.nGPR[SC_PARAM1].nV[0];

	auto thread = m_threads[id];
	if(!thread)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	assert(thread->status == THREAD_ZOMBIE);
	thread->status = THREAD_RUNNING;
	thread->epc    = thread->threadProc;

	auto context = reinterpret_cast<THREADCONTEXT*>(GetStructPtr(thread->contextPtr));
	context->gpr[CMIPS::A0].nV0 = arg;

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);

	LinkThread(id);
	ThreadShakeAndBake();
}

//23
void CPS2OS::sc_ExitThread()
{
	uint32 threadId = m_currentThreadId;

	auto thread = m_threads[threadId];
	thread->status = THREAD_ZOMBIE;
	UnlinkThread(threadId);

	ThreadShakeAndBake();
	ThreadReset(threadId);

	CheckLivingThreads();
}

//24
void CPS2OS::sc_ExitDeleteThread()
{
	uint32 threadId = m_currentThreadId;

	auto thread = m_threads[threadId];
	thread->status = THREAD_ZOMBIE;
	UnlinkThread(threadId);

	ThreadShakeAndBake();

	m_threads.Free(threadId);

	CheckLivingThreads();
}

//25
void CPS2OS::sc_TerminateThread()
{
	uint32 id = m_ee.m_State.nGPR[SC_PARAM0].nV[0];

	if(id == m_currentThreadId)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	auto thread = m_threads[id];
	if(!thread)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	if(thread->status == THREAD_ZOMBIE)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	thread->status = THREAD_ZOMBIE;
	UnlinkThread(id);
	ThreadReset(id);

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);
}

//29
//2A
void CPS2OS::sc_ChangeThreadPriority()
{
	bool isInt		= m_ee.m_State.nGPR[3].nV[0] == 0x2A;
	uint32 id		= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 prio		= m_ee.m_State.nGPR[SC_PARAM1].nV[0];

	auto thread = m_threads[id];
	if(!thread)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	uint32 prevPrio = thread->currPriority;
	thread->currPriority = prio;

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(prevPrio);

	//Reschedule if thread is already running
	if(thread->status == THREAD_RUNNING)
	{
		UnlinkThread(id);
		LinkThread(id);
	}

	if(!isInt)
	{
		ThreadShakeAndBake();
	}
}

//2B
void CPS2OS::sc_RotateThreadReadyQueue()
{
	uint32 prio = m_ee.m_State.nGPR[SC_PARAM0].nV[0];

	//Find first of this priority and reinsert if it's the same as the current thread
	//If it's not the same, the schedule will be rotated when another thread is choosen
	for(auto threadSchedulePair : m_threadSchedule)
	{
		auto scheduledThread = threadSchedulePair.second;
		if(scheduledThread->currPriority == prio)
		{
			UnlinkThread(threadSchedulePair.first);
			LinkThread(threadSchedulePair.first);
			break;
		}
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(prio);

	ThreadShakeAndBake();
}

//2F
void CPS2OS::sc_GetThreadId()
{
	m_ee.m_State.nGPR[SC_RETURN].nV[0] = m_currentThreadId;
	m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
}

//30
void CPS2OS::sc_ReferThreadStatus()
{
	uint32 id			= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 statusPtr	= m_ee.m_State.nGPR[SC_PARAM1].nV[0];

	if(id >= MAX_THREAD)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	if(id == 0)
	{
		id = m_currentThreadId;
	}

	auto thread = m_threads[id];
	if(!thread)
	{
		//TODO: This is actually valid on a real PS2 and won't return an error
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	uint32 ret = 0;
	switch(thread->status)
	{
	case THREAD_RUNNING:
		if(id == m_currentThreadId)
		{
			ret = THS_RUN;
		}
		else
		{
			ret = THS_READY;
		}
		break;
	case THREAD_WAITING:
	case THREAD_SLEEPING:
		ret = THS_WAIT;
		break;
	case THREAD_SUSPENDED:
		ret = THS_SUSPEND;
		break;
	case THREAD_SUSPENDED_WAITING:
	case THREAD_SUSPENDED_SLEEPING:
		ret = (THS_WAIT | THS_SUSPEND);
		break;
	case THREAD_ZOMBIE:
		ret = THS_DORMANT;
		break;
	}

	uint32 waitType = 0;
	switch(thread->status)
	{
	case THREAD_SLEEPING:
	case THREAD_SUSPENDED_SLEEPING:
		waitType = 1;
		break;
	case THREAD_WAITING:
	case THREAD_SUSPENDED_WAITING:
		waitType = 2;
		break;
	default:
		waitType = 0;
		break;
	}

	if(statusPtr != 0)
	{
		auto status = reinterpret_cast<THREADSTATUS*>(GetStructPtr(statusPtr));

		status->status          = ret;
		status->initPriority    = thread->initPriority;
		status->currPriority    = thread->currPriority;
		status->stackBase       = thread->stackBase;
		status->stackSize       = thread->stackSize;
		status->waitType        = waitType;
		status->wakeupCount     = thread->wakeUpCount;
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = ret;
}

//32
void CPS2OS::sc_SleepThread()
{
	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(m_currentThreadId);

	auto thread = m_threads[m_currentThreadId];
	if(thread->wakeUpCount == 0)
	{
		assert(thread->status == THREAD_RUNNING);
		thread->status = THREAD_SLEEPING;
		UnlinkThread(m_currentThreadId);
		ThreadShakeAndBake();
		return;
	}

	thread->wakeUpCount--;
}

//33
//34
void CPS2OS::sc_WakeupThread()
{
	uint32 id		= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	bool isInt		= m_ee.m_State.nGPR[3].nV[0] == 0x34;

	if((id == 0) || (id == m_currentThreadId))
	{
		//Can't wakeup a thread that's already running
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	auto thread = m_threads[id];
	if(!thread)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	if(thread->status == THREAD_ZOMBIE)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);

	if(
		(thread->status == THREAD_SLEEPING) || 
		(thread->status == THREAD_SUSPENDED_SLEEPING))
	{
		switch(thread->status)
		{
		case THREAD_SLEEPING:
			thread->status = THREAD_RUNNING;
			LinkThread(id);
			break;
		case THREAD_SUSPENDED_SLEEPING:
			thread->status = THREAD_SUSPENDED;
			break;
		default:
			assert(0);
			break;
		}
		if(!isInt)
		{
			ThreadShakeAndBake();
		}
	}
	else
	{
		thread->wakeUpCount++;
	}
}

//35
//36
void CPS2OS::sc_CancelWakeupThread()
{
	uint32 id		= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	bool isInt		= m_ee.m_State.nGPR[3].nV[0] == 0x36;

	auto thread = m_threads[id];
	if(!thread)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	uint32 wakeUpCount = thread->wakeUpCount;
	thread->wakeUpCount = 0;

	m_ee.m_State.nGPR[SC_RETURN].nD0 = wakeUpCount;
}

//37
//38
void CPS2OS::sc_SuspendThread()
{
	uint32 id = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	bool isInt = m_ee.m_State.nGPR[3].nV[0] == 0x38;

	if(id == m_currentThreadId)
	{
		//This actually works on a real PS2
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	auto thread = m_threads[id];
	if(!thread)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	if(
		(thread->status == THREAD_ZOMBIE) ||
		(thread->status == THREAD_SUSPENDED) ||
		(thread->status == THREAD_SUSPENDED_WAITING) ||
		(thread->status == THREAD_SUSPENDED_SLEEPING)
		)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	switch(thread->status)
	{
	case THREAD_RUNNING:
		thread->status = THREAD_SUSPENDED;
		UnlinkThread(id);
		break;
	case THREAD_WAITING:
		thread->status = THREAD_SUSPENDED_WAITING;
		break;
	case THREAD_SLEEPING:
		thread->status = THREAD_SUSPENDED_SLEEPING;
		break;
	default:
		assert(0);
		break;
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);

	if(!isInt)
	{
		ThreadShakeAndBake();
	}
}

//39
void CPS2OS::sc_ResumeThread()
{
	uint32 id = m_ee.m_State.nGPR[SC_PARAM0].nV[0];

	if(id == m_currentThreadId)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	auto thread = m_threads[id];
	if(!thread)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	if(
		(thread->status == THREAD_ZOMBIE) ||
		(thread->status == THREAD_RUNNING) ||
		(thread->status == THREAD_WAITING) ||
		(thread->status == THREAD_SLEEPING)
		)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	switch(thread->status)
	{
	case THREAD_SUSPENDED:
		thread->status = THREAD_RUNNING;
		LinkThread(id);
		break;
	case THREAD_SUSPENDED_WAITING:
		thread->status = THREAD_WAITING;
		break;
	case THREAD_SUSPENDED_SLEEPING:
		thread->status = THREAD_SLEEPING;
		break;
	default:
		assert(0);
		break;
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);

	ThreadShakeAndBake();
}

//3C
void CPS2OS::sc_SetupThread()
{
	uint32 stackBase = m_ee.m_State.nGPR[SC_PARAM1].nV[0];
	uint32 stackSize = m_ee.m_State.nGPR[SC_PARAM2].nV[0];

	uint32 stackAddr = 0;
	if(stackBase == 0xFFFFFFFF)
	{
		stackAddr = 0x02000000;
	}
	else
	{
		stackAddr = stackBase + stackSize;
	}

	uint32 argsBase = m_ee.m_State.nGPR[SC_PARAM3].nV[0];
	//Copy arguments
	{
		ArgumentList completeArgList;
		completeArgList.push_back(m_executableName);
		completeArgList.insert(completeArgList.end(), m_currentArguments.begin(), m_currentArguments.end());

		uint32 argsCount = static_cast<uint32>(completeArgList.size());

		*reinterpret_cast<uint32*>(m_ram + argsBase) = argsCount;
		uint32 argsPtrs = argsBase + 4;
		//We add 1 to argsCount because argv[argc] must be equal to 0
		uint32 argsPayload = argsPtrs + ((argsCount + 1) * 4);
		for(uint32 i = 0; i < argsCount; i++)
		{
			const auto& currentArg = completeArgList[i];
			*reinterpret_cast<uint32*>(m_ram + argsPtrs + (i * 4)) = argsPayload;
			uint32 argSize = static_cast<uint32>(currentArg.size()) + 1;
			memcpy(m_ram + argsPayload, currentArg.c_str(), argSize);
			argsPayload += argSize;
		}
		//Set argv[argc] to 0
		*reinterpret_cast<uint32*>(m_ram + argsPtrs + (argsCount * 4)) = 0;
	}

	uint32 threadId = m_threads.Allocate();
	assert(static_cast<int32>(threadId) != -1);

	//Set up the main thread
	//Priority needs to be 0 because some games rely on this
	//by calling RotateThreadReadyQueue(0) (Dynasty Warriors 2)
	auto thread = m_threads[threadId];
	thread->status			= THREAD_RUNNING;
	thread->stackBase		= stackAddr - stackSize;
	thread->initPriority	= 0;
	thread->currPriority	= 0;
	thread->contextPtr		= 0;

	LinkThread(threadId);
	m_currentThreadId = threadId;

	m_ee.m_State.nGPR[SC_RETURN].nV[0] = stackAddr;
	m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
}

//3D
void CPS2OS::sc_SetupHeap()
{
	auto thread = m_threads[m_currentThreadId];

	uint32 heapBase = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 heapSize = m_ee.m_State.nGPR[SC_PARAM1].nV[0];

	if(heapSize == 0xFFFFFFFF)
	{
		thread->heapBase = thread->stackBase;
	}
	else
	{
		thread->heapBase = heapBase + heapSize;
	}

	m_ee.m_State.nGPR[SC_RETURN].nV[0] = thread->heapBase;
	m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
}

//3E
void CPS2OS::sc_EndOfHeap()
{
	auto thread = m_threads[m_currentThreadId];

	m_ee.m_State.nGPR[SC_RETURN].nV[0] = thread->heapBase;
	m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
}

//40
void CPS2OS::sc_CreateSema()
{
	auto semaParam = reinterpret_cast<SEMAPHOREPARAM*>(GetStructPtr(m_ee.m_State.nGPR[SC_PARAM0].nV0));

	uint32 id = m_semaphores.Allocate();
	if(id == 0xFFFFFFFF)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = -1;
		return;
	}

	auto sema = m_semaphores[id];
	sema->count			= semaParam->initCount;
	sema->maxCount		= semaParam->maxCount;
	sema->waitCount		= 0;

	assert(sema->count <= sema->maxCount);

	m_ee.m_State.nGPR[SC_RETURN].nD0 = id;
}

//41
void CPS2OS::sc_DeleteSema()
{
	uint32 id = m_ee.m_State.nGPR[SC_PARAM0].nV[0];

	auto sema = m_semaphores[id];
	if(sema == nullptr)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}

	//Set return value now because we might reschedule
	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);

	//Release all threads waiting for this semaphore
	if(sema->waitCount != 0)
	{
		while(sema->waitCount != 0)
		{
			bool succeeded = SemaReleaseSingleThread(id, true);
			if(!succeeded) break;
		}
		ThreadShakeAndBake();
	}

	m_semaphores.Free(id);
}

//42
//43
void CPS2OS::sc_SignalSema()
{
	bool isInt	= m_ee.m_State.nGPR[3].nV[0] == 0x43;
	uint32 id	= m_ee.m_State.nGPR[SC_PARAM0].nV[0];

	auto sema = m_semaphores[id];
	if(sema == nullptr)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
		return;
	}
	
	//TODO: Check maximum value

	//Set return value here because we might reschedule
	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(id);

	if(sema->waitCount != 0)
	{
		SemaReleaseSingleThread(id, false);
		if(!isInt)
		{
			ThreadShakeAndBake();
		}
	}
	else
	{
		sema->count++;
	}
}

//44
void CPS2OS::sc_WaitSema()
{
	uint32 id = m_ee.m_State.nGPR[SC_PARAM0].nV[0];

	auto sema = m_semaphores[id];
	if(sema == nullptr)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = -1;
		return;
	}

	//Check if we can activate a speed hack for a thread that is stuck
	//in a loop checking the same semaphore over and over
	//This helps skipping an idle loop in Castlevania: Curse of Darkness
	if((m_semaWaitId == id) && (m_semaWaitCaller == m_ee.m_State.nGPR[CMIPS::RA].nV0))
	{
		m_semaWaitCount++;
		if(m_semaWaitCount > 100)
		{
			m_semaWaitThreadId = m_currentThreadId;
		}
	}
	else
	{
		m_semaWaitId = id;
		m_semaWaitCaller = m_ee.m_State.nGPR[CMIPS::RA].nV0;
		m_semaWaitCount = 0;
		m_semaWaitThreadId = -1;
	}

	if(sema->count == 0)
	{
		//Put this thread in sleep mode and reschedule...
		sema->waitCount++;

		auto thread = m_threads[m_currentThreadId];
		assert(thread->status == THREAD_RUNNING);
		thread->status   = THREAD_WAITING;
		thread->semaWait = id;

		UnlinkThread(m_currentThreadId);
		ThreadShakeAndBake();

		return;
	}

	if(sema->count != 0)
	{
		sema->count--;
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = id;
}

//45
//46
void CPS2OS::sc_PollSema()
{
	uint32 id = m_ee.m_State.nGPR[SC_PARAM0].nV[0];

	auto sema = m_semaphores[id];
	if(sema == nullptr)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = -1;
		return;
	}

	if(sema->count == 0)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = -1;
		return;
	}

	sema->count--;
	
	m_ee.m_State.nGPR[SC_RETURN].nD0 = id;
}

//47
//48
void CPS2OS::sc_ReferSemaStatus()
{
	bool isInt = m_ee.m_State.nGPR[3].nV[0] != 0x47;
	uint32 id = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	auto semaParam = reinterpret_cast<SEMAPHOREPARAM*>(GetStructPtr(m_ee.m_State.nGPR[SC_PARAM1].nV0));

	auto sema = m_semaphores[id];
	if(sema == nullptr)
	{
		m_ee.m_State.nGPR[SC_RETURN].nD0 = -1;
		return;
	}

	semaParam->count		= sema->count;
	semaParam->maxCount		= sema->maxCount;
	semaParam->waitThreads	= sema->waitCount;

	m_ee.m_State.nGPR[SC_RETURN].nD0 = id;
}

//64
void CPS2OS::sc_FlushCache()
{
	uint32 operationType = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
}

//70
void CPS2OS::sc_GsGetIMR()
{
	uint32 result = 0;

	if(m_gs != NULL)
	{
		result = m_gs->ReadPrivRegister(CGSHandler::GS_IMR);
	}

	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(result);
}

//71
void CPS2OS::sc_GsPutIMR()
{
	uint32 imr = m_ee.m_State.nGPR[SC_PARAM0].nV[0];

	if(m_gs != NULL)
	{
		m_gs->WritePrivRegister(CGSHandler::GS_IMR, imr);
	}
}

//73
void CPS2OS::sc_SetVSyncFlag()
{
	uint32 ptr1 = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 ptr2 = m_ee.m_State.nGPR[SC_PARAM1].nV[0];

	SetVsyncFlagPtrs(ptr1, ptr2);

	m_ee.m_State.nGPR[SC_RETURN].nV[0] = 0;
	m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
}

//74
void CPS2OS::sc_SetSyscall()
{
	uint32 number = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 address = m_ee.m_State.nGPR[SC_PARAM1].nV[0];

	if(number < 0x100)
	{
		GetCustomSyscallTable()[number] = address;
	}
	else if(number == 0x12C)
	{
		//This syscall doesn't do any bounds checking and it's possible to
		//set INTC handler for timer 3 through this system call (a particular version of libcdvd does this)
		//My guess is that the BIOS doesn't process custom INTC handlers for INT12 (timer 3) and the
		//only way to have an handler placed on that interrupt line is to do that trick.

		unsigned int line = 12;

		uint32 handlerId = m_intcHandlers.Allocate();
		if(static_cast<int32>(handlerId) == -1)
		{
			CLog::GetInstance().Print(LOG_NAME, "Couldn't set INTC handler through SetSyscall");
			return;
		}

		auto handler = m_intcHandlers[handlerId];
		handler->address	= address & 0x1FFFFFFF;
		handler->cause		= line;
		handler->arg		= 0;
		handler->gp			= 0;

		if(!(m_ee.m_pMemoryMap->GetWord(CINTC::INTC_MASK) & (1 << line)))
		{
			m_ee.m_pMemoryMap->SetWord(CINTC::INTC_MASK, (1 << line));
		}

		m_intcHandlerQueue.PushFront(handlerId);
	}
	else
	{
		CLog::GetInstance().Print(LOG_NAME, "Unknown syscall set.", number);
	}

	m_ee.m_State.nGPR[SC_RETURN].nV[0] = 0;
	m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
}

//76
void CPS2OS::sc_SifDmaStat()
{
	//If SIF dma has just been set (100 cycle delay), return 'queued' status.
	//This is required for Okami
	int64 timerDiff = static_cast<uint64>(m_ee.m_State.nCOP0[CCOP_SCU::COUNT]) - static_cast<uint64>(m_lastSifDmaTime);
	if((timerDiff < 0) || (timerDiff > 100))
	{
		//Completed
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(-1);
	}
	else
	{
		//Queued
		m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(1);
	}
}

//77
void CPS2OS::sc_SifSetDma()
{
	m_lastSifDmaTime = m_ee.m_State.nCOP0[CCOP_SCU::COUNT];

	auto xfers = reinterpret_cast<const SIFDMAREG*>(GetStructPtr(m_ee.m_State.nGPR[SC_PARAM0].nV0));
	uint32 count = m_ee.m_State.nGPR[SC_PARAM1].nV[0];

	//Returns count
	//DMA might call an interrupt handler
	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(count);

	for(unsigned int i = 0; i < count; i++)
	{
		const auto& xfer = xfers[i];

		uint32 size = (xfer.size + 0x0F) / 0x10;
		assert((xfer.srcAddr & 0x0F) == 0);
		assert((xfer.dstAddr & 0x03) == 0);

		m_ee.m_pMemoryMap->SetWord(CDMAC::D6_MADR, xfer.srcAddr);
		m_ee.m_pMemoryMap->SetWord(CDMAC::D6_TADR, xfer.dstAddr);
		m_ee.m_pMemoryMap->SetWord(CDMAC::D6_QWC,  size);
		m_ee.m_pMemoryMap->SetWord(CDMAC::D6_CHCR, CDMAC::CHCR_BIT::CHCR_STR);
	}
}

//78
void CPS2OS::sc_SifSetDChain()
{
	//Humm, set the SIF0 DMA channel in destination chain mode?

	//This syscall is invoked by the SIF dma interrupt handler (when a packet has been received)
	//To make sure every packet generates an interrupt, the SIF will hold into any packet 
	//that it might have in its queue while a packet is being processed.
	//The MarkPacketProcess function will tell the SIF that the packet has been processed
	//and that it can continue sending packets over. (Needed for Guilty Gear XX Accent Core Plus)
	bool isInt = m_ee.m_State.nGPR[3].nV[1] == ~0;
	if(isInt)
	{
		m_sif.MarkPacketProcessed();
	}
}

//79
void CPS2OS::sc_SifSetReg()
{
	uint32 registerId	= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 value		= m_ee.m_State.nGPR[SC_PARAM1].nV[0];

	m_sif.SetRegister(registerId, value);

	m_ee.m_State.nGPR[SC_RETURN].nD0 = 0;
}

//7A
void CPS2OS::sc_SifGetReg()
{
	uint32 registerId = m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	m_ee.m_State.nGPR[SC_RETURN].nD0 = static_cast<int32>(m_sif.GetRegister(registerId));
}

//7C
void CPS2OS::sc_Deci2Call()
{
	uint32 function		= m_ee.m_State.nGPR[SC_PARAM0].nV[0];
	uint32 param		= m_ee.m_State.nGPR[SC_PARAM1].nV[0];
	
	switch(function)
	{
	case 0x01:
		//Deci2Open
		{
			uint32 id = GetNextAvailableDeci2HandlerId();

			DECI2HANDLER* handler = GetDeci2Handler(id);
			handler->valid		= 1;
			handler->device		= *(uint32*)&m_ram[param + 0x00];
			handler->bufferAddr	= *(uint32*)&m_ram[param + 0x04];

			m_ee.m_State.nGPR[SC_RETURN].nV[0] = id;
			m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
		}
		break;
	case 0x03:
		//Deci2Send
		{
			uint32 id = *reinterpret_cast<uint32*>(GetStructPtr(param + 0x00));

			DECI2HANDLER* handler = GetDeci2Handler(id);

			if(handler->valid != 0)
			{
				uint32 stringAddr = *reinterpret_cast<uint32*>(&m_ram[handler->bufferAddr + 0x10]);
				stringAddr &= (PS2::EE_RAM_SIZE - 1);

				uint32 length = m_ram[stringAddr + 0x00] - 0x0C;
				uint8* string = &m_ram[stringAddr + 0x0C];

				m_iopBios.GetIoman()->Write(Iop::CIoman::FID_STDOUT, length, string);
			}

			m_ee.m_State.nGPR[SC_RETURN].nV[0] = 1;
			m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
		}
		break;
	case 0x04:
		//Deci2Poll
		{
			uint32 id = *reinterpret_cast<uint32*>(GetStructPtr(param + 0x00));
		
			DECI2HANDLER* handler = GetDeci2Handler(id);
			if(handler->valid != 0)
			{
				*(uint32*)&m_ram[handler->bufferAddr + 0x0C] = 0;
			}

			m_ee.m_State.nGPR[SC_RETURN].nV[0] = 1;
			m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
		}
		break;
	case 0x10:
		//kPuts
		{
			uint32 stringAddr = *reinterpret_cast<uint32*>(GetStructPtr(param));
			uint8* string = &m_ram[stringAddr];
			m_iopBios.GetIoman()->Write(1, static_cast<uint32>(strlen(reinterpret_cast<char*>(string))), string);
		}
		break;
	default:
		CLog::GetInstance().Print(LOG_NAME, "Unknown Deci2Call function (0x%0.8X) called. PC: 0x%0.8X.\r\n", function, m_ee.m_State.nPC);
		break;
	}

}

//7E
void CPS2OS::sc_MachineType()
{
	//Return 0x100 for liberx (is this ok?)
	m_ee.m_State.nGPR[SC_RETURN].nV[0] = 0x100;
	m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
}

//7F
void CPS2OS::sc_GetMemorySize()
{
	m_ee.m_State.nGPR[SC_RETURN].nV[0] = PS2::EE_RAM_SIZE;
	m_ee.m_State.nGPR[SC_RETURN].nV[1] = 0;
}

//////////////////////////////////////////////////
//System Call Handler
//////////////////////////////////////////////////

void CPS2OS::HandleSyscall()
{
	uint32 searchAddress = m_ee.m_State.nCOP0[CCOP_SCU::EPC];
	uint32 callInstruction = m_ee.m_pMemoryMap->GetInstruction(searchAddress);
	if(callInstruction != 0x0000000C)
	{
		//This will happen if an ADDIU R0, R0, $x instruction is encountered. Not sure if there's a use for that on the EE
		CLog::GetInstance().Print(LOG_NAME, "System call exception occured but no SYSCALL instruction found (addr = 0x%0.8X, opcode = 0x%0.8X).\r\n",
			searchAddress, callInstruction);
		m_ee.m_State.nHasException = 0;
		return;
	}

	uint32 func = m_ee.m_State.nGPR[3].nV[0];
	
	if(func == 0x666)
	{
		//Reschedule
		ThreadShakeAndBake();
	}
	else
	{
		if(func & 0x80000000)
		{
			func = 0 - func;
		}
		//Save for custom handler
		m_ee.m_State.nGPR[3].nV[0] = func;

		if(GetCustomSyscallTable()[func] == 0)
		{
	#ifdef _DEBUG
			DisassembleSysCall(static_cast<uint8>(func & 0xFF));
	#endif
			if(func < 0x80)
			{
				((this)->*(m_sysCall[func & 0xFF]))();
			}
		}
		else
		{
			m_ee.GenerateException(0x1FC00100);
		}
	}

	m_ee.m_State.nHasException = 0;
}

void CPS2OS::DisassembleSysCall(uint8 func)
{
#ifdef _DEBUG
	std::string description(GetSysCallDescription(func));
	if(description.length() != 0)
	{
		CLog::GetInstance().Print(LOG_NAME, "%d: %s\r\n", m_currentThreadId.Get(), description.c_str());
	}
#endif
}

std::string CPS2OS::GetSysCallDescription(uint8 function)
{
	char description[256];

	strcpy(description, "");

	switch(function)
	{
	case 0x02:
		sprintf(description, "GsSetCrt(interlace = %i, mode = %i, field = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM2].nV[0]);
		break;
	case 0x04:
		sprintf(description, SYSCALL_NAME_EXIT "();");
		break;
	case 0x06:
		sprintf(description, SYSCALL_NAME_LOADEXECPS2 "(exec = 0x%0.8X, argc = %d, argv = 0x%0.8X);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0],
			m_ee.m_State.nGPR[SC_PARAM1].nV[0],
			m_ee.m_State.nGPR[SC_PARAM2].nV[0]);
		break;
	case 0x07:
		sprintf(description, SYSCALL_NAME_EXECPS2 "(pc = 0x%0.8X, gp = 0x%0.8X, argc = %d, argv = 0x%0.8X);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0],
			m_ee.m_State.nGPR[SC_PARAM0].nV[1],
			m_ee.m_State.nGPR[SC_PARAM0].nV[2],
			m_ee.m_State.nGPR[SC_PARAM0].nV[3]);
		break;
	case 0x10:
		sprintf(description, SYSCALL_NAME_ADDINTCHANDLER "(cause = %i, address = 0x%0.8X, next = 0x%0.8X, arg = 0x%0.8X);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0],
			m_ee.m_State.nGPR[SC_PARAM1].nV[0],
			m_ee.m_State.nGPR[SC_PARAM2].nV[0],
			m_ee.m_State.nGPR[SC_PARAM3].nV[0]);
		break;
	case 0x11:
		sprintf(description, SYSCALL_NAME_REMOVEINTCHANDLER "(cause = %i, id = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x12:
		sprintf(description, SYSCALL_NAME_ADDDMACHANDLER "(channel = %i, address = 0x%0.8X, next = %i, arg = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM2].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM3].nV[0]);
		break;
	case 0x13:
		sprintf(description, SYSCALL_NAME_REMOVEDMACHANDLER "(channel = %i, handler = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x14:
		sprintf(description, SYSCALL_NAME_ENABLEINTC "(cause = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x15:
		sprintf(description, SYSCALL_NAME_DISABLEINTC "(cause = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x16:
		sprintf(description, SYSCALL_NAME_ENABLEDMAC "(channel = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x17:
		sprintf(description, SYSCALL_NAME_DISABLEDMAC "(channel = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x18:
		sprintf(description, SYSCALL_NAME_SETALARM "(time = %d, proc = 0x%0.8X, arg = 0x%0.8X);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], 
			m_ee.m_State.nGPR[SC_PARAM1].nV[0], 
			m_ee.m_State.nGPR[SC_PARAM2].nV[0]);
		break;
	case 0x1A:
		sprintf(description, SYSCALL_NAME_IENABLEINTC "(cause = %d);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x1B:
		sprintf(description, SYSCALL_NAME_IDISABLEINTC "(cause = %d);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x1C:
		sprintf(description, SYSCALL_NAME_IENABLEDMAC "(channel = %d);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x1D:
		sprintf(description, SYSCALL_NAME_IDISABLEDMAC "(channel = %d);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x1F:
		sprintf(description, SYSCALL_NAME_IRELEASEALARM "(id = %d);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x20:
		sprintf(description, SYSCALL_NAME_CREATETHREAD "(thread = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x21:
		sprintf(description, SYSCALL_NAME_DELETETHREAD "(id = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x22:
		sprintf(description, SYSCALL_NAME_STARTTHREAD "(id = 0x%0.8X, a0 = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x23:
		sprintf(description, SYSCALL_NAME_EXITTHREAD "();");
		break;
	case 0x24:
		sprintf(description, SYSCALL_NAME_EXITDELETETHREAD "();");
		break;
	case 0x25:
		sprintf(description, SYSCALL_NAME_TERMINATETHREAD "(id = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x29:
		sprintf(description, SYSCALL_NAME_CHANGETHREADPRIORITY "(id = 0x%0.8X, priority = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x2A:
		sprintf(description, SYSCALL_NAME_ICHANGETHREADPRIORITY "(id = 0x%0.8X, priority = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x2B:
		sprintf(description, SYSCALL_NAME_ROTATETHREADREADYQUEUE "(prio = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x2F:
		sprintf(description, SYSCALL_NAME_GETTHREADID "();");
		break;
	case 0x30:
		sprintf(description, SYSCALL_NAME_REFERTHREADSTATUS "(threadId = %d, infoPtr = 0x%0.8X);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0],
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x31:
		sprintf(description, SYSCALL_NAME_IREFERTHREADSTATUS "(threadId = %d, infoPtr = 0x%0.8X);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0],
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x32:
		sprintf(description, SYSCALL_NAME_SLEEPTHREAD "();");
		break;
	case 0x33:
		sprintf(description, SYSCALL_NAME_WAKEUPTHREAD "(id = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x34:
		sprintf(description, SYSCALL_NAME_IWAKEUPTHREAD "(id = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x35:
		sprintf(description, SYSCALL_NAME_CANCELWAKEUPTHREAD "(id = %d);", 
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x36:
		sprintf(description, SYSCALL_NAME_ICANCELWAKEUPTHREAD "(id = %d);", 
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x37:
		sprintf(description, SYSCALL_NAME_SUSPENDTHREAD "(id = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x38:
		sprintf(description, SYSCALL_NAME_ISUSPENDTHREAD "(id = %d);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x39:
		sprintf(description, SYSCALL_NAME_RESUMETHREAD "(id = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x3C:
		sprintf(description, "SetupThread(gp = 0x%0.8X, stack = 0x%0.8X, stack_size = 0x%0.8X, args = 0x%0.8X, root_func = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM2].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM3].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM4].nV[0]);
		break;
	case 0x3D:
		sprintf(description, "SetupHeap(heap_start = 0x%0.8X, heap_size = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x3E:
		sprintf(description, SYSCALL_NAME_ENDOFHEAP "();");
		break;
	case 0x40:
		sprintf(description, SYSCALL_NAME_CREATESEMA "(sema = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x41:
		sprintf(description, SYSCALL_NAME_DELETESEMA "(semaid = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x42:
		sprintf(description, SYSCALL_NAME_SIGNALSEMA "(semaid = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x43:
		sprintf(description, SYSCALL_NAME_ISIGNALSEMA "(semaid = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x44:
		sprintf(description, SYSCALL_NAME_WAITSEMA "(semaid = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x45:
		sprintf(description, SYSCALL_NAME_POLLSEMA "(semaid = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x46:
		sprintf(description, SYSCALL_NAME_IPOLLSEMA "(semaid = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x47:
		sprintf(description, SYSCALL_NAME_REFERSEMASTATUS "(semaid = %i, status = 0x%0.8X);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0],
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x48:
		sprintf(description, SYSCALL_NAME_IREFERSEMASTATUS "(semaid = %i, status = 0x%0.8X);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0],
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x64:
	case 0x68:
#ifdef _DEBUG
//		sprintf(description, SYSCALL_NAME_FLUSHCACHE "();");
#endif
		break;
	case 0x70:
		sprintf(description, SYSCALL_NAME_GSGETIMR "();");
		break;
	case 0x71:
		sprintf(description, SYSCALL_NAME_GSPUTIMR "(GS_IMR = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x73:
		sprintf(description, SYSCALL_NAME_SETVSYNCFLAG "(ptr1 = 0x%0.8X, ptr2 = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x74:
		sprintf(description, SYSCALL_NAME_SETSYSCALL "(num = 0x%0.2X, address = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x76:
		sprintf(description, SYSCALL_NAME_SIFDMASTAT "();");
		break;
	case 0x77:
		sprintf(description, SYSCALL_NAME_SIFSETDMA "(list = 0x%0.8X, count = %i);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x78:
		sprintf(description, SYSCALL_NAME_SIFSETDCHAIN "();");
		break;
	case 0x79:
		sprintf(description, "SifSetReg(register = 0x%0.8X, value = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0], \
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x7A:
		sprintf(description, "SifGetReg(register = 0x%0.8X);", \
			m_ee.m_State.nGPR[SC_PARAM0].nV[0]);
		break;
	case 0x7C:
		sprintf(description, SYSCALL_NAME_DECI2CALL "(func = 0x%0.8X, param = 0x%0.8X);",
			m_ee.m_State.nGPR[SC_PARAM0].nV[0],
			m_ee.m_State.nGPR[SC_PARAM1].nV[0]);
		break;
	case 0x7E:
		sprintf(description, SYSCALL_NAME_MACHINETYPE "();");
		break;
	case 0x7F:
		sprintf(description, "GetMemorySize();");
		break;
	}

	return std::string(description);
}

//////////////////////////////////////////////////
//System Call Handlers Table
//////////////////////////////////////////////////

CPS2OS::SystemCallHandler CPS2OS::m_sysCall[0x80] =
{
	//0x00
	&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,				&CPS2OS::sc_GsSetCrt,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Exit,				&CPS2OS::sc_Unhandled,			&CPS2OS::sc_LoadExecPS2,		&CPS2OS::sc_ExecPS2,
	//0x08
	&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,
	//0x10
	&CPS2OS::sc_AddIntcHandler,		&CPS2OS::sc_RemoveIntcHandler,		&CPS2OS::sc_AddDmacHandler,			&CPS2OS::sc_RemoveDmacHandler,		&CPS2OS::sc_EnableIntc,			&CPS2OS::sc_DisableIntc,		&CPS2OS::sc_EnableDmac,			&CPS2OS::sc_DisableDmac,
	//0x18
	&CPS2OS::sc_SetAlarm,			&CPS2OS::sc_Unhandled,				&CPS2OS::sc_EnableIntc,				&CPS2OS::sc_DisableIntc,			&CPS2OS::sc_EnableDmac,			&CPS2OS::sc_DisableDmac,		&CPS2OS::sc_Unhandled,			&CPS2OS::sc_ReleaseAlarm,
	//0x20
	&CPS2OS::sc_CreateThread,		&CPS2OS::sc_DeleteThread,			&CPS2OS::sc_StartThread,			&CPS2OS::sc_ExitThread,				&CPS2OS::sc_ExitDeleteThread,	&CPS2OS::sc_TerminateThread,	&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,
	//0x28
	&CPS2OS::sc_Unhandled,			&CPS2OS::sc_ChangeThreadPriority,	&CPS2OS::sc_ChangeThreadPriority,	&CPS2OS::sc_RotateThreadReadyQueue,	&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_GetThreadId,
	//0x30
	&CPS2OS::sc_ReferThreadStatus,	&CPS2OS::sc_ReferThreadStatus,		&CPS2OS::sc_SleepThread,			&CPS2OS::sc_WakeupThread,			&CPS2OS::sc_WakeupThread,		&CPS2OS::sc_CancelWakeupThread,	&CPS2OS::sc_CancelWakeupThread,	&CPS2OS::sc_SuspendThread,
	//0x38
	&CPS2OS::sc_SuspendThread,		&CPS2OS::sc_ResumeThread,			&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_SetupThread,		&CPS2OS::sc_SetupHeap,			&CPS2OS::sc_EndOfHeap,			&CPS2OS::sc_Unhandled,
	//0x40
	&CPS2OS::sc_CreateSema,			&CPS2OS::sc_DeleteSema,				&CPS2OS::sc_SignalSema,				&CPS2OS::sc_SignalSema,				&CPS2OS::sc_WaitSema,			&CPS2OS::sc_PollSema,			&CPS2OS::sc_PollSema,			&CPS2OS::sc_ReferSemaStatus,
	//0x48
	&CPS2OS::sc_ReferSemaStatus,	&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,
	//0x50
	&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,
	//0x58
	&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,
	//0x60
	&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_FlushCache,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,
	//0x68
	&CPS2OS::sc_FlushCache,			&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_Unhandled,
	//0x70
	&CPS2OS::sc_GsGetIMR,			&CPS2OS::sc_GsPutIMR,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_SetVSyncFlag,			&CPS2OS::sc_SetSyscall,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_SifDmaStat,			&CPS2OS::sc_SifSetDma,
	//0x78
	&CPS2OS::sc_SifSetDChain,		&CPS2OS::sc_SifSetReg,				&CPS2OS::sc_SifGetReg,				&CPS2OS::sc_Unhandled,				&CPS2OS::sc_Deci2Call,			&CPS2OS::sc_Unhandled,			&CPS2OS::sc_MachineType,		&CPS2OS::sc_GetMemorySize,
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////
//Debug Stuff

#ifdef DEBUGGER_INCLUDED

BiosDebugModuleInfoArray CPS2OS::GetModulesDebugInfo() const
{
	BiosDebugModuleInfoArray result;

	if(m_elf)
	{
		auto executableRange = GetExecutableRange();

		BIOS_DEBUG_MODULE_INFO module;
		module.name		= m_executableName;
		module.begin	= executableRange.first;
		module.end		= executableRange.second;
		module.param	= m_elf;
		result.push_back(module);
	}

	return result;
}

BiosDebugThreadInfoArray CPS2OS::GetThreadsDebugInfo() const
{
	BiosDebugThreadInfoArray threadInfos;

	for(auto threadIterator = std::begin(m_threads); threadIterator != std::end(m_threads); threadIterator++)
	{
		auto thread = *threadIterator;
		if(!thread) continue;

		auto threadContext = reinterpret_cast<THREADCONTEXT*>(GetStructPtr(thread->contextPtr));

		BIOS_DEBUG_THREAD_INFO threadInfo;
		threadInfo.id			= threadIterator;
		threadInfo.priority		= thread->currPriority;
		if(m_currentThreadId == threadIterator)
		{
			threadInfo.pc = m_ee.m_State.nPC;
			threadInfo.ra = m_ee.m_State.nGPR[CMIPS::RA].nV0;
			threadInfo.sp = m_ee.m_State.nGPR[CMIPS::SP].nV0;
		}
		else
		{
			threadInfo.pc = thread->epc;
			threadInfo.ra = threadContext->gpr[CMIPS::RA].nV0;
			threadInfo.sp = threadContext->gpr[CMIPS::SP].nV0;
		}

		switch(thread->status)
		{
		case THREAD_RUNNING:
			threadInfo.stateDescription = "Running";
			break;
		case THREAD_SLEEPING:
			threadInfo.stateDescription = "Sleeping";
			break;
		case THREAD_WAITING:
			threadInfo.stateDescription = string_format("Waiting (Semaphore: %d)", thread->semaWait);
			break;
		case THREAD_SUSPENDED:
			threadInfo.stateDescription = "Suspended";
			break;
		case THREAD_SUSPENDED_SLEEPING:
			threadInfo.stateDescription = "Suspended+Sleeping";
			break;
		case THREAD_SUSPENDED_WAITING:
			threadInfo.stateDescription = string_format("Suspended+Waiting (Semaphore: %d)", thread->semaWait);
			break;
		case THREAD_ZOMBIE:
			threadInfo.stateDescription = "Zombie";
			break;
		default:
			threadInfo.stateDescription = "Unknown";
			break;
		}

		threadInfos.push_back(threadInfo);
	}

	return threadInfos;
}

#endif
