Emulator/ArmCore.cs
using System.IO;

namespace sGBA;

public partial class ArmCore
{
	private const uint PsrUserMask = 0xF0000000u;
	private const uint PsrPrivMask = 0x000000CFu;
	private const uint PsrStateMask = 0x00000020u;

	public uint[] Gprs = new uint[16];
	public uint[] Registers => Gprs;

	public bool FlagN, FlagZ, FlagC, FlagV;
	public bool IrqDisable = true;
	public bool FiqDisable = true;
	public bool ThumbMode;
	public PrivilegeMode PrivilegeMode = PrivilegeMode.System;

	private readonly uint[][] _bankedRegisters = new uint[6][];
	private readonly uint[] _bankedSPSRs = new uint[6];

	public long Cycles;
	public long InstructionStartCycles;
	public bool Halted;
	public bool IrqPending;
	public uint OpenBusPrefetch;

	public uint[] PcTrace = new uint[128];
	public bool[] PcTraceThumb = new bool[64];
	public int PcTraceIndex;

	public bool CrashDetected;
	public uint CrashPc;
	public uint CrashCpsr;
	public uint[] CrashRegs;
	public bool CrashThumb;

	public uint[] BankedSPSRs => _bankedSPSRs;

	private uint _prefetch0;
	private uint _prefetch1;
	private bool _prefetchFlushed = true;
	private bool _pipelineCyclesPrecharged;

	public Gba Gba { get; }
	private readonly GbaMemory Memory;

	public ArmCore( Gba gba )
	{
		Gba = gba;
		Memory = gba.Memory;
		for ( int i = 0; i < 6; i++ )
			_bankedRegisters[i] = new uint[7];
	}

	public void Reset()
	{
		Array.Clear( Gprs );
		FlagN = FlagZ = FlagC = FlagV = false;
		IrqDisable = true;
		FiqDisable = true;
		ThumbMode = false;
		PrivilegeMode = PrivilegeMode.System;
		Halted = false;
		IrqPending = false;
		_prefetchFlushed = true;
		_pipelineCyclesPrecharged = false;
		Cycles = 0;

		for ( int i = 0; i < 6; i++ )
		{
			_bankedRegisters[i][0] = 0;
			_bankedRegisters[i][1] = 0;
			_bankedRegisters[i][2] = 0;
			_bankedRegisters[i][3] = 0;
			_bankedRegisters[i][4] = 0;
			_bankedRegisters[i][5] = 0;
			_bankedRegisters[i][6] = 0;
			_bankedSPSRs[i] = 0;
		}
	}

	public void SkipBios()
	{
		SetPrivilegeMode( PrivilegeMode.IRQ );
		Gprs[13] = GbaConstants.SpBaseIrq;
		SetPrivilegeMode( PrivilegeMode.Supervisor );
		Gprs[13] = GbaConstants.SpBaseSupervisor;
		SetPrivilegeMode( PrivilegeMode.System );
		Gprs[13] = GbaConstants.SpBaseSystem;

		IrqDisable = false;
		FiqDisable = false;
		Gprs[15] = 0x08000000;

		Gprs[15] &= ~3u;
		_prefetch0 = Memory.Load32( Gprs[15] );
		Gprs[15] += 4;
		_prefetch1 = Memory.Load32( Gprs[15] );
		Gprs[15] += 4;
		Memory.LastPrefetchedPc = 0;
		_prefetchFlushed = false;
	}

	public void Run( long targetCycles )
	{
		if ( CrashDetected )
		{
			Cycles = targetCycles;
			return;
		}

		if ( Halted )
			return;

		while ( Cycles < targetCycles && Cycles < Gba.Timing.NextEvent )
		{
			if ( Gba.Io.LockstepBlocked )
				return;

			if ( _prefetchFlushed )
			{
				FlushPipeline();
				_prefetchFlushed = false;
			}
			else if ( IrqPending && !IrqDisable )
			{
				RaiseIrq();
				IrqPending = false;
				FlushPipeline();
				_prefetchFlushed = false;
			}
			else
			{
				uint instrAddr = ThumbMode ? Gprs[15] - 4 : Gprs[15] - 8;

				if ( !IsExecutableAddress( instrAddr ) )
				{
					if ( !CrashDetected )
					{
						CrashDetected = true;
						CrashPc = instrAddr;
						CrashCpsr = GetCpsrRaw();
						CrashRegs = new uint[16];
						Array.Copy( Gprs, CrashRegs, 16 );
						CrashThumb = ThumbMode;
					}
					Cycles = targetCycles;
					return;
				}

				InstructionStartCycles = Cycles;

				if ( ThumbMode )
					ExecuteThumb();
				else
					ExecuteArm();

				if ( _prefetchFlushed )
					PrechargePipelineCycles();
			}

			if ( Halted || CrashDetected )
				return;
		}
	}

