Emulator/GbaBios.cs
namespace sGBA;

public partial class GbaBios
{
	public Gba Gba { get; }

	public bool HleActive;

	public int BiosStall;

	public GbaBios( Gba gba )
	{
		Gba = gba;
	}

	public bool HandleSwi( uint comment )
	{
		switch ( comment )
		{
			case 0xF0:
				Gba.Cpu.Registers[11] = (uint)BiosStall;
				return true;
			case 0xFA:
				Gba.Memory.FlushAgbPrint();
				return true;
		}

		HleActive = true;
		BiosStall = 0;
		bool useStall = false;

		switch ( comment )
		{
			case 0x00: // SoftReset
				HleActive = false;
				return false;
			case 0x01: RegisterRamReset(); break;
			case 0x02: // Halt
				HleActive = false;
				return false;
			case 0x03: Stop(); break;
			case 0x04: // IntrWait
			case 0x05: // VBlankIntrWait
				HleActive = false;
				return false;
			case 0x06:
				useStall = true;
				Div();
				break;
			case 0x07:
				useStall = true;
				DivArm();
				break;
			case 0x08:
				useStall = true;
				Sqrt();
				break;
			case 0x09:
				useStall = true;
				ArcTan();
				break;
			case 0x0A:
				useStall = true;
				ArcTan2();
				break;
			case 0x0B: // CpuSet
			case 0x0C: // CpuFastSet
				if ( (Gba.Cpu.Registers[0] >> 24) < 2 )
				{
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Cannot CpuSet from BIOS" );
					break;
				}

				uint alignmentMask = (Gba.Cpu.Registers[2] & (1u << 26)) != 0 ? 3u : 1u;
				if ( (Gba.Cpu.Registers[0] & alignmentMask) != 0 )
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Misaligned CpuSet source" );
				if ( (Gba.Cpu.Registers[1] & alignmentMask) != 0 )
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Misaligned CpuSet destination" );

				HleActive = false;
				return false;
			case 0x0D: GetBiosChecksum(); break;
			case 0x0E: BgAffineSet(); break;
			case 0x0F: ObjAffineSet(); break;
			case 0x10:
				if ( Gba.Cpu.Registers[0] < 0x02000000 )
				{
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad BitUnPack source" );
					break;
				}

				switch ( Gba.Cpu.Registers[1] >> 24 )
				{
					default:
						GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad BitUnPack destination" );
						goto case 0x2;
					case 0x2:
					case 0x3:
					case 0x6:
						BitUnPack();
						break;
				}
				break;
			case 0x11:
				if ( (Gba.Cpu.Registers[0] & 0x0E000000) == 0 )
				{
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad LZ77 source" );
					break;
				}

				switch ( Gba.Cpu.Registers[1] >> 24 )
				{
					default:
						GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad LZ77 destination" );
						goto case 0x2;
					case 0x2:
					case 0x3:
					case 0x6:
						useStall = true;
						LZ77UnCompWram();
						break;
				}
				break;
			case 0x12:
				if ( (Gba.Cpu.Registers[0] & 0x0E000000) == 0 )
				{
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad LZ77 source" );
					break;
				}

				switch ( Gba.Cpu.Registers[1] >> 24 )
				{
					default:
						GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad LZ77 destination" );
						goto case 0x2;
					case 0x2:
					case 0x3:
					case 0x6:
						useStall = true;
						LZ77UnCompVram();
						break;
				}
				break;
			case 0x13:
				if ( (Gba.Cpu.Registers[0] & 0x0E000000) == 0 )
				{
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad Huffman source" );
					break;
				}

				switch ( Gba.Cpu.Registers[1] >> 24 )
				{
					default:
						GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad Huffman destination" );
						goto case 0x2;
					case 0x2:
					case 0x3:
					case 0x6:
						HuffmanUnComp();
						break;
				}
				break;
			case 0x14:
				if ( (Gba.Cpu.Registers[0] & 0x0E000000) == 0 )
				{
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad RL source" );
					break;
				}

				switch ( Gba.Cpu.Registers[1] >> 24 )
				{
					default:
						GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad RL destination" );
						goto case 0x2;
					case 0x2:
					case 0x3:
					case 0x6:
						RLUnCompWram();
						break;
				}
				break;
			case 0x15:
				if ( (Gba.Cpu.Registers[0] & 0x0E000000) == 0 )
				{
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad RL source" );
					break;
				}

				switch ( Gba.Cpu.Registers[1] >> 24 )
				{
					default:
						GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad RL destination" );
						goto case 0x2;
					case 0x2:
					case 0x3:
					case 0x6:
						RLUnCompVram();
						break;
				}
				break;
			case 0x16:
				if ( (Gba.Cpu.Registers[0] & 0x0E000000) == 0 )
				{
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad UnFilter source" );
					break;
				}

				switch ( Gba.Cpu.Registers[1] >> 24 )
				{
					default:
						GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad UnFilter destination" );
						goto case 0x2;
					case 0x2:
					case 0x3:
					case 0x6:
						Diff8BitUnFilterWram();
						break;
				}
				break;
			case 0x17:
				if ( (Gba.Cpu.Registers[0] & 0x0E000000) == 0 )
				{
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad UnFilter source" );
					break;
				}

				switch ( Gba.Cpu.Registers[1] >> 24 )
				{
					default:
						GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad UnFilter destination" );
						goto case 0x2;
					case 0x2:
					case 0x3:
					case 0x6:
						Diff8BitUnFilterVram();
						break;
				}
				break;
			case 0x18:
				if ( (Gba.Cpu.Registers[0] & 0x0E000000) == 0 )
				{
					GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad UnFilter source" );
					break;
				}

				switch ( Gba.Cpu.Registers[1] >> 24 )
				{
					default:
						GbaLog.Write( LogCategory.GBABIOS, LogLevel.GameError, "Bad UnFilter destination" );
						goto case 0x2;
					case 0x2:
					case 0x3:
					case 0x6:
						Diff16BitUnFilter();
						break;
				}
				break;
			case 0x19:
				GbaLog.Write( LogCategory.GBABIOS, LogLevel.Stub, "Stub software interrupt: SoundBias (19)" );
				break;
			case 0x1F: MidiKey2Freq(); break;
			case 0x2A: // SoundDriverGetJumpList
				HleActive = false;
				return false;
			default:
				GbaLog.Write( LogCategory.GBABIOS, LogLevel.Stub, "Stub software interrupt: {0:X2}", comment );
				break;
		}

		if ( useStall && BiosStall >= 18 )
		{
			BiosStall -= 18;
			Gba.Cpu.Cycles += BiosStall & 3;
			BiosStall &= ~3;
			HleActive = false;
			return false;
		}

		if ( useStall )
		{
			Gba.Cpu.Cycles += BiosStall;
		}

		HleActive = false;

		int region = (int)((Gba.Cpu.Gprs[15] >> 24) & 0xF);
		Gba.Cpu.Cycles += 45 + Gba.Memory.WaitstatesNonseq16[region];
		if ( Gba.Cpu.ThumbMode )
			Gba.Cpu.Cycles += Gba.Memory.WaitstatesNonseq16[region] + Gba.Memory.WaitstatesSeq16[region];
		else
			Gba.Cpu.Cycles += Gba.Memory.WaitstatesNonseq32[region] + Gba.Memory.WaitstatesSeq32[region];

		Gba.Memory.BiosPrefetch = 0xE3A02004;

		return true;
	}

