#include <cmath>
#include <cstring>
#include "GBA.h"
#include "bios.h"
#include "GBAinline.h"
#include "Globals.h"

int32_t sineTable[] =
{
	0x0000, 0x0192, 0x0323, 0x04B5, 0x0645, 0x07D5, 0x0964, 0x0AF1,
	0x0C7C, 0x0E05, 0x0F8C, 0x1111, 0x1294, 0x1413, 0x158F, 0x1708,
	0x187D, 0x19EF, 0x1B5D, 0x1CC6, 0x1E2B, 0x1F8B, 0x20E7, 0x223D,
	0x238E, 0x24DA, 0x261F, 0x275F, 0x2899, 0x29CD, 0x2AFA, 0x2C21,
	0x2D41, 0x2E5A, 0x2F6B, 0x3076, 0x3179, 0x3274, 0x3367, 0x3453,
	0x3536, 0x3612, 0x36E5, 0x37AF, 0x3871, 0x392A, 0x39DA, 0x3A82,
	0x3B20, 0x3BB6, 0x3C42, 0x3CC5, 0x3D3E, 0x3DAE, 0x3E14, 0x3E71,
	0x3EC5, 0x3F0E, 0x3F4E, 0x3F84, 0x3FB1, 0x3FD3, 0x3FEC, 0x3FFB,
	0x4000, 0x3FFB, 0x3FEC, 0x3FD3, 0x3FB1, 0x3F84, 0x3F4E, 0x3F0E,
	0x3EC5, 0x3E71, 0x3E14, 0x3DAE, 0x3D3E, 0x3CC5, 0x3C42, 0x3BB6,
	0x3B20, 0x3A82, 0x39DA, 0x392A, 0x3871, 0x37AF, 0x36E5, 0x3612,
	0x3536, 0x3453, 0x3367, 0x3274, 0x3179, 0x3076, 0x2F6B, 0x2E5A,
	0x2D41, 0x2C21, 0x2AFA, 0x29CD, 0x2899, 0x275F, 0x261F, 0x24DA,
	0x238E, 0x223D, 0x20E7, 0x1F8B, 0x1E2B, 0x1CC6, 0x1B5D, 0x19EF,
	0x187D, 0x1708, 0x158F, 0x1413, 0x1294, 0x1111, 0x0F8C, 0x0E05,
	0x0C7C, 0x0AF1, 0x0964, 0x07D5, 0x0645, 0x04B5, 0x0323, 0x0192,
	0x0000, 0xFE6E, 0xFCDD, 0xFB4B, 0xF9BB, 0xF82B, 0xF69C, 0xF50F,
	0xF384, 0xF1FB, 0xF074, 0xEEEF, 0xED6C, 0xEBED, 0xEA71, 0xE8F8,
	0xE783, 0xE611, 0xE4A3, 0xE33A, 0xE1D5, 0xE075, 0xDF19, 0xDDC3,
	0xDC72, 0xDB26, 0xD9E1, 0xD8A1, 0xD767, 0xD633, 0xD506, 0xD3DF,
	0xD2BF, 0xD1A6, 0xD095, 0xCF8A, 0xCE87, 0xCD8C, 0xCC99, 0xCBAD,
	0xCACA, 0xC9EE, 0xC91B, 0xC851, 0xC78F, 0xC6D6, 0xC626, 0xC57E,
	0xC4E0, 0xC44A, 0xC3BE, 0xC33B, 0xC2C2, 0xC252, 0xC1EC, 0xC18F,
	0xC13B, 0xC0F2, 0xC0B2, 0xC07C, 0xC04F, 0xC02D, 0xC014, 0xC005,
	0xC000, 0xC005, 0xC014, 0xC02D, 0xC04F, 0xC07C, 0xC0B2, 0xC0F2,
	0xC13B, 0xC18F, 0xC1EC, 0xC252, 0xC2C2, 0xC33B, 0xC3BE, 0xC44A,
	0xC4E0, 0xC57E, 0xC626, 0xC6D6, 0xC78F, 0xC851, 0xC91B, 0xC9EE,
	0xCACA, 0xCBAD, 0xCC99, 0xCD8C, 0xCE87, 0xCF8A, 0xD095, 0xD1A6,
	0xD2BF, 0xD3DF, 0xD506, 0xD633, 0xD767, 0xD8A1, 0xD9E1, 0xDB26,
	0xDC72, 0xDDC3, 0xDF19, 0xE075, 0xE1D5, 0xE33A, 0xE4A3, 0xE611,
	0xE783, 0xE8F8, 0xEA71, 0xEBED, 0xED6C, 0xEEEF, 0xF074, 0xF1FB,
	0xF384, 0xF50F, 0xF69C, 0xF82B, 0xF9BB, 0xFB4B, 0xFCDD, 0xFE6E
};