	private static bool IsExecutableAddress( uint addr )
	{
		int region = (int)(addr >> 24);
		return region == 0x00 || region == 0x02 || region == 0x03 ||
			   (region >= 0x08 && region <= 0x0D);
	}

	public void FlushPipeline()
	{
		Memory.LastPrefetchedPc = 0;

		if ( ThumbMode )
		{
			Gprs[15] &= ~1u;
			_prefetch0 = Memory.Load16( Gprs[15] );
			Gprs[15] += 2;
			_prefetch1 = Memory.Load16( Gprs[15] );
			Gprs[15] += 2;
		}
		else
		{
			Gprs[15] &= ~3u;
			_prefetch0 = Memory.Load32( Gprs[15] );
			Gprs[15] += 4;
			_prefetch1 = Memory.Load32( Gprs[15] );
			Gprs[15] += 4;
		}

		if ( !_pipelineCyclesPrecharged )
		{
			int region = (int)((Gprs[15] >> 24) & 0xF);
			if ( ThumbMode )
				Cycles += 2 + Memory.WaitstatesNonseq16[region] + Memory.WaitstatesSeq16[region];
			else
				Cycles += 2 + Memory.WaitstatesNonseq32[region] + Memory.WaitstatesSeq32[region];
		}

		_pipelineCyclesPrecharged = false;
	}

	public void PrechargePipelineCycles()
	{
		if ( _pipelineCyclesPrecharged )
			return;

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

		_pipelineCyclesPrecharged = true;
	}

	public uint LoadBadValue()
	{
		uint value = OpenBusPrefetch;

		if ( ThumbMode )
		{
			uint prefetch0 = _prefetch0 & 0xFFFF;
			uint prefetch1 = _prefetch1 & 0xFFFF;
			value = prefetch1;

			switch ( Gprs[15] >> 24 )
			{
				case 0x0:
				case 0x7:
					value <<= 16;
					value |= prefetch0;
					break;
				case 0x3:
					if ( (Gprs[15] & 2) != 0 )
					{
						value <<= 16;
						value |= prefetch0;
					}
					else
					{
						value |= prefetch0 << 16;
					}
					break;
				default:
					value |= value << 16;
					break;
			}
		}

		return value;
	}

	public void SerializePipeline( BinaryWriter w )
	{
		w.Write( _prefetchFlushed );
		w.Write( _pipelineCyclesPrecharged );
		w.Write( _prefetch0 );
		w.Write( _prefetch1 );
	}

	public void DeserializePipeline( BinaryReader r )
	{
		_prefetchFlushed = r.ReadBoolean();
		_pipelineCyclesPrecharged = r.ReadBoolean();
		_prefetch0 = r.ReadUInt32();
		_prefetch1 = r.ReadUInt32();
	}

	public void RaiseIrq()
	{
		uint savedCpsr = GetCpsrRaw();
		SetPrivilegeMode( PrivilegeMode.IRQ );
		SetSpsr( savedCpsr );
		Gprs[14] = Gprs[15] - (ThumbMode ? 0u : 4u);
		IrqDisable = true;
		ThumbMode = false;
		Gprs[15] = GbaConstants.BaseIrq;
		_prefetchFlushed = true;
		Halted = false;
	}

	public uint GetCpsrRaw()
	{
		uint cpsr = 0;
		if ( FlagN ) cpsr |= 0x80000000;
		if ( FlagZ ) cpsr |= 0x40000000;
		if ( FlagC ) cpsr |= 0x20000000;
		if ( FlagV ) cpsr |= 0x10000000;
		if ( IrqDisable ) cpsr |= 0x80;
		if ( FiqDisable ) cpsr |= 0x40;
		if ( ThumbMode ) cpsr |= 0x20;
		cpsr |= (uint)PrivilegeMode;
		return cpsr;
	}

	public void SetCpsr( uint cpsr )
	{
		FlagN = (cpsr & 0x80000000) != 0;
		FlagZ = (cpsr & 0x40000000) != 0;
		FlagC = (cpsr & 0x20000000) != 0;
		FlagV = (cpsr & 0x10000000) != 0;
		bool wasIrqDisabled = IrqDisable;
		IrqDisable = (cpsr & 0x80) != 0;
		FiqDisable = (cpsr & 0x40) != 0;
		ThumbMode = (cpsr & 0x20) != 0;

		PrivilegeMode newMode = (PrivilegeMode)(cpsr & 0x1F);
		if ( newMode != PrivilegeMode && IsValidMode( newMode ) )
			SetPrivilegeMode( newMode );

		Gba.Io.TestIrq();
	}