	private void GetBiosChecksum()
	{
		Gba.Cpu.Registers[0] = 0xBAAE187F;
		Gba.Cpu.Registers[1] = 1;
		Gba.Cpu.Registers[3] = 0x4000;
	}

	private void RegisterRamReset()
	{
		uint flags = Gba.Cpu.Registers[0];
		var io = Gba.Io;

		io.Write16( 0x000, 0x0080 );

		if ( (flags & 0x01) != 0 )
			Gba.Memory.Wram.AsSpan( 0, 0x40000 ).Clear();
		if ( (flags & 0x02) != 0 )
			Gba.Memory.Iwram.AsSpan( 0, 0x7E00 ).Clear();
		if ( (flags & 0x04) != 0 )
			Array.Clear( Gba.Memory.PaletteRam );
		if ( (flags & 0x08) != 0 )
			Gba.Memory.Vram.AsSpan( 0, 0x18000 ).Clear();
		if ( (flags & 0x10) != 0 )
			Array.Clear( Gba.Memory.Oam );
		if ( (flags & 0x20) != 0 )
		{
			io.Write16( 0x128, 0 );
			io.Write16( 0x134, 0x8000 );
			io.Write16( 0x120, 0 );
			io.Write16( 0x140, 0 );
			io.Write16( 0x150, 0 );
			io.Write16( 0x152, 0 );
			io.Write16( 0x154, 0 );
			io.Write16( 0x156, 0 );
		}
		if ( (flags & 0x40) != 0 )
		{
			var apu = Gba.Audio;
			apu.WriteRegister( 0x60, 0 );
			apu.WriteRegister( 0x62, 0 );
			apu.WriteRegister( 0x64, 0 );
			apu.WriteRegister( 0x68, 0 );
			apu.WriteRegister( 0x6C, 0 );
			apu.WriteRegister( 0x70, 0 );
			apu.WriteRegister( 0x72, 0 );
			apu.WriteRegister( 0x74, 0 );
			apu.WriteRegister( 0x78, 0 );
			apu.WriteRegister( 0x7C, 0 );
			apu.WriteRegister( 0x80, 0 );
			apu.WriteRegister( 0x82, 0 );
			apu.WriteRegister( 0x84, 0 );
			apu.WriteRegister( 0x088, 0x0200 );
			Array.Clear( apu.WaveRam );
		}
		if ( (flags & 0x80) != 0 )
		{
			io.Write16( 0x004, 0 );
			io.Write16( 0x006, 0 );
			io.Write16( 0x008, 0 );
			io.Write16( 0x00A, 0 );
			io.Write16( 0x00C, 0 );
			io.Write16( 0x00E, 0 );
			io.Write16( 0x010, 0 );
			io.Write16( 0x012, 0 );
			io.Write16( 0x014, 0 );
			io.Write16( 0x016, 0 );
			io.Write16( 0x018, 0 );
			io.Write16( 0x01A, 0 );
			io.Write16( 0x01C, 0 );
			io.Write16( 0x01E, 0 );
			io.Write16( 0x020, 0x0100 );
			io.Write16( 0x022, 0 );
			io.Write16( 0x024, 0 );
			io.Write16( 0x026, 0x0100 );
			io.Write16( 0x028, 0 );
			io.Write16( 0x02A, 0 );
			io.Write16( 0x02C, 0 );
			io.Write16( 0x02E, 0 );
			io.Write16( 0x030, 0x0100 );
			io.Write16( 0x032, 0 );
			io.Write16( 0x034, 0 );
			io.Write16( 0x036, 0x0100 );
			io.Write16( 0x038, 0 );
			io.Write16( 0x03A, 0 );
			io.Write16( 0x03C, 0 );
			io.Write16( 0x03E, 0 );
			io.Write16( 0x040, 0 );
			io.Write16( 0x042, 0 );
			io.Write16( 0x044, 0 );
			io.Write16( 0x046, 0 );
			io.Write16( 0x048, 0 );
			io.Write16( 0x04A, 0 );
			io.Write16( 0x04C, 0 );
			io.Write16( 0x050, 0 );
			io.Write16( 0x052, 0 );
			io.Write16( 0x054, 0 );
			for ( uint ch = 0; ch < 4; ch++ )
			{
				uint b = 0x0B0 + ch * 12;
				io.Write16( b, 0 );
				io.Write16( b + 2, 0 );
				io.Write16( b + 4, 0 );
				io.Write16( b + 6, 0 );
				io.Write16( b + 8, 0 );
				io.Write16( b + 10, 0 );
			}
			for ( uint t = 0; t < 4; t++ )
			{
				io.Write16( 0x100 + t * 4, 0 );
				io.Write16( 0x102 + t * 4, 0 );
			}
			io.Write16( 0x200, 0 );
			io.Write16( 0x202, 0xFFFF );
			io.Write16( 0x204, 0 );
			io.Write16( 0x208, 0 );
		}
	}

	private void Stop()
	{
		Gba.Cpu.Halted = true;
	}
}