void BIOS_ArcTan()
{
	int32_t a =  -(static_cast<int32_t>(reg[0].I * reg[0].I) >> 14);
	int32_t b = ((0xA9 * a) >> 14) + 0x390;
	b = ((b * a) >> 14) + 0x91C;
	b = ((b * a) >> 14) + 0xFB6;
	b = ((b * a) >> 14) + 0x16AA;
	b = ((b * a) >> 14) + 0x2081;
	b = ((b * a) >> 14) + 0x3651;
	b = ((b * a) >> 14) + 0xA2F9;
	a = (static_cast<int32_t>(reg[0].I) * b) >> 16;
	reg[0].I = a;
}

void BIOS_ArcTan2()
{
	int32_t x = reg[0].I;
	int32_t y = reg[1].I;
	uint32_t res = 0;
	if (!y)
		res = (x >> 16) & 0x8000;
	else
	{
		if (!x)
			res = ((y >> 16) & 0x8000) + 0x4000;
		else
		{
			if (std::abs(x) > std::abs(y) || (std::abs(x) == std::abs(y) && !(x < 0 && y < 0)))
			{
				reg[1].I = x;
				reg[0].I = y << 14;
				BIOS_Div();
				BIOS_ArcTan();
				if (x < 0)
					res = 0x8000 + reg[0].I;
				else
					res = (((y >> 16) & 0x8000) << 1) + reg[0].I;
			}
			else
			{
				reg[0].I = x << 14;
				BIOS_Div();
				BIOS_ArcTan();
				res = (0x4000 + ((y >> 16) & 0x8000)) - reg[0].I;
			}
		}
	}
	reg[0].I = res;
}

void BIOS_BitUnPack()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;
	uint32_t header = reg[2].I;

	int len = CPUReadHalfWord(header);
	// check address
	if (!(source & 0xe000000) || !((source + len) & 0xe000000))
		return;

	int bits = CPUReadByte(header + 2);
	int revbits = 8 - bits;
	//uint32_t value = 0;
	uint32_t base = CPUReadMemory(header + 4);
	bool addBase = !!(base & 0x80000000);
	base &= 0x7fffffff;
	int dataSize = CPUReadByte(header + 3);

	int data = 0;
	int bitwritecount = 0;
	while (1)
	{
		--len;
		if (len < 0)
			break;
		int mask = 0xff >> revbits;
		uint8_t b = CPUReadByte(source);
		++source;
		int bitcount = 0;
		while (1)
		{
			if (bitcount >= 8)
				break;
			uint32_t d = b & mask;
			uint32_t temp = d >> bitcount;
			if (d || addBase)
				temp += base;
			data |= temp << bitwritecount;
			bitwritecount += dataSize;
			if (bitwritecount >= 32)
			{
				CPUWriteMemory(dest, data);
				dest += 4;
				data = 0;
				bitwritecount = 0;
			}
			mask <<= bits;
			bitcount += bits;
		}
	}
}

void BIOS_GetBiosChecksum()
{
	reg[0].I = 0xBAAE187F;
}