	private static bool IsValidMode( PrivilegeMode mode )
	{
		return mode == PrivilegeMode.User || mode == PrivilegeMode.FIQ || mode == PrivilegeMode.IRQ ||
			   mode == PrivilegeMode.Supervisor || mode == PrivilegeMode.Abort ||
			   mode == PrivilegeMode.Undefined || mode == PrivilegeMode.System;
	}

	public void SetPrivilegeMode( PrivilegeMode mode )
	{
		if ( mode == PrivilegeMode )
			return;

		RegisterBank newBank = SelectBank( mode );
		RegisterBank oldBank = SelectBank( PrivilegeMode );

		if ( newBank != oldBank )
		{
			if ( mode == PrivilegeMode.FIQ || PrivilegeMode == PrivilegeMode.FIQ )
			{
				int oldFiqBank = oldBank == RegisterBank.Fiq ? 1 : 0;
				int newFiqBank = newBank == RegisterBank.Fiq ? 1 : 0;
				_bankedRegisters[oldFiqBank][2] = Gprs[8];
				_bankedRegisters[oldFiqBank][3] = Gprs[9];
				_bankedRegisters[oldFiqBank][4] = Gprs[10];
				_bankedRegisters[oldFiqBank][5] = Gprs[11];
				_bankedRegisters[oldFiqBank][6] = Gprs[12];
				Gprs[8] = _bankedRegisters[newFiqBank][2];
				Gprs[9] = _bankedRegisters[newFiqBank][3];
				Gprs[10] = _bankedRegisters[newFiqBank][4];
				Gprs[11] = _bankedRegisters[newFiqBank][5];
				Gprs[12] = _bankedRegisters[newFiqBank][6];
			}
			_bankedRegisters[(int)oldBank][0] = Gprs[13];
			_bankedRegisters[(int)oldBank][1] = Gprs[14];
			Gprs[13] = _bankedRegisters[(int)newBank][0];
			Gprs[14] = _bankedRegisters[(int)newBank][1];
		}

		PrivilegeMode = mode;
	}

	private static RegisterBank SelectBank( PrivilegeMode mode )
	{
		switch ( mode )
		{
			case PrivilegeMode.User:
			case PrivilegeMode.System:
				return RegisterBank.None;
			case PrivilegeMode.FIQ:
				return RegisterBank.Fiq;
			case PrivilegeMode.IRQ:
				return RegisterBank.Irq;
			case PrivilegeMode.Supervisor:
				return RegisterBank.Supervisor;
			case PrivilegeMode.Abort:
				return RegisterBank.Abort;
			case PrivilegeMode.Undefined:
				return RegisterBank.Undefined;
			default:
				return RegisterBank.None;
		}
	}

	public uint GetSpsr()
	{
		if ( PrivilegeMode == PrivilegeMode.User || PrivilegeMode == PrivilegeMode.System )
			return GetCpsrRaw();
		return _bankedSPSRs[(int)SelectBank( PrivilegeMode )];
	}

	public void SetSpsr( uint value )
	{
		_bankedSPSRs[(int)SelectBank( PrivilegeMode )] = value;
	}

	private uint GetUserReg( int reg )
	{
		if ( PrivilegeMode == PrivilegeMode.User || PrivilegeMode == PrivilegeMode.System )
			return Gprs[reg];

		if ( reg >= 8 && reg <= 12 && PrivilegeMode == PrivilegeMode.FIQ )
			return _bankedRegisters[(int)RegisterBank.None][reg - 6];

		if ( reg == 13 || reg == 14 )
			return _bankedRegisters[(int)RegisterBank.None][reg - 13];

		return Gprs[reg];
	}

	private void SetUserReg( int reg, uint value )
	{
		if ( PrivilegeMode == PrivilegeMode.User || PrivilegeMode == PrivilegeMode.System )
		{
			Gprs[reg] = value;
			return;
		}

		if ( reg >= 8 && reg <= 12 && PrivilegeMode == PrivilegeMode.FIQ )
		{
			_bankedRegisters[(int)RegisterBank.None][reg - 6] = value;
			return;
		}

		if ( reg == 13 || reg == 14 )
		{
			_bankedRegisters[(int)RegisterBank.None][reg - 13] = value;
			return;
		}

		Gprs[reg] = value;
	}

