// Meteor - A Nintendo Gameboy Advance emulator
// Copyright (C) 2009-2011 Philippe Daouadi
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include "ameteor/bios.hpp"
#include "ameteor/cpu.hpp"
#include "ameteor/memory.hpp"

#include "globals.hpp"

#include "debug.hpp"

#include <cmath>

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

		void Bios000h ()
		{
			debug("Bios entry point");
			R(13) = 0x03007FE0;
			R(15) = 0x08000004;
			CPU.SwitchToMode(Cpu::M_IRQ);
			R(13) = 0x03007FA0;
			CPU.SwitchToMode(Cpu::M_SYS);
			R(13) = 0x03007F00;
			ICPSR.irq_d = false;
			IO.Write8(Io::POSTFLG, 0x01);
		}

		void Bios008h ()
		{
			// if we are here, we should be in SVC mode (0x13)
			// store the spsr, r11, r12 and r14 on the stack
			uint32_t baseadd = R(13) - (4*4), add = (baseadd & 0xFFFFFFFC);
			MEM.Write32(add     , SPSR);
			MEM.Write32(add += 4, R(11));
			MEM.Write32(add += 4, R(12));
			MEM.Write32(add +  4, R(14));
			R(13) = baseadd;

			uint8_t swiComment = MEM.Read8(R(14) - 2);

			// put 0x1F in cpsr but don't touch to the irq disable bit
			CPU.SwitchToMode(0x1F);
			CPSR = 0x0000001F | (CPSR & (0x1 << 7));
			CPU.UpdateICpsr();

			// store r11 and r14 (of the user mode) on the stack
			baseadd = R(13) - (2*4); add = (baseadd & 0xFFFFFFFC);
			MEM.Write32(add     , R(11));
			MEM.Write32(add +  4, R(14));
			R(13) = baseadd;

			R(14) = 0x168;

			debug("Software IRQ start");
			switch (swiComment)
			{
				case 0x04:
					IntrWait();
					break;
				case 0x05:
					VBlankIntrWait();
					break;
				default:
					met_abort("not implemented : " << (int)swiComment);
					break;
			}
		}

		void Bios168h ()
		{
			uint32_t add = R(13) & 0xFFFFFFFC;
			R( 2) = MEM.Read32(add     );
			R(14) = MEM.Read32(add += 4);
			R(13) += 2*4;

			// SVC with fiq and irq disabled
			CPU.SwitchToMode(0x13); // SVC
			CPSR = 0x000000D3;
			CPU.UpdateICpsr();
			add = R(13) & 0xFFFFFFFC;

			SPSR = MEM.Read32(add);

			R(11) = MEM.Read32(add += 4);
			R(12) = MEM.Read32(add += 4);
			R(14) = MEM.Read32(add +  4);
			R(13) += 4*4;

			// FIXME this works (for thumb) ?
			if (CPU.Spsr().b.thumb)
				R(15) = R(14) + 2;
			else
				R(15) = R(14) + 4;

			debug("Software IRQ end");
			CPU.SwitchModeBack();
		}

		void Bios018h ()
		{
			debug("IRQ start");
			// stmfd r13!,r0-r3,r12,r14
			uint32_t baseadd = R(13) - (6*4), add = (baseadd & 0xFFFFFFFC);
			MEM.Write32(add     , R( 0));
			MEM.Write32(add += 4, R( 1));
			MEM.Write32(add += 4, R( 2));
			MEM.Write32(add += 4, R( 3));
			MEM.Write32(add += 4, R(12));
			MEM.Write32(add +  4, R(14));

			R(13) = baseadd;

			// add r14,r15,0h
			R(14) = 0x00000130;

			R(15) = MEM.Read32(0x03007FFC) + 4;
		}

		void Bios130h ()
		{
			debug("IRQ end");
			// ldmfd r13!,r0-r3,r12,r14
			uint32_t add = R(13) & 0xFFFFFFFC;
			R( 0) = MEM.Read32(add     );
			R( 1) = MEM.Read32(add += 4);
			R( 2) = MEM.Read32(add += 4);
			R( 3) = MEM.Read32(add += 4);
			R(12) = MEM.Read32(add += 4);
			R(14) = MEM.Read32(add +  4);

			R(13) += 6*4;

			// subs r15,r14,4h
			R(15) = R(14);
			if (CPU.Spsr().b.thumb)
				R(15) -= 2;

			CPU.SwitchModeBack();

			// XXX FIXME, usefull ? test on breath of fire !
			/*if (FLAG_T)
				R(15) &= 0xFFFFFFFE;
			else
				R(15) &= 0xFFFFFFFC;*/
		}

		void SoftReset ()
		{
			CPU.SoftReset ();
			if (MEM.Read8(0x03007FFA))
				R(15) = 0x02000004;
			else
				R(15) = 0x08000004;

			MEM.SoftReset ();
		}

		void RegisterRamReset ()
		{
			IO.Write16(Io::DISPCNT, 0x0080);
			uint8_t flagRes = R(0);
			if (flagRes & (0x1     ))
				MEM.ClearWbram();
			if (flagRes & (0x1 << 1))
				MEM.ClearWcram();
			if (flagRes & (0x1 << 2))
				MEM.ClearPalette();
			if (flagRes & (0x1 << 3))
				MEM.ClearVram();
			if (flagRes & (0x1 << 4))
				MEM.ClearOam();
			if (flagRes & (0x1 << 5))
				IO.ClearSio ();
			if (flagRes & (0x1 << 6))
				IO.ClearSound ();
			if (flagRes & (0x1 << 7))
				IO.ClearOthers ();
		}

		void Halt ()
		{
			IO.Write8(Io::HALTCNT, 0);
		}

		void IntrWait ()
		{
			// FIXME ugly
			R(13) -= 8;
			MEM.Write32(R(13) & 0xFFFFFFFC, R(4));
			MEM.Write32((R(13)+4) & 0xFFFFFFFC, R(14));

			uint16_t& intFlags = *(uint16_t*)MEM.GetRealAddress(0x03007FF8);

			if (R(0))
			{
				if (intFlags & R(1))
					intFlags = (intFlags & R(1)) ^ intFlags;
				else
					FLAG_Z = 1;
				IO.Write16(Io::IME, 1);
			}

			IO.Write8(Io::HALTCNT, 0);

			// return address (after IRQ)
			R(15) = 0x33C;

			debug("IntrWait start");
		}

		void Bios338h ()
		{
			uint16_t& intFlags = *(uint16_t*)MEM.GetRealAddress(0x03007FF8);

			if (!(intFlags & R(1)))
			{
				IO.Write16(Io::IME, 1);
				IO.Write8(Io::HALTCNT, 0);
			}
			else
			{
				intFlags = (intFlags & R(1)) ^ intFlags;
				IO.Write16(Io::IME, 1);

				// FIXME ugly
				R(4) = MEM.Read32(R(13) & 0xFFFFFFFC);
				R(14) = MEM.Read32((R(13)+4) & 0xFFFFFFFC);
				R(13) += 8;

				// should lead to 0x168
				R(15) = R(14)+4;
			}

			debug("IntWait end");
		}

		void VBlankIntrWait ()
		{
			R(0) = 1;
			R(1) = 1;
			IntrWait();
		}

		void Div ()
		{
			if (!R(1))
				met_abort("Div by 0");

			int32_t number = R(0), denom = R(1);

			int32_t div = number / denom;
			R(0) = div;
			R(1) = number % denom;
			R(3) = div < 0 ? -div : div;
		}

		void DivArm ()
		{
			uint32_t tmp = R(0);
			R(0) = R(1);
			R(1) = tmp;
			Div();
		}

		void Sqrt ()
		{
			R(0) = (uint16_t)sqrt((float)R(0));
		}

		void ArcTan ()
		{
			int32_t a = -(((int32_t)R(0) * R(0)) >> 14);
			int32_t b = 0xA9;
			b = ((a * b) >> 14) + 0x0390;
			b = ((a * b) >> 14) + 0x091C;
			b = ((a * b) >> 14) + 0x0FB6;
			b = ((a * b) >> 14) + 0X16AA;
			b = ((a * b) >> 14) + 0X2081;
			b = ((a * b) >> 14) + 0X3651;
			b = ((a * b) >> 14) + 0XA2F9;

			R(0) = (R(0) * b) >> 16;
		}

		void ArcTan2 ()
		{
			int16_t x = R(0), y = R(1);
			if (y)
				if (x)
					if (abs(x) < abs(y))
					{
						R(0) <<= 14;
						Div();
						ArcTan();
						R(0) = 0x4000 - R(0);
						if (y < 0)
							R(0) += 0x8000;
					}
					else
					{
						uint32_t r1 = R(1);
						R(1) = R(0);
						R(0) = r1 << 14;
						Div();
						ArcTan();
						if (x < 0)
							R(0) += 0x8000;
						else if (y < 0)
							R(0) += 0x10000;
					}
				else
					if (y < 0)
						R(0) = 0xc000;
					else
						R(0) = 0x4000;
			else
				if (x < 0)
					R(0) = 0x8000;
				else
					R(0) = 0x0000;
		}

		void CpuSet ()
		{
			if (R(2) & (0x1 << 26)) // 32 bits
			{
				if (R(2) & (0x1 << 24)) // fixed source address
				{
					uint32_t source = MEM.Read32(R(0) & 0xFFFFFFFC);
					uint32_t address = R(1) & 0xFFFFFFFC;
					for (uint32_t count = (R(2) & 0x001FFFFF); count; --count)
					{
						MEM.Write32(address, source);
						address += 4;
					}
				}
				else // copy
				{
					uint32_t src = R(0) & 0xFFFFFFFC;
					uint32_t dest = R(1) & 0xFFFFFFFC;
					for (uint32_t count = (R(2) & 0x001FFFFF); count; --count)
					{
						MEM.Write32(dest, MEM.Read32(src));
						src += 4;
						dest += 4;
					}
				}
			}
			else // 16 bits
			{
				if (R(2) & (0x1 << 24)) // fixed source address
				{
					uint16_t source = MEM.Read16(R(0));
					uint32_t address = R(1);
					for (uint32_t count = (R(2) & 0x001FFFFF); count; --count)
					{
						MEM.Write16(address, source);
						address += 2;
					}
				}
				else // copy
				{
					uint32_t src = R(0);
					uint32_t dest = R(1);
					for (uint32_t count = (R(2) & 0x001FFFFF); count; --count)
					{
						MEM.Write16(dest, MEM.Read16(src));
						src += 2;
						dest += 2;
					}
				}
			}
		}

		void CpuFastSet ()
		{
			if (R(2) & (0x1 << 24)) // fixed source address
			{
				uint32_t source = MEM.Read32(R(0));
				uint32_t address = R(1);
				for (uint32_t count = (R(2) & 0x001FFFFF); count; --count)
				{
					MEM.Write32(address, source);
					address += 4;
				}
			}
			else // copy
			{
				uint32_t src = R(0);
				uint32_t dest = R(1);
				for (uint32_t count = (R(2) & 0x001FFFFF); count; --count)
				{
					MEM.Write32(dest, MEM.Read32(src));
					src += 4;
					dest += 4;
				}
			}
		}

		void BgAffineSet ()
		{
			uint32_t src = R(0);
			uint32_t dest = R(1);
			uint32_t num = R(2);

			int32_t cx, cy;
			int16_t dix, diy, rx, ry;
			uint16_t alpha;

			int32_t cos, sin;
			int16_t dx, dmx, dy, dmy;

			while (num--)
			{
				cx = MEM.Read32(src);
				src += 4;
				cy = MEM.Read32(src);
				src += 4;
				dix = MEM.Read16(src);
				src += 2;
				diy = MEM.Read16(src);
				src += 2;
				rx = MEM.Read16(src);
				src += 2;
				ry = MEM.Read16(src);
				src += 2;
				alpha = MEM.Read16(src) >> 8;
				src += 2;

				sin = sineTable[alpha];
				cos = sineTable[(alpha + 0x40) & 0xFF];

				dx = (rx * cos) >> 14;
				dmx = -((rx * sin) >> 14);
				dy = (ry * sin) >> 14;
				dmy = (ry * cos) >> 14;

				MEM.Write16(dest, dx);
				dest += 2;
				MEM.Write16(dest, dmx);
				dest += 2;
				MEM.Write16(dest, dy);
				dest += 2;
				MEM.Write16(dest, dmy);
				dest += 2;

				MEM.Write32(dest, cx - dx * dix - dmx * diy);
				dest += 4;
				MEM.Write32(dest, cy - dy * dix - dmy * diy);
				dest += 4;
			}
		}

		void ObjAffineSet ()
		{
			uint32_t src = R(0);
			uint32_t dest = R(1);
			uint32_t num = R(2);
			uint32_t off = R(3);

			int16_t rx, ry;
			uint16_t alpha;

			int32_t cos, sin;
			int16_t dx, dmx, dy, dmy;

			while (num--)
			{
				rx = MEM.Read16(src);
				src += 2;
				ry = MEM.Read16(src);
				src += 2;
				alpha = MEM.Read16(src) >> 8;
				src += 4;

				sin = sineTable[alpha];
				cos = sineTable[(alpha + 0x40) & 0xFF];

				dx = (rx * cos) >> 14;
				dmx = -((rx * sin) >> 14);
				dy = (ry * sin) >> 14;
				dmy = (ry * cos) >> 14;

				MEM.Write16(dest, dx);
				dest += off;
				MEM.Write16(dest, dmx);
				dest += off;
				MEM.Write16(dest, dy);
				dest += off;
				MEM.Write16(dest, dmy);
				dest += off;
			}
		}

		void LZ77UnCompWram ()
		{
			uint32_t src = R(0);
			uint32_t header = MEM.Read32(src);
			src += 4;
			if (((header >> 4) & 0xF) != 1)
				met_abort("This is not LZ77 data");
			uint32_t size = header >> 8;
			debug("LZ77UnCompWram from " << IOS_ADD << R(0) << " to " << IOS_ADD << R(1) << ", len : " << size);
			uint32_t dest = R(1);
			uint8_t flags;
			uint16_t block;
			uint8_t blocklen;
			uint32_t realaddr;

			// for each block of a flags byte + 8 blocks
			while (true)
			{
				flags = MEM.Read8(src++);

				for (uint8_t i = 0; i < 8; ++i)
				{
					// compressed block of 2 bytes
					if (flags & 0x80)
					{
						block = MEM.Read8(src) << 8 | MEM.Read8(src+1);
						src += 2;
						blocklen = (block >> 12) + 3;
						realaddr = dest - (block & 0x0FFF) - 1;
						for(uint16_t j = 0; j < blocklen; ++j)
						{
							MEM.Write8(dest++, MEM.Read8(realaddr++));

							--size;
							if(size == 0)
							{
								size = header >> 8;
								return;
							}
						}
					}
					// uncompressed block of 1 byte
					else
					{
						MEM.Write8(dest++, MEM.Read8(src++));

						--size;
						if (size == 0)
						{
							size = header >> 8;
							return;
						}
					}

					flags <<= 1;
				}
			}
		}

		void LZ77UnCompVram ()
		{
			uint32_t src = R(0);
			uint32_t header = MEM.Read32(src);
			src += 4;
			if (((header >> 4) & 0xF) != 1)
				met_abort("This is not LZ77 data");
			uint32_t size = header >> 8;
			debug("LZ77UnCompVram from " << IOS_ADD << R(0) << " to " << IOS_ADD << R(1) << ", len : " << size);
			uint32_t dest = R(1);
			uint8_t flags;
			uint16_t out = 0;
			uint8_t shift = 0;
			uint16_t block;
			uint8_t blocklen;
			uint32_t realaddr;

			// for each block of a flags byte + 8 blocks
			while (true)
			{
				flags = MEM.Read8(src++);

				for (uint8_t i = 0; i < 8; ++i)
				{
					// compressed block of 2 bytes
					if (flags & 0x80)
					{
						block = MEM.Read8(src) << 8 | MEM.Read8(src+1);
						src += 2;
						blocklen = (block >> 12) + 3;
						realaddr = dest + (shift/8) - (block & 0x0FFF) - 1;
						for(uint16_t j = 0; j < blocklen; ++j) {
							out |= MEM.Read8(realaddr++) << shift;
							shift += 8;

							if(shift == 16) {
								MEM.Write16(dest, out);
								dest += 2;
								out = 0;
								shift = 0;
							}

							--size;
							if(size == 0)
							{
								size = header >> 8;
								return;
							}
						}
					}
					// uncompressed block of 1 byte
					else
					{
						out |= MEM.Read8(src++) << shift;
						shift += 8;

						if (shift == 16)
						{
							MEM.Write16(dest, out);
							dest += 2;
							shift = 0;
							out = 0;
						}

						--size;
						if (size == 0)
						{
							size = header >> 8;
							return;
						}
					}

					flags <<= 1;
				}
			}
		}

		void HuffUnComp ()
		{
			uint32_t src = R(0) & 0xFFFFFFFC;
			uint32_t dest = R(1);
			uint32_t header = MEM.Read32(src);
			src += 4;
			if (((header >> 4) & 0xF) != 2)
				met_abort("This is not Huffman data");
			uint8_t blockLen = header & 0xF;
			uint32_t size = header >> 8;
			if (size % 4)
				met_abort("Size not multiple of 4 in HuffUnComp");
			uint32_t treeStart = src + 1;
			src += 2 + MEM.Read8(src) * 2;

			uint32_t cData = MEM.Read32(src);
			src += 4;
			uint32_t mask = 0x80000000;
			uint32_t treePos = treeStart;
			uint8_t node = MEM.Read8(treePos);
			bool endNode = false;
			uint32_t oData = 0;
			uint8_t oShift = 0;

			while (size)
			{
				treePos = (treePos & 0xFFFFFFFE) + (node & 0x3F) * 2 + 2;
				if (cData & mask)
				{
					++treePos;
					if (node & (0x1 << 6))
						endNode = true;
				}
				else
				{
					if (node & (0x1 << 7))
						endNode = true;
				}
				node = MEM.Read8(treePos);

				if (endNode)
				{
					oData |= ((uint32_t)node) << oShift;
					oShift += blockLen;

					if (oShift >= 32)
					{
						MEM.Write32(dest, oData);
						dest += 4;
						size -= 4;

						oShift -= 32;
						if (oShift)
							oData = node >> (8 - oShift);
						else
							oData = 0;
					}
					endNode = false;
					treePos = treeStart;
					node = MEM.Read8(treePos);
				}

				mask >>= 1;
				if (!mask)
				{
					cData = MEM.Read32(src);
					src += 4;
					mask = 0x80000000;
				}
			}
		}

		void RLUnCompWram ()
		{
			uint32_t src = R(0);
			uint32_t header = MEM.Read32(src);
			src += 4;
			if (((header >> 4) & 0xF) != 3)
				met_abort("This is not RL data");
			uint32_t size = header >> 8;
			debug("RLUnCompWram from " << IOS_ADD << R(0) << " to " << IOS_ADD << R(1) << ", len : " << size);
			uint32_t dest = R(1);
			uint8_t flags;
			uint8_t block;
			uint8_t blocklen;

			// for each block
			while (true)
			{
				flags = MEM.Read8(src++);
				blocklen = flags & 0x7F;

				// compressed block
				if (flags & 0x80)
				{
					blocklen += 3;
					block = MEM.Read8(src++);

					for(uint8_t i = 0; i < blocklen; ++i) {
						MEM.Write8(dest++, block);

						--size;
						if(size == 0)
						{
							size = header >> 8;
							return;
						}
					}
				}
				// uncompressed block
				else
				{
					blocklen += 1;

					for (uint8_t i = 0; i < blocklen; ++i)
					{
						MEM.Write8(dest++, MEM.Read8(src++));

						--size;
						if (size == 0)
						{
							size = header >> 8;
							return;
						}
					}
				}
			}
		}

		void RLUnCompVram ()
		{
			uint32_t src = R(0);
			uint32_t header = MEM.Read32(src);
			src += 4;
			if (((header >> 4) & 0xF) != 3)
				met_abort("This is not RL data");
			uint32_t size = header >> 8;
			debug("RLUnCompVram from " << IOS_ADD << R(0) << " to " << IOS_ADD << R(1) << ", len : " << size);
			uint32_t dest = R(1);
			uint8_t flags;
			uint16_t out = 0;
			uint8_t shift = 0;
			uint8_t block;
			uint8_t blocklen;

			// for each block
			while (true)
			{
				flags = MEM.Read8(src++);
				blocklen = flags & 0x7F;

				// compressed block
				if (flags & 0x80)
				{
					blocklen += 3;
					block = MEM.Read8(src++);

					for(uint8_t i = 0; i < blocklen; ++i) {
						out |= block << shift;
						shift += 8;

						if(shift == 16) {
							MEM.Write16(dest, out);
							dest += 2;
							out = 0;
							shift = 0;
						}

						--size;
						if(size == 0)
						{
							size = header >> 8;
							return;
						}
					}
				}
				// uncompressed block
				else
				{
					blocklen += 1;

					for (uint8_t i = 0; i < blocklen; ++i)
					{
						out |= MEM.Read8(src++) << shift;
						shift += 8;

						if (shift == 16)
						{
							MEM.Write16(dest, out);
							dest += 2;
							shift = 0;
							out = 0;
						}

						--size;
						if (size == 0)
						{
							size = header >> 8;
							return;
						}
					}
				}
			}
		}
	}
}