void BIOS_BgAffineSet()
{
	uint32_t src = reg[0].I;
	uint32_t dest = reg[1].I;
	int num = reg[2].I;

	for (int i = 0; i < num; ++i)
	{
		int32_t cx = CPUReadMemory(src);
		src += 4;
		int32_t cy = CPUReadMemory(src);
		src += 4;
		int16_t dispx = CPUReadHalfWord(src);
		src += 2;
		int16_t dispy = CPUReadHalfWord(src);
		src += 2;
		int16_t rx = CPUReadHalfWord(src);
		src += 2;
		int16_t ry = CPUReadHalfWord(src);
		src += 2;
		uint16_t theta = CPUReadHalfWord(src) >> 8;
		src += 4; // keep structure alignment
		int32_t a = sineTable[(theta + 0x40) & 255];
		int32_t b = sineTable[theta];

		int16_t dx = (rx * a) >> 14;
		int16_t dmx = (rx * b) >> 14;
		int16_t dy = (ry * b) >> 14;
		int16_t dmy = (ry * a) >> 14;

		CPUWriteHalfWord(dest, dx);
		dest += 2;
		CPUWriteHalfWord(dest, -dmx);
		dest += 2;
		CPUWriteHalfWord(dest, dy);
		dest += 2;
		CPUWriteHalfWord(dest, dmy);
		dest += 2;

		int32_t startx = cx - dx * dispx + dmx * dispy;
		int32_t starty = cy - dy * dispx - dmy * dispy;

		CPUWriteMemory(dest, startx);
		dest += 4;
		CPUWriteMemory(dest, starty);
		dest += 4;
	}
}

void BIOS_CpuSet()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;
	uint32_t cnt = reg[2].I;

	if (!(source & 0xe000000) || !((source + (((cnt << 11) >> 9) & 0x1fffff)) & 0xe000000))
		return;

	int count = cnt & 0x1FFFFF;

	// 32-bit ?
	if ((cnt >> 26) & 1)
	{
		// needed for 32-bit mode!
		source &= 0xFFFFFFFC;
		dest &= 0xFFFFFFFC;
		// fill ?
		if ((cnt >> 24) & 1)
		{
			uint32_t value = source > 0x0EFFFFFF ? 0x1CAD1CAD : CPUReadMemory(source);
			while (count)
			{
				CPUWriteMemory(dest, value);
				dest += 4;
				--count;
			}
		}
		else
		{
			// copy
			while (count)
			{
				CPUWriteMemory(dest, source > 0x0EFFFFFF ? 0x1CAD1CAD : CPUReadMemory(source));
				source += 4;
				dest += 4;
				--count;
			}
		}
	}
	else
	{
		// 16-bit fill?
		if ((cnt >> 24) & 1)
		{
			uint16_t value = source > 0x0EFFFFFF ? 0x1CAD : CPUReadHalfWord(source);
			while (count)
			{
				CPUWriteHalfWord(dest, value);
				dest += 2;
				--count;
			}
		}
		else
		{
			// copy
			while (count)
			{
				CPUWriteHalfWord(dest, source > 0x0EFFFFFF ? 0x1CAD : CPUReadHalfWord(source));
				source += 2;
				dest += 2;
				--count;
			}
		}
	}
}

void BIOS_CpuFastSet()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;
	uint32_t cnt = reg[2].I;

	if (!(source & 0xe000000) || !((source + (((cnt << 11) >> 9) & 0x1fffff)) & 0xe000000))
		return;

	// needed for 32-bit mode!
	source &= 0xFFFFFFFC;
	dest &= 0xFFFFFFFC;

	int count = cnt & 0x1FFFFF;

	// fill?
	if ((cnt >> 24) & 1)
	{
		while (count > 0)
		{
			// BIOS always transfers 32 bytes at a time
			uint32_t value = source > 0x0EFFFFFF ? 0xBAFFFFFB : CPUReadMemory(source);
			for (int i = 0; i < 8; ++i)
			{
				CPUWriteMemory(dest, value);
				dest += 4;
			}
			count -= 8;
		}
	}
	else
	{
		// copy
		while (count > 0)
		{
			// BIOS always transfers 32 bytes at a time
			for (int i = 0; i < 8; ++i)
			{
				CPUWriteMemory(dest, source > 0x0EFFFFFF ? 0xBAFFFFFB :CPUReadMemory(source));
				source += 4;
				dest += 4;
			}
			count -= 8;
		}
	}
}

