Cores/Nds/NDS.Cart.cs
namespace Emulotl.Nds;

public sealed partial class NDS
{
	private readonly uint[] Key1_KeyBuf = new uint[0x412];

	public bool IsNativeArm7Bios;

	private byte[] _nativeArm9Bios;
	private byte[] _nativeArm7Bios;

	public void LoadBios9( byte[] data ) => _nativeArm9Bios = data;
	public void LoadBios7( byte[] data ) => _nativeArm7Bios = data;

	private static uint ByteSwap( uint v )
		=> ((v >> 24) & 0xFF) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | ((v << 24) & 0xFF000000);

	private void Key1_Encrypt( uint[] data, int o )
	{
		uint y = data[o];
		uint x = data[o + 1];

		for ( uint i = 0x0; i <= 0xF; i++ )
		{
			uint z = Key1_KeyBuf[i] ^ x;
			x = Key1_KeyBuf[0x012 + (z >> 24)];
			x += Key1_KeyBuf[0x112 + ((z >> 16) & 0xFF)];
			x ^= Key1_KeyBuf[0x212 + ((z >> 8) & 0xFF)];
			x += Key1_KeyBuf[0x312 + (z & 0xFF)];
			x ^= y;
			y = z;
		}

		data[o] = x ^ Key1_KeyBuf[0x10];
		data[o + 1] = y ^ Key1_KeyBuf[0x11];
	}

	private void Key1_Decrypt( uint[] data, int o )
	{
		uint y = data[o];
		uint x = data[o + 1];

		for ( uint i = 0x11; i >= 0x2; i-- )
		{
			uint z = Key1_KeyBuf[i] ^ x;
			x = Key1_KeyBuf[0x012 + (z >> 24)];
			x += Key1_KeyBuf[0x112 + ((z >> 16) & 0xFF)];
			x ^= Key1_KeyBuf[0x212 + ((z >> 8) & 0xFF)];
			x += Key1_KeyBuf[0x312 + (z & 0xFF)];
			x ^= y;
			y = z;
		}

		data[o] = x ^ Key1_KeyBuf[0x1];
		data[o + 1] = y ^ Key1_KeyBuf[0x0];
	}

	private void Key1_ApplyKeycode( uint[] keycode, uint mod )
	{
		Key1_Encrypt( keycode, 1 );
		Key1_Encrypt( keycode, 0 );

		uint[] temp = [0, 0];

		for ( uint i = 0; i <= 0x11; i++ )
			Key1_KeyBuf[i] ^= ByteSwap( keycode[i % mod] );

		for ( uint i = 0; i <= 0x410; i += 2 )
		{
			Key1_Encrypt( temp, 0 );
			Key1_KeyBuf[i] = temp[1];
			Key1_KeyBuf[i + 1] = temp[0];
		}
	}

	private void Key1_LoadKeyBuf()
	{
		if ( IsNativeArm7Bios )
		{
			for ( int i = 0; i < 0x412; i++ )
				Key1_KeyBuf[i] = Read32( ARM7BIOS, (uint)(0x0030 + i * 4) );
		}
		else
		{
			Array.Clear( Key1_KeyBuf );
		}
	}

	private void Key1_InitKeycode( uint idcode, uint level, uint mod )
	{
		Key1_LoadKeyBuf();

		uint[] keycode = [idcode, idcode >> 1, idcode << 1];
		if ( level >= 1 ) Key1_ApplyKeycode( keycode, mod );
		if ( level >= 2 ) Key1_ApplyKeycode( keycode, mod );
		if ( level >= 3 )
		{
			keycode[1] <<= 1;
			keycode[2] >>= 1;
			Key1_ApplyKeycode( keycode, mod );
		}
	}

	private void Key1_DecryptAt( byte[] buf, int off )
	{
		uint[] d = [Read32( buf, (uint)off ), Read32( buf, (uint)(off + 4) )];
		Key1_Decrypt( d, 0 );
		Write32( buf, (uint)off, d[0] );
		Write32( buf, (uint)(off + 4), d[1] );
	}

	private void Key1_EncryptAt( byte[] buf, int off )
	{
		uint[] d = [Read32( buf, (uint)off ), Read32( buf, (uint)(off + 4) )];
		Key1_Encrypt( d, 0 );
		Write32( buf, (uint)off, d[0] );
		Write32( buf, (uint)(off + 4), d[1] );
	}

	private bool _secureAreaProcessed;

	private void ProcessSecureArea()
	{
		if ( _secureAreaProcessed )
			return;
		_secureAreaProcessed = true;

		uint arm9base = RomU32( 0x20 );
		if ( !(arm9base >= 0x4000 && arm9base < 0x8000) )
			return;
		if ( _rom == null || arm9base + 0x800 > _rom.Length )
			return;

		if ( Read32( _rom, arm9base ) == 0xE7FFDEFF && Read32( _rom, arm9base + 0x10 ) != 0xE7FFDEFF )
		{
			uint gamecode = RomU32( 0x0C );

			_rom[arm9base + 0] = (byte)'e';
			_rom[arm9base + 1] = (byte)'n';
			_rom[arm9base + 2] = (byte)'c';
			_rom[arm9base + 3] = (byte)'r';
			_rom[arm9base + 4] = (byte)'y';
			_rom[arm9base + 5] = (byte)'O';
			_rom[arm9base + 6] = (byte)'b';
			_rom[arm9base + 7] = (byte)'j';

			Key1_InitKeycode( gamecode, 3, 2 );
			for ( uint i = 0; i < 0x800; i += 8 )
				Key1_EncryptAt( _rom, (int)(arm9base + i) );

			Key1_InitKeycode( gamecode, 2, 2 );
			Key1_EncryptAt( _rom, (int)arm9base );
		}
	}

	private void DecryptSecureArea( byte[] outBuf )
	{
		uint gamecode = RomU32( 0x0C );
		uint arm9base = RomU32( 0x20 );

		for ( int i = 0; i < 0x800; i++ )
			outBuf[i] = _rom[arm9base + i];

		Key1_InitKeycode( gamecode, 2, 2 );
		Key1_DecryptAt( outBuf, 0 );

		Key1_InitKeycode( gamecode, 3, 2 );
		for ( int i = 0; i < 0x800; i += 8 )
			Key1_DecryptAt( outBuf, i );

		bool ok = outBuf[0] == 'e' && outBuf[1] == 'n' && outBuf[2] == 'c' && outBuf[3] == 'r'
			&& outBuf[4] == 'y' && outBuf[5] == 'O' && outBuf[6] == 'b' && outBuf[7] == 'j';

		if ( ok )
		{
			Platform.Log( LogLevel.Info, "Secure area decryption OK" );
			Write32( outBuf, 0, 0xE7FFDEFF );
			Write32( outBuf, 4, 0xE7FFDEFF );
		}
		else
		{
			Platform.Log( LogLevel.Warn, "Secure area decryption failed" );
			for ( int i = 0; i < 0x800; i += 4 )
				Write32( outBuf, (uint)i, 0xE7FFDEFF );
		}
	}
}