	private void SetLogicFlags( uint result, bool carry )
	{
		FlagN = (result & 0x80000000) != 0;
		FlagZ = result == 0;
		FlagC = carry;
	}

	private void SetAddFlags( uint a, uint b, uint result )
	{
		FlagN = (result & 0x80000000) != 0;
		FlagZ = result == 0;
		FlagC = result < a;
		FlagV = ((a ^ result) & (b ^ result) & 0x80000000) != 0;
	}

	private void SetSubFlags( uint a, uint b, uint result )
	{
		FlagN = (result & 0x80000000) != 0;
		FlagZ = result == 0;
		FlagC = a >= b;
		FlagV = ((a ^ b) & (a ^ result) & 0x80000000) != 0;
	}

	private void SetAdcFlags( uint a, uint b, bool carry )
	{
		ulong result = (ulong)a + b + (carry ? 1u : 0u);
		uint r = (uint)result;
		FlagN = (r & 0x80000000) != 0;
		FlagZ = r == 0;
		FlagC = result > 0xFFFFFFFF;
		FlagV = ((a ^ r) & (b ^ r) & 0x80000000) != 0;
	}

	private void SetSbcFlags( uint a, uint b, bool carry )
	{
		uint borrow = carry ? 0u : 1u;
		ulong result = (ulong)a - b - borrow;
		uint r = (uint)result;
		FlagN = (r & 0x80000000) != 0;
		FlagZ = r == 0;
		FlagC = a >= (ulong)b + borrow;
		FlagV = ((a ^ b) & (a ^ r) & 0x80000000) != 0;
	}

	private bool CheckCondition( uint cond )
	{
		switch ( cond )
		{
			case 0x0: return FlagZ;
			case 0x1: return !FlagZ;
			case 0x2: return FlagC;
			case 0x3: return !FlagC;
			case 0x4: return FlagN;
			case 0x5: return !FlagN;
			case 0x6: return FlagV;
			case 0x7: return !FlagV;
			case 0x8: return FlagC && !FlagZ;
			case 0x9: return !FlagC || FlagZ;
			case 0xA: return FlagN == FlagV;
			case 0xB: return FlagN != FlagV;
			case 0xC: return !FlagZ && FlagN == FlagV;
			case 0xD: return FlagZ || FlagN != FlagV;
			case 0xE: return true;
			default: return false;
		}
	}

	private static uint Ror( uint val, int amount )
	{
		amount &= 31;
		return (val >> amount) | (val << (32 - amount));
	}

	private uint ReadWordRotated( uint address )
	{
		uint val = Memory.Load32( address );
		int rot = (int)(address & 3) * 8;
		if ( rot != 0 ) val = Ror( val, rot );
		return val;
	}

	private uint ApplyShift( uint val, uint shiftType, uint amount )
	{
		switch ( shiftType )
		{
			case 0: return amount == 0 ? val : val << (int)amount;
			case 1: return amount == 0 ? 0 : val >> (int)amount;
			case 2: return amount == 0 ? (uint)((int)val >> 31) : (uint)((int)val >> (int)amount);
			case 3: return amount == 0 ? (FlagC ? 0x80000000u : 0) | (val >> 1) : Ror( val, (int)amount );
			default: return val;
		}
	}

	private static int BitCount( uint val ) => System.Numerics.BitOperations.PopCount( val );

	private static int MultiplyExtraCycles( uint rs )
	{
		if ( (rs & 0xFFFFFF00) == 0 || (rs & 0xFFFFFF00) == 0xFFFFFF00 ) return 1;
		if ( (rs & 0xFFFF0000) == 0 || (rs & 0xFFFF0000) == 0xFFFF0000 ) return 2;
		if ( (rs & 0xFF000000) == 0 || (rs & 0xFF000000) == 0xFF000000 ) return 3;
		return 4;
	}

	private static int MultiplyExtraCyclesUnsigned( uint rs )
	{
		if ( (rs & 0xFFFFFF00) == 0 ) return 1;
		if ( (rs & 0xFFFF0000) == 0 ) return 2;
		if ( (rs & 0xFF000000) == 0 ) return 3;
		return 4;
	}
}

public enum PrivilegeMode : uint
{
	User = 0x10,
	FIQ = 0x11,
	IRQ = 0x12,
	Supervisor = 0x13,
	Abort = 0x17,
	Undefined = 0x1B,
	System = 0x1F,
}

public enum RegisterBank
{
	None = 0,
	Fiq = 1,
	Irq = 2,
	Supervisor = 3,
	Abort = 4,
	Undefined = 5,
}