void BIOS_Diff8bitUnFilterWram()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;

	uint32_t header = CPUReadMemory(source);
	source += 4;

	if (!(source & 0xe000000) || !((source + ((header >> 8) & 0x1fffff)) & 0xe000000))
		return;

	int len = header >> 8;

	uint8_t data = CPUReadByte(source++);
	CPUWriteByte(dest++, data);
	--len;

	while (len > 0)
	{
		uint8_t diff = CPUReadByte(source++);
		data += diff;
		CPUWriteByte(dest++, data);
		--len;
	}
}

void BIOS_Diff8bitUnFilterVram()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;

	uint32_t header = CPUReadMemory(source);
	source += 4;

	if (!(source & 0xe000000) || !((source + ((header >> 8) & 0x1fffff)) & 0xe000000))
		return;

	int len = header >> 8;

	uint8_t data = CPUReadByte(source++);
	uint16_t writeData = data;
	int shift = 8;
	int bytes = 1;

	while (len >= 2)
	{
		uint8_t diff = CPUReadByte(source++);
		data += diff;
		writeData |= data << shift;
		++bytes;
		shift += 8;
		if (bytes == 2)
		{
			CPUWriteHalfWord(dest, writeData);
			dest += 2;
			len -= 2;
			bytes = 0;
			writeData = 0;
			shift = 0;
		}
	}
}

void BIOS_Diff16bitUnFilter()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;

	uint32_t header = CPUReadMemory(source);
	source += 4;

	if (!(source & 0xe000000) || !((source + ((header >> 8) & 0x1fffff)) & 0xe000000))
		return;

	int len = header >> 8;

	uint16_t data = CPUReadHalfWord(source);
	source += 2;
	CPUWriteHalfWord(dest, data);
	dest += 2;
	len -= 2;

	while (len >= 2)
	{
		uint16_t diff = CPUReadHalfWord(source);
		source += 2;
		data += diff;
		CPUWriteHalfWord(dest, data);
		dest += 2;
		len -= 2;
	}
}

void BIOS_Div()
{
	int number = reg[0].I;
	int denom = reg[1].I;

	if (denom)
	{
		reg[0].I = number / denom;
		reg[1].I = number % denom;
		int32_t temp = static_cast<int32_t>(reg[0].I);
		reg[3].I = static_cast<uint32_t>(temp < 0 ? -temp : temp);
	}
}

void BIOS_HuffUnComp()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;

	uint32_t header = CPUReadMemory(source);
	source += 4;

	if (!(source & 0xe000000) || !((source + ((header >> 8) & 0x1fffff)) & 0xe000000))
		return;

	uint8_t treeSize = CPUReadByte(source++);

	uint32_t treeStart = source;

	source += ((treeSize + 1) << 1) - 1; // minus because we already skipped one byte

	int len = header >> 8;

	uint32_t mask = 0x80000000;
	uint32_t data = CPUReadMemory(source);
	source += 4;

	int pos = 0;
	uint8_t rootNode = CPUReadByte(treeStart);
	uint8_t currentNode = rootNode;
	bool writeData = false;
	int byteShift = 0;
	int byteCount = 0;
	uint32_t writeValue = 0;

	if ((header & 0x0F) == 8)
	{
		while (len > 0)
		{
			// take left
			if (!pos)
				++pos;
			else
				pos += ((currentNode & 0x3F) + 1) << 1;

			if (data & mask)
			{
				// right
				if (currentNode & 0x40)
					writeData = true;
				currentNode = CPUReadByte(treeStart + pos + 1);
			}
			else
			{
				// left
				if (currentNode & 0x80)
					writeData = true;
				currentNode = CPUReadByte(treeStart + pos);
			}

			if (writeData)
			{
				writeValue |= currentNode << byteShift;
				++byteCount;
				byteShift += 8;

				pos = 0;
				currentNode = rootNode;
				writeData = false;

				if (byteCount == 4)
				{
					byteCount = 0;
					byteShift = 0;
					CPUWriteMemory(dest, writeValue);
					writeValue = 0;
					dest += 4;
					len -= 4;
				}
			}
			mask >>= 1;
			if (!mask)
			{
				mask = 0x80000000;
				data = CPUReadMemory(source);
				source += 4;
			}
		}
	}
	else
	{
		int halfLen = 0;
		int value = 0;
		while (len > 0)
		{
			// take left
			if (!pos)
				++pos;
			else
				pos += ((currentNode & 0x3F) + 1) << 1;

			if (data & mask)
			{
				// right
				if (currentNode & 0x40)
					writeData = true;
				currentNode = CPUReadByte(treeStart + pos + 1);
			}
			else
			{
				// left
				if (currentNode & 0x80)
					writeData = true;
				currentNode = CPUReadByte(treeStart + pos);
			}

			if (writeData)
			{
				if (!halfLen)
					value |= currentNode;
				else
					value |= currentNode << 4;

				halfLen += 4;
				if (halfLen == 8)
				{
					writeValue |= value << byteShift;
					++byteCount;
					byteShift += 8;

					halfLen = 0;
					value = 0;

					if (byteCount == 4)
					{
						byteCount = 0;
						byteShift = 0;
						CPUWriteMemory(dest, writeValue);
						dest += 4;
						writeValue = 0;
						len -= 4;
					}
				}
				pos = 0;
				currentNode = rootNode;
				writeData = false;
			}
			mask >>= 1;
			if (!mask)
			{
				mask = 0x80000000;
				data = CPUReadMemory(source);
				source += 4;
			}
		}
	}
}

void BIOS_LZ77UnCompVram()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;

	uint32_t header = CPUReadMemory(source);
	source += 4;

	if (!(source & 0xe000000) || !((source + ((header >> 8) & 0x1fffff)) & 0xe000000))
		return;

	int byteCount = 0;
	int byteShift = 0;
	uint32_t writeValue = 0;

	int len = header >> 8;

	while (len > 0)
	{
		uint8_t d = CPUReadByte(source++);

		if (d)
		{
			for (int i = 0; i < 8; ++i)
			{
				if (d & 0x80)
				{
					uint16_t data = CPUReadByte(source++) << 8;
					data |= CPUReadByte(source++);
					int length = (data >> 12) + 3;
					int offset = data & 0x0FFF;
					uint32_t windowOffset = dest + byteCount - offset - 1;
					for (int i2 = 0; i2 < length; ++i2)
					{
						writeValue |= CPUReadByte(windowOffset++) << byteShift;
						byteShift += 8;
						++byteCount;

						if (byteCount == 2)
						{
							CPUWriteHalfWord(dest, writeValue);
							dest += 2;
							byteCount = 0;
							byteShift = 0;
							writeValue = 0;
						}
						--len;
						if (!len)
							return;
					}
				}
				else
				{
					writeValue |= CPUReadByte(source++) << byteShift;
					byteShift += 8;
					++byteCount;
					if (byteCount == 2)
					{
						CPUWriteHalfWord(dest, writeValue);
						dest += 2;
						byteCount = 0;
						byteShift = 0;
						writeValue = 0;
					}
					--len;
					if (!len)
						return;
				}
				d <<= 1;
			}
		}
		else
		{
			for (int i = 0; i < 8; ++i)
			{
				writeValue |= CPUReadByte(source++) << byteShift;
				byteShift += 8;
				++byteCount;
				if (byteCount == 2)
				{
					CPUWriteHalfWord(dest, writeValue);
					dest += 2;
					byteShift = 0;
					byteCount = 0;
					writeValue = 0;
				}
				--len;
				if (!len)
					return;
			}
		}
	}
}

void BIOS_LZ77UnCompWram()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;

	uint32_t header = CPUReadMemory(source);
	source += 4;

	if (!(source & 0xe000000) || !((source + ((header >> 8) & 0x1fffff)) & 0xe000000))
		return;

	int len = header >> 8;

	while (len > 0)
	{
		uint8_t d = CPUReadByte(source++);

		if (d)
		{
			for (int i = 0; i < 8; ++i)
			{
				if (d & 0x80)
				{
					uint16_t data = CPUReadByte(source++) << 8;
					data |= CPUReadByte(source++);
					int length = (data >> 12) + 3;
					int offset = data & 0x0FFF;
					uint32_t windowOffset = dest - offset - 1;
					for (int i2 = 0; i2 < length; ++i2)
					{
						CPUWriteByte(dest++, CPUReadByte(windowOffset++));
						--len;
						if (!len)
							return;
					}
				}
				else
				{
					CPUWriteByte(dest++, CPUReadByte(source++));
					--len;
					if (!len)
						return;
				}
				d <<= 1;
			}
		}
		else
		{
			for (int i = 0; i < 8; ++i)
			{
				CPUWriteByte(dest++, CPUReadByte(source++));
				--len;
				if (!len)
					return;
			}
		}
	}
}

void BIOS_ObjAffineSet()
{
	uint32_t src = reg[0].I;
	uint32_t dest = reg[1].I;
	int num = reg[2].I;
	int offset = reg[3].I;

	for (int i = 0; i < num; ++i)
	{
		int16_t rx = CPUReadHalfWord(src);
		src += 2;
		int16_t ry = CPUReadHalfWord(src);
		src += 2;
		uint16_t theta = CPUReadHalfWord(src) >> 8;
		src += 4; // keep structure alignment

		int32_t a = sineTable[(theta + 0x40) & 255];
		int32_t b = sineTable[theta];

		int16_t dx = (static_cast<int32_t>(rx) * a) >> 14;
		int16_t dmx = (static_cast<int32_t>(rx) * b) >> 14;
		int16_t dy = (static_cast<int32_t>(ry) * b) >> 14;
		int16_t dmy = (static_cast<int32_t>(ry) * a) >> 14;

		CPUWriteHalfWord(dest, dx);
		dest += offset;
		CPUWriteHalfWord(dest, -dmx);
		dest += offset;
		CPUWriteHalfWord(dest, dy);
		dest += offset;
		CPUWriteHalfWord(dest, dmy);
		dest += offset;
	}
}

void BIOS_RegisterRamReset(uint32_t flags)
{
	// no need to trace here. this is only called directly from GBA.cpp
	// to emulate bios initialization

	CPUUpdateRegister(0x0, 0x80);

	if (flags)
	{
		if (flags & 0x01)
			// clear work RAM
			memset(&workRAM[0], 0, 0x40000);
		if (flags & 0x02)
			// clear internal RAM
			memset(&internalRAM[0], 0, 0x7e00); // don't clear 0x7e00-0x7fff
		if (flags & 0x04)
			// clear palette RAM
			memset(&paletteRAM[0], 0, 0x400);
		if (flags & 0x08)
			// clear VRAM
			memset(&vram[0], 0, 0x18000);
		if (flags & 0x10)
			// clean OAM
			memset(&oam[0], 0, 0x400);

		if (flags & 0x80)
		{
			int i;
			for (i = 0; i < 0x10; ++i)
				CPUUpdateRegister(0x200 + i * 2, 0);

			for (i = 0; i < 0xF; ++i)
				CPUUpdateRegister(0x4 + i * 2, 0);

			for (i = 0; i < 0x20; ++i)
				CPUUpdateRegister(0x20 + i * 2, 0);

			for (i = 0; i < 0x18; ++i)
				CPUUpdateRegister(0xb0 + i * 2, 0);

			CPUUpdateRegister(0x130, 0);
			CPUUpdateRegister(0x20, 0x100);
			CPUUpdateRegister(0x30, 0x100);
			CPUUpdateRegister(0x26, 0x100);
			CPUUpdateRegister(0x36, 0x100);
		}

		if (flags & 0x20)
		{
			int i;
			for (i = 0; i < 8; ++i)
				CPUUpdateRegister(0x110 + i * 2, 0);
			CPUUpdateRegister(0x134, 0x8000);
			for (i = 0; i < 7; ++i)
				CPUUpdateRegister(0x140 + i * 2, 0);
		}

		if (flags & 0x40)
		{
			CPUWriteByte(0x4000084, 0);
			CPUWriteByte(0x4000084, 0x80);
			CPUWriteMemory(0x4000080, 0x880e0000);
			CPUUpdateRegister(0x88, CPUReadHalfWord(0x4000088) & 0x3ff);
			CPUWriteByte(0x4000070, 0x70);
			int i;
			for (i = 0; i < 8; ++i)
				CPUUpdateRegister(0x90 + i * 2, 0);
			CPUWriteByte(0x4000070, 0);
			for (i = 0; i < 8; ++i)
				CPUUpdateRegister(0x90 + i * 2, 0);
			CPUWriteByte(0x4000084, 0);
		}
	}
}

void BIOS_RegisterRamReset()
{
	BIOS_RegisterRamReset(reg[0].I);
}

void BIOS_RLUnCompVram()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;

	uint32_t header = CPUReadMemory(source & 0xFFFFFFFC);
	source += 4;

	if (!(source & 0xe000000) || !((source + ((header >> 8) & 0x1fffff)) & 0xe000000))
		return;

	int len = header >> 8;
	int byteCount = 0;
	int byteShift = 0;
	uint32_t writeValue = 0;

	while (len > 0)
	{
		uint8_t d = CPUReadByte(source++);
		int l = d & 0x7F;
		if (d & 0x80)
		{
			uint8_t data = CPUReadByte(source++);
			l += 3;
			for (int i = 0;i < l; ++i)
			{
				writeValue |= data << byteShift;
				byteShift += 8;
				++byteCount;

				if (byteCount == 2)
				{
					CPUWriteHalfWord(dest, writeValue);
					dest += 2;
					byteCount = 0;
					byteShift = 0;
					writeValue = 0;
				}
				--len;
				if (!len)
					return;
			}
		}
		else
		{
			++l;
			for (int i = 0; i < l; ++i)
			{
				writeValue |= CPUReadByte(source++) << byteShift;
				byteShift += 8;
				++byteCount;
				if (byteCount == 2)
				{
					CPUWriteHalfWord(dest, writeValue);
					dest += 2;
					byteCount = 0;
					byteShift = 0;
					writeValue = 0;
				}
				--len;
				if (!len)
					return;
			}
		}
	}
}

void BIOS_RLUnCompWram()
{
	uint32_t source = reg[0].I;
	uint32_t dest = reg[1].I;

	uint32_t header = CPUReadMemory(source & 0xFFFFFFFC);
	source += 4;

	if (!(source & 0xe000000) || !((source + ((header >> 8) & 0x1fffff)) & 0xe000000))
		return;

	int len = header >> 8;

	while (len > 0)
	{
		uint8_t d = CPUReadByte(source++);
		int l = d & 0x7F;
		if (d & 0x80)
		{
			uint8_t data = CPUReadByte(source++);
			l += 3;
			for (int i = 0; i < l; ++i)
			{
				CPUWriteByte(dest++, data);
				--len;
				if (!len)
					return;
			}
		}
		else
		{
			++l;
			for (int i = 0; i < l; ++i)
			{
				CPUWriteByte(dest++, CPUReadByte(source++));
				--len;
				if (!len)
					return;
			}
		}
	}
}

void BIOS_SoftReset()
{
	armState = true;
	armMode = 0x1F;
	armIrqEnable = false;
	C_FLAG = V_FLAG = N_FLAG = Z_FLAG = false;
	reg[13].I = 0x03007F00;
	reg[14].I = 0x00000000;
	reg[16].I = 0x00000000;
	reg[R13_IRQ].I = 0x03007FA0;
	reg[R14_IRQ].I = 0x00000000;
	reg[SPSR_IRQ].I = 0x00000000;
	reg[R13_SVC].I = 0x03007FE0;
	reg[R14_SVC].I = 0x00000000;
	reg[SPSR_SVC].I = 0x00000000;
	uint8_t b = internalRAM[0x7ffa];

	memset(&internalRAM[0x7e00], 0, 0x200);

	if (b)
	{
		armNextPC = 0x02000000;
		reg[15].I = 0x02000004;
	}
	else
	{
		armNextPC = 0x08000000;
		reg[15].I = 0x08000004;
	}
}

void BIOS_Sqrt()
{
	reg[0].I = static_cast<uint32_t>(std::sqrt(static_cast<double>(reg[0].I)));
}

void BIOS_MidiKey2Freq()
{
	int freq = CPUReadMemory(reg[0].I + 4);
	double tmp = (180 - reg[1].I) - (reg[2].I / 256.0);
	tmp = std::pow(2.0, tmp / 12.0);
	reg[0].I = static_cast<int>(freq / tmp);
}

void BIOS_SndDriverJmpTableCopy()
{
	for (int i = 0; i < 0x24; ++i)
	{
		CPUWriteMemory(reg[0].I, 0x9c);
		reg[0].I += 4;
	}
}