Decompiled source of Game Boy Emulator v1.5.0

plugins/Game_Boy_Emulator/CodeDMGEmu.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BRCCodeDmg;
using BepInEx;
using BepInEx.Configuration;
using CommonAPI;
using CommonAPI.Phone;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Reptile;
using Reptile.Phone;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
public sealed class APU
{
	private const int CpuClock = 4194304;

	public const int DefaultSampleFifoSize = 8192;

	private readonly float[] sampleFifo;

	private readonly MMU mmu;

	private readonly int sampleRate;

	private readonly SquareChannel ch1;

	private readonly SquareChannel ch2;

	private readonly WaveChannel ch3;

	private readonly NoiseChannel ch4;

	private int frameSequencerStep;

	private double sampleCycleCounter;

	private readonly object sampleLock = new object();

	private int sampleReadIndex;

	private int sampleWriteIndex;

	private int sampleCount;

	private double capacitorL;

	private double capacitorR;

	private readonly double hpfChargeFactor;

	private float lastLeftSample;

	private float lastRightSample;

	private bool enabled = true;

	public int BufferedSamples
	{
		get
		{
			lock (sampleLock)
			{
				return sampleCount;
			}
		}
	}

	public int UnderrunCount { get; private set; }

	public int OverflowDropCount { get; private set; }

	public APU(MMU mmu, int sampleRate = 48000, int sampleFifoSize = 8192)
	{
		this.mmu = mmu;
		this.sampleRate = ((sampleRate > 0) ? sampleRate : 48000);
		if (sampleFifoSize <= 0)
		{
			sampleFifoSize = 8192;
		}
		sampleFifo = new float[sampleFifoSize];
		ch1 = new SquareChannel(hasSweep: true);
		ch2 = new SquareChannel(hasSweep: false);
		ch3 = new WaveChannel(mmu.IsCGBMode);
		ch4 = new NoiseChannel();
		hpfChargeFactor = Math.Pow(mmu.IsCGBMode ? 0.998943 : 0.999958, 4194304.0 / (double)this.sampleRate);
	}

	public void SetEnabled(bool enabled)
	{
		this.enabled = enabled;
	}

	public void Step(int tCycles)
	{
		double num = 4194304.0 / (double)sampleRate;
		if (!enabled)
		{
			sampleCycleCounter += tCycles;
			while (sampleCycleCounter >= num)
			{
				sampleCycleCounter -= num;
				PushStereoSample(0f, 0f);
			}
			return;
		}
		if ((mmu.NR52 & 0x80) == 0)
		{
			sampleCycleCounter += tCycles;
			while (sampleCycleCounter >= num)
			{
				sampleCycleCounter -= num;
				PushStereoSample(0f, 0f);
			}
			return;
		}
		ch1.StepTimer(tCycles);
		ch2.StepTimer(tCycles);
		ch3.StepTimer(tCycles);
		ch4.StepTimer(tCycles);
		sampleCycleCounter += tCycles;
		while (sampleCycleCounter >= num)
		{
			sampleCycleCounter -= num;
			MixAndPushSample();
		}
	}

	public void ClockDivApu()
	{
		if ((mmu.NR52 & 0x80u) != 0)
		{
			if ((frameSequencerStep & 1) == 0)
			{
				ch1.ClockLength();
				ch2.ClockLength();
				ch3.ClockLength();
				ch4.ClockLength();
			}
			if (frameSequencerStep == 2 || frameSequencerStep == 6)
			{
				ch1.ClockSweep();
			}
			if (frameSequencerStep == 7)
			{
				ch1.ClockEnvelope();
				ch2.ClockEnvelope();
				ch4.ClockEnvelope();
			}
			frameSequencerStep = (frameSequencerStep + 1) & 7;
		}
	}

	public byte ReadRegister(ushort address)
	{
		switch (address)
		{
		case 65296:
			return (byte)(ch1.NR10 | 0x80u);
		case 65297:
			return (byte)(ch1.NR11 | 0x3Fu);
		case 65298:
			return ch1.NR12;
		case 65299:
			return byte.MaxValue;
		case 65300:
			return (byte)(ch1.NR14 | 0xBFu);
		case 65302:
			return (byte)(ch2.NR11 | 0x3Fu);
		case 65303:
			return ch2.NR12;
		case 65304:
			return byte.MaxValue;
		case 65305:
			return (byte)(ch2.NR14 | 0xBFu);
		case 65306:
			return (byte)(ch3.NR30 | 0x7Fu);
		case 65307:
			return byte.MaxValue;
		case 65308:
			return (byte)(ch3.NR32 | 0x9Fu);
		case 65309:
			return byte.MaxValue;
		case 65310:
			return (byte)(ch3.NR34 | 0xBFu);
		case 65312:
			return byte.MaxValue;
		case 65313:
			return ch4.NR42;
		case 65314:
			return ch4.NR43;
		case 65315:
			return (byte)(ch4.NR44 | 0xBFu);
		case 65316:
			return mmu.NR50;
		case 65317:
			return mmu.NR51;
		case 65318:
			return (byte)((mmu.NR52 & 0x80u) | 0x70u | (ch1.Enabled ? 1u : 0u) | (ch2.Enabled ? 2u : 0u) | (ch3.Enabled ? 4u : 0u) | (ch4.Enabled ? 8u : 0u));
		default:
			if (address >= 65328 && address <= 65343)
			{
				return ch3.ReadWaveRam(address);
			}
			return byte.MaxValue;
		}
	}

	public void WriteRegister(ushort address, byte value)
	{
		if (address == 65318)
		{
			WriteNR52(value);
			return;
		}
		if ((mmu.NR52 & 0x80) == 0)
		{
			if (address >= 65328 && address <= 65343)
			{
				ch3.WriteWaveRam(address, value);
			}
			if (!mmu.IsCGBMode)
			{
				switch (address)
				{
				case 65297:
					ch1.WriteLengthOnly(value);
					break;
				case 65302:
					ch2.WriteLengthOnly(value);
					break;
				case 65307:
					ch3.WriteLengthOnly(value);
					break;
				case 65312:
					ch4.WriteLengthOnly(value);
					break;
				}
			}
			return;
		}
		switch (address)
		{
		case 65296:
			ch1.WriteNR10(value);
			break;
		case 65297:
			ch1.WriteNR11(value);
			break;
		case 65298:
			ch1.WriteNR12(value);
			break;
		case 65299:
			ch1.WriteNR13(value);
			break;
		case 65300:
		{
			bool flag = (ch1.NR14 & 0x40) != 0;
			bool flag2 = (value & 0x40) != 0;
			bool flag3 = (value & 0x80) != 0;
			bool flag4 = (frameSequencerStep & 1) == 1;
			if (flag4 && !flag && flag2 && ch1.LengthCounter > 0)
			{
				ch1.ExtraLengthClock();
			}
			ch1.WriteNR14(value);
			if (flag4 && flag3 && flag2 && ch1.LengthWasZeroOnTrigger)
			{
				ch1.ExtraLengthClock();
			}
			if (flag3 && frameSequencerStep == 7)
			{
				ch1.DelayEnvelopeTimerForObscureTrigger();
			}
			break;
		}
		case 65302:
			ch2.WriteNR11(value);
			break;
		case 65303:
			ch2.WriteNR12(value);
			break;
		case 65304:
			ch2.WriteNR13(value);
			break;
		case 65305:
		{
			bool flag9 = (ch2.NR14 & 0x40) != 0;
			bool flag10 = (value & 0x40) != 0;
			bool flag11 = (value & 0x80) != 0;
			bool flag12 = (frameSequencerStep & 1) == 1;
			if (flag12 && !flag9 && flag10 && ch2.LengthCounter > 0)
			{
				ch2.ExtraLengthClock();
			}
			ch2.WriteNR14(value);
			if (flag12 && flag11 && flag10 && ch2.LengthWasZeroOnTrigger)
			{
				ch2.ExtraLengthClock();
			}
			if (flag11 && frameSequencerStep == 7)
			{
				ch2.DelayEnvelopeTimerForObscureTrigger();
			}
			break;
		}
		case 65306:
			ch3.WriteNR30(value);
			break;
		case 65307:
			ch3.WriteNR31(value);
			break;
		case 65308:
			ch3.WriteNR32(value);
			break;
		case 65309:
			ch3.WriteNR33(value);
			break;
		case 65310:
		{
			bool flag13 = (ch3.NR34 & 0x40) != 0;
			bool flag14 = (value & 0x40) != 0;
			bool flag15 = (value & 0x80) != 0;
			bool flag16 = (frameSequencerStep & 1) == 1;
			if (flag16 && !flag13 && flag14 && ch3.LengthCounter > 0)
			{
				ch3.ExtraLengthClock();
			}
			ch3.WriteNR34(value);
			if (flag16 && flag15 && flag14 && ch3.LengthWasZeroOnTrigger)
			{
				ch3.ExtraLengthClock();
			}
			break;
		}
		case 65312:
			ch4.WriteNR41(value);
			break;
		case 65313:
			ch4.WriteNR42(value);
			break;
		case 65314:
			ch4.WriteNR43(value);
			break;
		case 65315:
		{
			bool flag5 = (ch4.NR44 & 0x40) != 0;
			bool flag6 = (value & 0x40) != 0;
			bool flag7 = (value & 0x80) != 0;
			bool flag8 = (frameSequencerStep & 1) == 1;
			if (flag8 && !flag5 && flag6 && ch4.LengthCounter > 0)
			{
				ch4.ExtraLengthClock();
			}
			ch4.WriteNR44(value);
			if (flag8 && flag7 && flag6 && ch4.LengthWasZeroOnTrigger)
			{
				ch4.ExtraLengthClock();
			}
			if (flag7 && frameSequencerStep == 7)
			{
				ch4.DelayEnvelopeTimerForObscureTrigger();
			}
			break;
		}
		case 65316:
			mmu.NR50 = value;
			break;
		case 65317:
			mmu.NR51 = value;
			break;
		default:
			if (address >= 65328 && address <= 65343)
			{
				ch3.WriteWaveRam(address, value);
			}
			break;
		}
	}

	private void WriteNR52(byte value)
	{
		bool flag = (value & 0x80) != 0;
		bool flag2 = (mmu.NR52 & 0x80) != 0;
		if (!flag && flag2)
		{
			mmu.NR52 = 0;
			mmu.NR50 = 0;
			mmu.NR51 = 0;
			frameSequencerStep = 0;
			ch1.PowerOff();
			ch2.PowerOff();
			ch3.PowerOff();
			ch4.PowerOff();
			capacitorL = 0.0;
			capacitorR = 0.0;
		}
		else if (flag && !flag2)
		{
			mmu.NR52 = 128;
			frameSequencerStep = 0;
			ch1.ResetAfterPowerOn(mmu.IsCGBMode);
			ch2.ResetAfterPowerOn(mmu.IsCGBMode);
			ch3.ResetAfterPowerOn(mmu.IsCGBMode);
			ch4.ResetAfterPowerOn(mmu.IsCGBMode);
			capacitorL = 0.0;
			capacitorR = 0.0;
		}
	}

	private static float DigitalToAnalog(int digital)
	{
		return (float)digital / 7.5f - 1f;
	}

	private float Channel1Analog()
	{
		return ch1.DacEnabled ? DigitalToAnalog(ch1.GetDigitalOutput()) : 0f;
	}

	private float Channel2Analog()
	{
		return ch2.DacEnabled ? DigitalToAnalog(ch2.GetDigitalOutput()) : 0f;
	}

	private float Channel3Analog()
	{
		return ch3.DacEnabled ? DigitalToAnalog(ch3.GetDigitalOutput()) : 0f;
	}

	private float Channel4Analog()
	{
		return ch4.DacEnabled ? DigitalToAnalog(ch4.GetDigitalOutput()) : 0f;
	}

	private void MixAndPushSample()
	{
		double num = 0.0;
		double num2 = 0.0;
		float num3 = Channel1Analog();
		float num4 = Channel2Analog();
		float num5 = Channel3Analog();
		float num6 = Channel4Analog();
		if ((mmu.NR51 & 0x10u) != 0)
		{
			num += (double)num3;
		}
		if ((mmu.NR51 & 0x20u) != 0)
		{
			num += (double)num4;
		}
		if ((mmu.NR51 & 0x40u) != 0)
		{
			num += (double)num5;
		}
		if ((mmu.NR51 & 0x80u) != 0)
		{
			num += (double)num6;
		}
		if (((uint)mmu.NR51 & (true ? 1u : 0u)) != 0)
		{
			num2 += (double)num3;
		}
		if ((mmu.NR51 & 2u) != 0)
		{
			num2 += (double)num4;
		}
		if ((mmu.NR51 & 4u) != 0)
		{
			num2 += (double)num5;
		}
		if ((mmu.NR51 & 8u) != 0)
		{
			num2 += (double)num6;
		}
		double num7 = (double)(((mmu.NR50 >> 4) & 7) + 1) / 8.0;
		double num8 = (double)((mmu.NR50 & 7) + 1) / 8.0;
		num *= num7 * 0.25;
		num2 *= num8 * 0.25;
		bool dacsEnabled = ch1.DacEnabled || ch2.DacEnabled || ch3.DacEnabled || ch4.DacEnabled;
		num = HighPassDMG(num, ref capacitorL, dacsEnabled);
		num2 = HighPassDMG(num2, ref capacitorR, dacsEnabled);
		PushStereoSample(Clamp((float)num), Clamp((float)num2));
	}

	private double HighPassDMG(double input, ref double capacitor, bool dacsEnabled)
	{
		double num = input - capacitor;
		if (dacsEnabled)
		{
			capacitor = input - num * hpfChargeFactor;
		}
		else
		{
			capacitor *= hpfChargeFactor;
			num = 0.0 - capacitor;
		}
		return num;
	}

	private static float Clamp(float v)
	{
		if (v < -1f)
		{
			return -1f;
		}
		if (v > 1f)
		{
			return 1f;
		}
		return v;
	}

	private void PushStereoSample(float left, float right)
	{
		lock (sampleLock)
		{
			EnsureSpaceNoLock(2);
			sampleFifo[sampleWriteIndex] = left;
			sampleWriteIndex = (sampleWriteIndex + 1) % sampleFifo.Length;
			sampleFifo[sampleWriteIndex] = right;
			sampleWriteIndex = (sampleWriteIndex + 1) % sampleFifo.Length;
			sampleCount += 2;
			lastLeftSample = left;
			lastRightSample = right;
		}
	}

	private void EnsureSpaceNoLock(int needed)
	{
		while (sampleCount + needed > sampleFifo.Length)
		{
			if (sampleCount >= 2)
			{
				sampleReadIndex = (sampleReadIndex + 2) % sampleFifo.Length;
				sampleCount -= 2;
			}
			else
			{
				sampleReadIndex = 0;
				sampleWriteIndex = 0;
				sampleCount = 0;
			}
			OverflowDropCount++;
		}
	}

	public int ReadSamples(float[] dest, int offset, int count)
	{
		lock (sampleLock)
		{
			int num = count & -2;
			int val = sampleCount & -2;
			int num2 = Math.Min(num, val);
			for (int i = 0; i < num2; i++)
			{
				dest[offset + i] = sampleFifo[sampleReadIndex];
				sampleReadIndex = (sampleReadIndex + 1) % sampleFifo.Length;
			}
			sampleCount -= num2;
			for (int j = num2; j < num; j += 2)
			{
				dest[offset + j] = lastLeftSample;
				if (j + 1 < num)
				{
					dest[offset + j + 1] = lastRightSample;
				}
			}
			if (num2 < num)
			{
				UnderrunCount++;
			}
			return num;
		}
	}

	public void SaveState(BinaryWriter writer)
	{
		writer.Write(frameSequencerStep);
		writer.Write(sampleCycleCounter);
		writer.Write(mmu.NR50);
		writer.Write(mmu.NR51);
		writer.Write(mmu.NR52);
		writer.Write(capacitorL);
		writer.Write(capacitorR);
		ch1.SaveState(writer);
		ch2.SaveState(writer);
		ch3.SaveState(writer);
		ch4.SaveState(writer);
	}

	public void LoadState(BinaryReader reader)
	{
		frameSequencerStep = reader.ReadInt32();
		sampleCycleCounter = reader.ReadDouble();
		mmu.NR50 = reader.ReadByte();
		mmu.NR51 = reader.ReadByte();
		mmu.NR52 = reader.ReadByte();
		capacitorL = reader.ReadDouble();
		capacitorR = reader.ReadDouble();
		ch1.LoadState(reader);
		ch2.LoadState(reader);
		ch3.LoadState(reader);
		ch4.LoadState(reader);
		lock (sampleLock)
		{
			sampleReadIndex = 0;
			sampleWriteIndex = 0;
			sampleCount = 0;
			UnderrunCount = 0;
			OverflowDropCount = 0;
		}
	}
}
internal class CPU
{
	public byte A;

	public byte B;

	public byte C;

	public byte D;

	public byte E;

	public byte H;

	public byte L;

	public byte F;

	public ushort PC;

	public ushort SP;

	public bool zero;

	public bool negative;

	public bool halfCarry;

	public bool carry;

	public bool IME;

	private bool halted;

	private MMU mmu;

	public CPU(MMU mmu)
	{
		A = (B = (C = (D = (E = (H = (L = (F = 0)))))));
		PC = 0;
		SP = 0;
		zero = (negative = (halfCarry = (carry = false)));
		IME = false;
		this.mmu = mmu;
		Console.WriteLine("CPU init");
	}

	public void Reset(bool isGbc = false)
	{
		if (isGbc)
		{
			A = 17;
			F = 128;
			UpdateFlagsFromF();
			B = 0;
			C = 0;
			D = byte.MaxValue;
			E = 86;
			H = 0;
			L = 13;
		}
		else
		{
			A = 1;
			F = 176;
			UpdateFlagsFromF();
			B = 0;
			C = 19;
			D = 0;
			E = 216;
			H = 1;
			L = 77;
		}
		PC = 256;
		SP = 65534;
		IME = false;
		halted = false;
		mmu.JOYP = 207;
		mmu.DIV = (byte)((!isGbc) ? 24 : 0);
		mmu.IF = 225;
		mmu.LCDC = 145;
		mmu.STAT = 133;
		mmu.SCY = 0;
		mmu.SCX = 0;
		mmu.LY = 0;
		mmu.LYC = 0;
		mmu.BGP = 252;
		mmu.Write(65360, 1);
	}

	public int HandleInterrupts()
	{
		byte b = mmu.Read(65295);
		byte b2 = mmu.Read(ushort.MaxValue);
		byte b3 = (byte)(b & b2);
		if (b3 != 0)
		{
			halted = false;
			if (IME)
			{
				IME = false;
				for (int i = 0; i < 5; i++)
				{
					if ((b3 & (1 << i)) != 0)
					{
						mmu.Write(65295, (byte)(b & ~(1 << i)));
						SP--;
						mmu.Write(SP, (byte)((uint)(PC >> 8) & 0xFFu));
						SP--;
						mmu.Write(SP, (byte)(PC & 0xFFu));
						PC = GetInterruptHandlerAddress(i);
						return 20;
					}
				}
			}
			return 0;
		}
		if (halted && b3 != 0)
		{
			halted = false;
		}
		return 0;
	}

	private ushort GetInterruptHandlerAddress(int bit)
	{
		return bit switch
		{
			0 => 64, 
			1 => 72, 
			2 => 80, 
			3 => 88, 
			4 => 96, 
			_ => 0, 
		};
	}

	private void UpdateFFromFlags()
	{
		F = 0;
		if (zero)
		{
			F |= 128;
		}
		if (negative)
		{
			F |= 64;
		}
		if (halfCarry)
		{
			F |= 32;
		}
		if (carry)
		{
			F |= 16;
		}
	}

	public void UpdateFlagsFromF()
	{
		zero = (F & 0x80) != 0;
		negative = (F & 0x40) != 0;
		halfCarry = (F & 0x20) != 0;
		carry = (F & 0x10) != 0;
	}

	private ushort Get16BitReg(string pair)
	{
		return pair.ToLower() switch
		{
			"bc" => (ushort)((B << 8) | C), 
			"de" => (ushort)((D << 8) | E), 
			"hl" => (ushort)((H << 8) | L), 
			"af" => (ushort)((A << 8) | F), 
			_ => 0, 
		};
	}

	private void Load16BitReg(string pair, ushort value)
	{
		switch (pair.ToLower())
		{
		case "bc":
			B = (byte)(value >> 8);
			C = (byte)(value & 0xFFu);
			break;
		case "de":
			D = (byte)(value >> 8);
			E = (byte)(value & 0xFFu);
			break;
		case "hl":
			H = (byte)(value >> 8);
			L = (byte)(value & 0xFFu);
			break;
		case "af":
			A = (byte)(value >> 8);
			F = (byte)(value & 0xFFu);
			break;
		}
	}

	public void Log()
	{
		UpdateFFromFlags();
		ushort pC = PC;
		ushort address = (ushort)(PC + 1);
		ushort address2 = (ushort)(PC + 2);
		ushort address3 = (ushort)(PC + 3);
		Console.WriteLine("A: " + A.ToString("X2") + " F: " + F.ToString("X2") + " B: " + B.ToString("X2") + " C: " + C.ToString("X2") + " D: " + D.ToString("X2") + " E: " + E.ToString("X2") + " H: " + H.ToString("X2") + " L: " + L.ToString("X2") + " SP: " + SP.ToString("X4") + " PC: 00:" + PC.ToString("X4") + " (" + mmu.Read(pC).ToString("X2") + " " + mmu.Read(address).ToString("X2") + " " + mmu.Read(address2).ToString("X2") + " " + mmu.Read(address3).ToString("X2") + ")");
	}

	private byte Fetch()
	{
		return mmu.Read(PC++);
	}

	public int ExecuteInstruction()
	{
		int num = HandleInterrupts();
		if (num > 0)
		{
			return num;
		}
		if (halted)
		{
			return 4;
		}
		byte b = Fetch();
		return b switch
		{
			0 => NOP(), 
			1 => LD_RR_U16(ref B, ref C), 
			2 => LD_ARR_R(ref A, "bc"), 
			3 => INC_RR("bc"), 
			4 => INC_R(ref B), 
			5 => DEC_R(ref B), 
			6 => LD_R_U8(ref B), 
			7 => RLCA(), 
			8 => LD_AU16_SP(), 
			9 => ADD_HL_RR("bc"), 
			10 => LD_R_ARR(ref A, "bc"), 
			11 => DEC_RR("bc"), 
			12 => INC_R(ref C), 
			13 => DEC_R(ref C), 
			14 => LD_R_U8(ref C), 
			15 => RRCA(), 
			16 => STOP(), 
			17 => LD_RR_U16(ref D, ref E), 
			18 => LD_ARR_R(ref A, "de"), 
			19 => INC_RR("de"), 
			20 => INC_R(ref D), 
			21 => DEC_R(ref D), 
			22 => LD_R_U8(ref D), 
			23 => RLA(), 
			24 => JR_CON_I8(flag: true), 
			25 => ADD_HL_RR("de"), 
			26 => LD_R_ARR(ref A, "de"), 
			27 => DEC_RR("de"), 
			28 => INC_R(ref E), 
			29 => DEC_R(ref E), 
			30 => LD_R_U8(ref E), 
			31 => RRA(), 
			32 => JR_CON_I8(!zero), 
			33 => LD_RR_U16(ref H, ref L), 
			34 => LD_AHLI_A(), 
			35 => INC_RR("hl"), 
			36 => INC_R(ref H), 
			37 => DEC_R(ref H), 
			38 => LD_R_U8(ref H), 
			39 => DAA(), 
			40 => JR_CON_I8(zero), 
			41 => ADD_HL_RR("hl"), 
			42 => LD_A_AHLI(), 
			43 => DEC_RR("hl"), 
			44 => INC_R(ref L), 
			45 => DEC_R(ref L), 
			46 => LD_R_U8(ref L), 
			47 => CPL(), 
			48 => JR_CON_I8(!carry), 
			49 => LD_SP_U16(), 
			50 => LD_AHLM_A(), 
			51 => INC_SP(), 
			52 => INC_AHL(), 
			53 => DEC_AHL(), 
			54 => LD_AHL_U8(), 
			55 => SCF(), 
			56 => JR_CON_I8(carry), 
			57 => ADD_HL_SP(), 
			58 => LD_A_AHLM(), 
			59 => DEC_SP(), 
			60 => INC_R(ref A), 
			61 => DEC_R(ref A), 
			62 => LD_R_U8(ref A), 
			63 => CCF(), 
			64 => LD_R1_R2(ref B, ref B), 
			65 => LD_R1_R2(ref B, ref C), 
			66 => LD_R1_R2(ref B, ref D), 
			67 => LD_R1_R2(ref B, ref E), 
			68 => LD_R1_R2(ref B, ref H), 
			69 => LD_R1_R2(ref B, ref L), 
			70 => LD_R_ARR(ref B, "hl"), 
			71 => LD_R1_R2(ref B, ref A), 
			72 => LD_R1_R2(ref C, ref B), 
			73 => LD_R1_R2(ref C, ref C), 
			74 => LD_R1_R2(ref C, ref D), 
			75 => LD_R1_R2(ref C, ref E), 
			76 => LD_R1_R2(ref C, ref H), 
			77 => LD_R1_R2(ref C, ref L), 
			78 => LD_R_ARR(ref C, "hl"), 
			79 => LD_R1_R2(ref C, ref A), 
			80 => LD_R1_R2(ref D, ref B), 
			81 => LD_R1_R2(ref D, ref C), 
			82 => LD_R1_R2(ref D, ref D), 
			83 => LD_R1_R2(ref D, ref E), 
			84 => LD_R1_R2(ref D, ref H), 
			85 => LD_R1_R2(ref D, ref L), 
			86 => LD_R_ARR(ref D, "hl"), 
			87 => LD_R1_R2(ref D, ref A), 
			88 => LD_R1_R2(ref E, ref B), 
			89 => LD_R1_R2(ref E, ref C), 
			90 => LD_R1_R2(ref E, ref D), 
			91 => LD_R1_R2(ref E, ref E), 
			92 => LD_R1_R2(ref E, ref H), 
			93 => LD_R1_R2(ref E, ref L), 
			94 => LD_R_ARR(ref E, "hl"), 
			95 => LD_R1_R2(ref E, ref A), 
			96 => LD_R1_R2(ref H, ref B), 
			97 => LD_R1_R2(ref H, ref C), 
			98 => LD_R1_R2(ref H, ref D), 
			99 => LD_R1_R2(ref H, ref E), 
			100 => LD_R1_R2(ref H, ref H), 
			101 => LD_R1_R2(ref H, ref L), 
			102 => LD_R_ARR(ref H, "hl"), 
			103 => LD_R1_R2(ref H, ref A), 
			104 => LD_R1_R2(ref L, ref B), 
			105 => LD_R1_R2(ref L, ref C), 
			106 => LD_R1_R2(ref L, ref D), 
			107 => LD_R1_R2(ref L, ref E), 
			108 => LD_R1_R2(ref L, ref H), 
			109 => LD_R1_R2(ref L, ref L), 
			110 => LD_R_ARR(ref L, "hl"), 
			111 => LD_R1_R2(ref L, ref A), 
			112 => LD_ARR_R(ref B, "hl"), 
			113 => LD_ARR_R(ref C, "hl"), 
			114 => LD_ARR_R(ref D, "hl"), 
			115 => LD_ARR_R(ref E, "hl"), 
			116 => LD_ARR_R(ref H, "hl"), 
			117 => LD_ARR_R(ref L, "hl"), 
			118 => HALT(), 
			119 => LD_ARR_R(ref A, "hl"), 
			120 => LD_R1_R2(ref A, ref B), 
			121 => LD_R1_R2(ref A, ref C), 
			122 => LD_R1_R2(ref A, ref D), 
			123 => LD_R1_R2(ref A, ref E), 
			124 => LD_R1_R2(ref A, ref H), 
			125 => LD_R1_R2(ref A, ref L), 
			126 => LD_R_ARR(ref A, "hl"), 
			127 => LD_R1_R2(ref A, ref A), 
			128 => ADD_A_R(ref B), 
			129 => ADD_A_R(ref C), 
			130 => ADD_A_R(ref D), 
			131 => ADD_A_R(ref E), 
			132 => ADD_A_R(ref H), 
			133 => ADD_A_R(ref L), 
			134 => ADD_A_ARR("hl"), 
			135 => ADD_A_R(ref A), 
			136 => ADC_A_R(ref B), 
			137 => ADC_A_R(ref C), 
			138 => ADC_A_R(ref D), 
			139 => ADC_A_R(ref E), 
			140 => ADC_A_R(ref H), 
			141 => ADC_A_R(ref L), 
			142 => ADC_A_ARR("hl"), 
			143 => ADC_A_R(ref A), 
			144 => SUB_A_R(ref B), 
			145 => SUB_A_R(ref C), 
			146 => SUB_A_R(ref D), 
			147 => SUB_A_R(ref E), 
			148 => SUB_A_R(ref H), 
			149 => SUB_A_R(ref L), 
			150 => SUB_A_ARR("hl"), 
			151 => SUB_A_R(ref A), 
			152 => SBC_A_R(ref B), 
			153 => SBC_A_R(ref C), 
			154 => SBC_A_R(ref D), 
			155 => SBC_A_R(ref E), 
			156 => SBC_A_R(ref H), 
			157 => SBC_A_R(ref L), 
			158 => SBC_A_ARR("hl"), 
			159 => SBC_A_R(ref A), 
			160 => AND_A_R(ref B), 
			161 => AND_A_R(ref C), 
			162 => AND_A_R(ref D), 
			163 => AND_A_R(ref E), 
			164 => AND_A_R(ref H), 
			165 => AND_A_R(ref L), 
			166 => AND_A_ARR("hl"), 
			167 => AND_A_R(ref A), 
			168 => XOR_A_R(ref B), 
			169 => XOR_A_R(ref C), 
			170 => XOR_A_R(ref D), 
			171 => XOR_A_R(ref E), 
			172 => XOR_A_R(ref H), 
			173 => XOR_A_R(ref L), 
			174 => XOR_A_ARR("hl"), 
			175 => XOR_A_R(ref A), 
			176 => OR_A_R(ref B), 
			177 => OR_A_R(ref C), 
			178 => OR_A_R(ref D), 
			179 => OR_A_R(ref E), 
			180 => OR_A_R(ref H), 
			181 => OR_A_R(ref L), 
			182 => OR_A_ARR("hl"), 
			183 => OR_A_R(ref A), 
			184 => CP_A_R(ref B), 
			185 => CP_A_R(ref C), 
			186 => CP_A_R(ref D), 
			187 => CP_A_R(ref E), 
			188 => CP_A_R(ref H), 
			189 => CP_A_R(ref L), 
			190 => CP_A_ARR("hl"), 
			191 => CP_A_R(ref A), 
			192 => RET_CON(!zero), 
			193 => POP_RR("bc"), 
			194 => JP_CON_U16(!zero), 
			195 => JP_CON_U16(flag: true), 
			196 => CALL_CON_U16(!zero), 
			197 => PUSH_RR("bc"), 
			198 => ADD_A_U8(), 
			199 => RST(0), 
			200 => RET_CON(zero), 
			201 => RET(), 
			202 => JP_CON_U16(zero), 
			203 => ExecuteCB(), 
			204 => CALL_CON_U16(zero), 
			205 => CALL_U16(), 
			206 => ADC_A_U8(), 
			207 => RST(8), 
			208 => RET_CON(!carry), 
			209 => POP_RR("de"), 
			210 => JP_CON_U16(!carry), 
			211 => DMG_EXIT(b), 
			212 => CALL_CON_U16(!carry), 
			213 => PUSH_RR("de"), 
			214 => SUB_A_U8(), 
			215 => RST(16), 
			216 => RET_CON(carry), 
			217 => RETI(), 
			218 => JP_CON_U16(carry), 
			219 => DMG_EXIT(b), 
			220 => CALL_CON_U16(carry), 
			221 => DMG_EXIT(b), 
			222 => SBC_A_U8(), 
			223 => RST(24), 
			224 => LD_FF00_U8_A(), 
			225 => POP_RR("hl"), 
			226 => LD_FF00_C_A(), 
			227 => DMG_EXIT(b), 
			228 => DMG_EXIT(b), 
			229 => PUSH_RR("hl"), 
			230 => AND_A_U8(), 
			231 => RST(32), 
			232 => ADD_SP_I8(), 
			233 => JP_HL(), 
			234 => LD_AU16_A(), 
			235 => DMG_EXIT(b), 
			236 => DMG_EXIT(b), 
			237 => DMG_EXIT(b), 
			238 => XOR_A_U8(), 
			239 => RST(40), 
			240 => LD_A_FF00_U8(), 
			241 => POP_AF(), 
			242 => LD_A_FF00_C(), 
			243 => DI(), 
			244 => DMG_EXIT(b), 
			245 => PUSH_RR("af"), 
			246 => OR_A_U8(), 
			247 => RST(48), 
			248 => LD_HL_SP_I8(), 
			249 => LD_SP_HL(), 
			250 => LD_A_AU16(), 
			251 => EI(), 
			252 => DMG_EXIT(b), 
			253 => DMG_EXIT(b), 
			254 => CP_A_U8(), 
			_ => RST(56), 
		};
	}

	public int ExecuteCB()
	{
		return Fetch() switch
		{
			0 => RLC_R(ref B), 
			1 => RLC_R(ref C), 
			2 => RLC_R(ref D), 
			3 => RLC_R(ref E), 
			4 => RLC_R(ref H), 
			5 => RLC_R(ref L), 
			6 => RLC_AHL(), 
			7 => RLC_R(ref A), 
			8 => RRC_R(ref B), 
			9 => RRC_R(ref C), 
			10 => RRC_R(ref D), 
			11 => RRC_R(ref E), 
			12 => RRC_R(ref H), 
			13 => RRC_R(ref L), 
			14 => RRC_AHL(), 
			15 => RRC_R(ref A), 
			16 => RL_R(ref B), 
			17 => RL_R(ref C), 
			18 => RL_R(ref D), 
			19 => RL_R(ref E), 
			20 => RL_R(ref H), 
			21 => RL_R(ref L), 
			22 => RL_AHL(), 
			23 => RL_R(ref A), 
			24 => RR_R(ref B), 
			25 => RR_R(ref C), 
			26 => RR_R(ref D), 
			27 => RR_R(ref E), 
			28 => RR_R(ref H), 
			29 => RR_R(ref L), 
			30 => RR_AHL(), 
			31 => RR_R(ref A), 
			32 => SLA_R(ref B), 
			33 => SLA_R(ref C), 
			34 => SLA_R(ref D), 
			35 => SLA_R(ref E), 
			36 => SLA_R(ref H), 
			37 => SLA_R(ref L), 
			38 => SLA_AHL(), 
			39 => SLA_R(ref A), 
			40 => SRA_R(ref B), 
			41 => SRA_R(ref C), 
			42 => SRA_R(ref D), 
			43 => SRA_R(ref E), 
			44 => SRA_R(ref H), 
			45 => SRA_R(ref L), 
			46 => SRA_AHL(), 
			47 => SRA_R(ref A), 
			48 => SWAP_R(ref B), 
			49 => SWAP_R(ref C), 
			50 => SWAP_R(ref D), 
			51 => SWAP_R(ref E), 
			52 => SWAP_R(ref H), 
			53 => SWAP_R(ref L), 
			54 => SWAP_AHL(), 
			55 => SWAP_R(ref A), 
			56 => SRL_R(ref B), 
			57 => SRL_R(ref C), 
			58 => SRL_R(ref D), 
			59 => SRL_R(ref E), 
			60 => SRL_R(ref H), 
			61 => SRL_R(ref L), 
			62 => SRL_AHL(), 
			63 => SRL_R(ref A), 
			64 => BIT_N_R(0, ref B), 
			65 => BIT_N_R(0, ref C), 
			66 => BIT_N_R(0, ref D), 
			67 => BIT_N_R(0, ref E), 
			68 => BIT_N_R(0, ref H), 
			69 => BIT_N_R(0, ref L), 
			70 => BIT_N_AHL(0), 
			71 => BIT_N_R(0, ref A), 
			72 => BIT_N_R(1, ref B), 
			73 => BIT_N_R(1, ref C), 
			74 => BIT_N_R(1, ref D), 
			75 => BIT_N_R(1, ref E), 
			76 => BIT_N_R(1, ref H), 
			77 => BIT_N_R(1, ref L), 
			78 => BIT_N_AHL(1), 
			79 => BIT_N_R(1, ref A), 
			80 => BIT_N_R(2, ref B), 
			81 => BIT_N_R(2, ref C), 
			82 => BIT_N_R(2, ref D), 
			83 => BIT_N_R(2, ref E), 
			84 => BIT_N_R(2, ref H), 
			85 => BIT_N_R(2, ref L), 
			86 => BIT_N_AHL(2), 
			87 => BIT_N_R(2, ref A), 
			88 => BIT_N_R(3, ref B), 
			89 => BIT_N_R(3, ref C), 
			90 => BIT_N_R(3, ref D), 
			91 => BIT_N_R(3, ref E), 
			92 => BIT_N_R(3, ref H), 
			93 => BIT_N_R(3, ref L), 
			94 => BIT_N_AHL(3), 
			95 => BIT_N_R(3, ref A), 
			96 => BIT_N_R(4, ref B), 
			97 => BIT_N_R(4, ref C), 
			98 => BIT_N_R(4, ref D), 
			99 => BIT_N_R(4, ref E), 
			100 => BIT_N_R(4, ref H), 
			101 => BIT_N_R(4, ref L), 
			102 => BIT_N_AHL(4), 
			103 => BIT_N_R(4, ref A), 
			104 => BIT_N_R(5, ref B), 
			105 => BIT_N_R(5, ref C), 
			106 => BIT_N_R(5, ref D), 
			107 => BIT_N_R(5, ref E), 
			108 => BIT_N_R(5, ref H), 
			109 => BIT_N_R(5, ref L), 
			110 => BIT_N_AHL(5), 
			111 => BIT_N_R(5, ref A), 
			112 => BIT_N_R(6, ref B), 
			113 => BIT_N_R(6, ref C), 
			114 => BIT_N_R(6, ref D), 
			115 => BIT_N_R(6, ref E), 
			116 => BIT_N_R(6, ref H), 
			117 => BIT_N_R(6, ref L), 
			118 => BIT_N_AHL(6), 
			119 => BIT_N_R(6, ref A), 
			120 => BIT_N_R(7, ref B), 
			121 => BIT_N_R(7, ref C), 
			122 => BIT_N_R(7, ref D), 
			123 => BIT_N_R(7, ref E), 
			124 => BIT_N_R(7, ref H), 
			125 => BIT_N_R(7, ref L), 
			126 => BIT_N_AHL(7), 
			127 => BIT_N_R(7, ref A), 
			128 => RES_N_R(0, ref B), 
			129 => RES_N_R(0, ref C), 
			130 => RES_N_R(0, ref D), 
			131 => RES_N_R(0, ref E), 
			132 => RES_N_R(0, ref H), 
			133 => RES_N_R(0, ref L), 
			134 => RES_N_AHL(0), 
			135 => RES_N_R(0, ref A), 
			136 => RES_N_R(1, ref B), 
			137 => RES_N_R(1, ref C), 
			138 => RES_N_R(1, ref D), 
			139 => RES_N_R(1, ref E), 
			140 => RES_N_R(1, ref H), 
			141 => RES_N_R(1, ref L), 
			142 => RES_N_AHL(1), 
			143 => RES_N_R(1, ref A), 
			144 => RES_N_R(2, ref B), 
			145 => RES_N_R(2, ref C), 
			146 => RES_N_R(2, ref D), 
			147 => RES_N_R(2, ref E), 
			148 => RES_N_R(2, ref H), 
			149 => RES_N_R(2, ref L), 
			150 => RES_N_AHL(2), 
			151 => RES_N_R(2, ref A), 
			152 => RES_N_R(3, ref B), 
			153 => RES_N_R(3, ref C), 
			154 => RES_N_R(3, ref D), 
			155 => RES_N_R(3, ref E), 
			156 => RES_N_R(3, ref H), 
			157 => RES_N_R(3, ref L), 
			158 => RES_N_AHL(3), 
			159 => RES_N_R(3, ref A), 
			160 => RES_N_R(4, ref B), 
			161 => RES_N_R(4, ref C), 
			162 => RES_N_R(4, ref D), 
			163 => RES_N_R(4, ref E), 
			164 => RES_N_R(4, ref H), 
			165 => RES_N_R(4, ref L), 
			166 => RES_N_AHL(4), 
			167 => RES_N_R(4, ref A), 
			168 => RES_N_R(5, ref B), 
			169 => RES_N_R(5, ref C), 
			170 => RES_N_R(5, ref D), 
			171 => RES_N_R(5, ref E), 
			172 => RES_N_R(5, ref H), 
			173 => RES_N_R(5, ref L), 
			174 => RES_N_AHL(5), 
			175 => RES_N_R(5, ref A), 
			176 => RES_N_R(6, ref B), 
			177 => RES_N_R(6, ref C), 
			178 => RES_N_R(6, ref D), 
			179 => RES_N_R(6, ref E), 
			180 => RES_N_R(6, ref H), 
			181 => RES_N_R(6, ref L), 
			182 => RES_N_AHL(6), 
			183 => RES_N_R(6, ref A), 
			184 => RES_N_R(7, ref B), 
			185 => RES_N_R(7, ref C), 
			186 => RES_N_R(7, ref D), 
			187 => RES_N_R(7, ref E), 
			188 => RES_N_R(7, ref H), 
			189 => RES_N_R(7, ref L), 
			190 => RES_N_AHL(7), 
			191 => RES_N_R(7, ref A), 
			192 => SET_N_R(0, ref B), 
			193 => SET_N_R(0, ref C), 
			194 => SET_N_R(0, ref D), 
			195 => SET_N_R(0, ref E), 
			196 => SET_N_R(0, ref H), 
			197 => SET_N_R(0, ref L), 
			198 => SET_N_AHL(0), 
			199 => SET_N_R(0, ref A), 
			200 => SET_N_R(1, ref B), 
			201 => SET_N_R(1, ref C), 
			202 => SET_N_R(1, ref D), 
			203 => SET_N_R(1, ref E), 
			204 => SET_N_R(1, ref H), 
			205 => SET_N_R(1, ref L), 
			206 => SET_N_AHL(1), 
			207 => SET_N_R(1, ref A), 
			208 => SET_N_R(2, ref B), 
			209 => SET_N_R(2, ref C), 
			210 => SET_N_R(2, ref D), 
			211 => SET_N_R(2, ref E), 
			212 => SET_N_R(2, ref H), 
			213 => SET_N_R(2, ref L), 
			214 => SET_N_AHL(2), 
			215 => SET_N_R(2, ref A), 
			216 => SET_N_R(3, ref B), 
			217 => SET_N_R(3, ref C), 
			218 => SET_N_R(3, ref D), 
			219 => SET_N_R(3, ref E), 
			220 => SET_N_R(3, ref H), 
			221 => SET_N_R(3, ref L), 
			222 => SET_N_AHL(3), 
			223 => SET_N_R(3, ref A), 
			224 => SET_N_R(4, ref B), 
			225 => SET_N_R(4, ref C), 
			226 => SET_N_R(4, ref D), 
			227 => SET_N_R(4, ref E), 
			228 => SET_N_R(4, ref H), 
			229 => SET_N_R(4, ref L), 
			230 => SET_N_AHL(4), 
			231 => SET_N_R(4, ref A), 
			232 => SET_N_R(5, ref B), 
			233 => SET_N_R(5, ref C), 
			234 => SET_N_R(5, ref D), 
			235 => SET_N_R(5, ref E), 
			236 => SET_N_R(5, ref H), 
			237 => SET_N_R(5, ref L), 
			238 => SET_N_AHL(5), 
			239 => SET_N_R(5, ref A), 
			240 => SET_N_R(6, ref B), 
			241 => SET_N_R(6, ref C), 
			242 => SET_N_R(6, ref D), 
			243 => SET_N_R(6, ref E), 
			244 => SET_N_R(6, ref H), 
			245 => SET_N_R(6, ref L), 
			246 => SET_N_AHL(6), 
			247 => SET_N_R(6, ref A), 
			248 => SET_N_R(7, ref B), 
			249 => SET_N_R(7, ref C), 
			250 => SET_N_R(7, ref D), 
			251 => SET_N_R(7, ref E), 
			252 => SET_N_R(7, ref H), 
			253 => SET_N_R(7, ref L), 
			254 => SET_N_AHL(7), 
			_ => SET_N_R(7, ref A), 
		};
	}

	private int LD_ARR_R(ref byte r, string regPair)
	{
		ushort address = Get16BitReg(regPair);
		mmu.Write(address, r);
		return 8;
	}

	private int LD_AHLM_A()
	{
		ushort num = Get16BitReg("hl");
		mmu.Write(num, A);
		num--;
		Load16BitReg("hl", num);
		return 8;
	}

	private int LD_AHLI_A()
	{
		ushort num = Get16BitReg("hl");
		mmu.Write(num, A);
		num++;
		Load16BitReg("hl", num);
		return 8;
	}

	private int LD_R_U8(ref byte r)
	{
		byte b = Fetch();
		r = b;
		return 8;
	}

	private int LD_AHL_U8()
	{
		ushort address = Get16BitReg("hl");
		byte value = Fetch();
		mmu.Write(address, value);
		return 12;
	}

	private int LD_R_ARR(ref byte r, string regPair)
	{
		ushort address = Get16BitReg(regPair);
		r = mmu.Read(address);
		return 8;
	}

	private int LD_A_AHLM()
	{
		ushort num = Get16BitReg("hl");
		A = mmu.Read(num);
		num--;
		Load16BitReg("hl", num);
		return 8;
	}

	private int LD_A_AHLI()
	{
		ushort num = Get16BitReg("hl");
		A = mmu.Read(num);
		num++;
		Load16BitReg("hl", num);
		return 8;
	}

	private int LD_R1_R2(ref byte r1, ref byte r2)
	{
		r1 = r2;
		return 4;
	}

	private int LD_FF00_U8_A()
	{
		byte b = Fetch();
		ushort address = (ushort)(65280 + b);
		mmu.Write(address, A);
		return 12;
	}

	private int LD_A_FF00_U8()
	{
		byte b = Fetch();
		ushort address = (ushort)(65280 + b);
		A = mmu.Read(address);
		return 12;
	}

	private int LD_FF00_C_A()
	{
		ushort address = (ushort)(65280 + C);
		mmu.Write(address, A);
		return 8;
	}

	private int LD_A_FF00_C()
	{
		ushort address = (ushort)(65280 + C);
		A = mmu.Read(address);
		return 8;
	}

	private int LD_AU16_A()
	{
		byte b = Fetch();
		byte b2 = Fetch();
		ushort address = (ushort)((b2 << 8) | b);
		mmu.Write(address, A);
		return 16;
	}

	private int LD_A_AU16()
	{
		byte b = Fetch();
		byte b2 = Fetch();
		ushort address = (ushort)((b2 << 8) | b);
		A = mmu.Read(address);
		return 16;
	}

	private int LD_SP_U16()
	{
		byte b = Fetch();
		byte b2 = Fetch();
		ushort sP = (ushort)((b2 << 8) | b);
		SP = sP;
		return 12;
	}

	private int LD_RR_U16(ref byte r1, ref byte r2)
	{
		r2 = Fetch();
		r1 = Fetch();
		return 12;
	}

	private int LD_AU16_SP()
	{
		byte b = Fetch();
		byte b2 = Fetch();
		ushort num = (ushort)((b2 << 8) | b);
		byte value = (byte)(SP & 0xFFu);
		byte value2 = (byte)((uint)(SP >> 8) & 0xFFu);
		mmu.Write(num, value);
		mmu.Write((ushort)(num + 1), value2);
		return 20;
	}

	private int PUSH_RR(string regPair)
	{
		ushort num = Get16BitReg(regPair);
		SP--;
		mmu.Write(SP, (byte)(num >> 8));
		SP--;
		mmu.Write(SP, (byte)(num & 0xFFu));
		return 16;
	}

	private int POP_RR(string regPair)
	{
		byte b = mmu.Read(SP);
		SP++;
		byte b2 = mmu.Read(SP);
		ushort value = (ushort)((b2 << 8) | b);
		Load16BitReg(regPair, value);
		SP++;
		return 12;
	}

	private int POP_AF()
	{
		byte b = mmu.Read(SP);
		SP++;
		byte a = mmu.Read(SP);
		A = a;
		F = (byte)(b & 0xF0u);
		UpdateFlagsFromF();
		SP++;
		return 12;
	}

	private int LD_SP_HL()
	{
		SP = Get16BitReg("hl");
		return 8;
	}

	private int INC_R(ref byte r)
	{
		int num = r + 1;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = (num & 0xF) == 0;
		UpdateFFromFlags();
		r = (byte)num;
		return 4;
	}

	private int INC_AHL()
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		int num = b + 1;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = (num & 0xF) == 0;
		UpdateFFromFlags();
		mmu.Write(address, (byte)num);
		return 12;
	}

	private int DEC_R(ref byte r)
	{
		int num = r - 1;
		zero = (num & 0xFF) == 0;
		negative = true;
		halfCarry = (r & 0xF) == 0;
		UpdateFFromFlags();
		r = (byte)num;
		return 4;
	}

	private int DEC_AHL()
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		int num = b - 1;
		zero = (num & 0xFF) == 0;
		negative = true;
		halfCarry = (b & 0xF) == 0;
		UpdateFFromFlags();
		mmu.Write(address, (byte)num);
		return 12;
	}

	private int ADD_A_R(ref byte r)
	{
		int num = A + r;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = (A & 0xF) + (r & 0xF) > 15;
		carry = num > 255;
		UpdateFFromFlags();
		A = (byte)((uint)num & 0xFFu);
		return 4;
	}

	private int ADD_A_ARR(string regPair)
	{
		ushort address = Get16BitReg(regPair);
		byte b = mmu.Read(address);
		int num = A + b;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = (A & 0xF) + (b & 0xF) > 15;
		carry = num > 255;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int ADD_A_U8()
	{
		byte b = Fetch();
		int num = A + b;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = (A & 0xF) + (b & 0xF) > 15;
		carry = num > 255;
		UpdateFFromFlags();
		A = (byte)((uint)num & 0xFFu);
		return 8;
	}

	private int ADC_A_R(ref byte r)
	{
		int num = A + r + (carry ? 1 : 0);
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = (A & 0xF) + (r & 0xF) + (carry ? 1 : 0) > 15;
		carry = num > 255;
		UpdateFFromFlags();
		A = (byte)((uint)num & 0xFFu);
		return 4;
	}

	private int ADC_A_ARR(string regPair)
	{
		ushort address = Get16BitReg(regPair);
		byte b = mmu.Read(address);
		int num = A + b + (carry ? 1 : 0);
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = (A & 0xF) + (b & 0xF) + (carry ? 1 : 0) > 15;
		carry = num > 255;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int ADC_A_U8()
	{
		byte b = Fetch();
		int num = A + b + (carry ? 1 : 0);
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = (A & 0xF) + (b & 0xF) + (carry ? 1 : 0) > 15;
		carry = num > 255;
		UpdateFFromFlags();
		A = (byte)((uint)num & 0xFFu);
		return 8;
	}

	private int SUB_A_R(ref byte r)
	{
		int num = A - r;
		zero = num == 0;
		negative = true;
		halfCarry = (A & 0xF) < (r & 0xF);
		carry = A < r;
		UpdateFFromFlags();
		A = (byte)num;
		return 4;
	}

	private int SUB_A_ARR(string regPair)
	{
		ushort address = Get16BitReg(regPair);
		byte b = mmu.Read(address);
		int num = A - b;
		zero = (num & 0xFF) == 0;
		negative = true;
		halfCarry = (A & 0xF) < (b & 0xF);
		carry = num < 0;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int SUB_A_U8()
	{
		byte b = Fetch();
		int num = A - b;
		zero = num == 0;
		negative = true;
		halfCarry = (A & 0xF) < (b & 0xF);
		carry = A < b;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int SBC_A_R(ref byte r)
	{
		int num = A - r - (carry ? 1 : 0);
		zero = (num & 0xFF) == 0;
		negative = true;
		halfCarry = (A & 0xF) - (r & 0xF) - (carry ? 1 : 0) < 0;
		carry = num < 0;
		UpdateFFromFlags();
		A = (byte)num;
		return 4;
	}

	private int SBC_A_ARR(string regPair)
	{
		ushort address = Get16BitReg(regPair);
		byte b = mmu.Read(address);
		int num = A - b - (carry ? 1 : 0);
		zero = (num & 0xFF) == 0;
		negative = true;
		halfCarry = (A & 0xF) - (b & 0xF) - (carry ? 1 : 0) < 0;
		carry = num < 0;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int SBC_A_U8()
	{
		byte b = Fetch();
		int num = A - b - (carry ? 1 : 0);
		zero = (num & 0xFF) == 0;
		negative = true;
		halfCarry = (A & 0xF) - (b & 0xF) - (carry ? 1 : 0) < 0;
		carry = num < 0;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int AND_A_R(ref byte r)
	{
		int num = A & r;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = true;
		carry = false;
		UpdateFFromFlags();
		A = (byte)num;
		return 4;
	}

	private int AND_A_ARR(string regPair)
	{
		ushort address = Get16BitReg(regPair);
		byte b = mmu.Read(address);
		int num = A & b;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = true;
		carry = false;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int AND_A_U8()
	{
		byte b = Fetch();
		int num = A & b;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = true;
		carry = false;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int XOR_A_R(ref byte r)
	{
		int num = A ^ r;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = false;
		carry = false;
		UpdateFFromFlags();
		A = (byte)num;
		return 4;
	}

	private int XOR_A_ARR(string regPair)
	{
		ushort address = Get16BitReg(regPair);
		byte b = mmu.Read(address);
		int num = A ^ b;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = false;
		carry = false;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int XOR_A_U8()
	{
		byte b = Fetch();
		int num = A ^ b;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = false;
		carry = false;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int OR_A_R(ref byte r)
	{
		int num = A | r;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = false;
		carry = false;
		UpdateFFromFlags();
		A = (byte)num;
		return 4;
	}

	private int OR_A_ARR(string regPair)
	{
		ushort address = Get16BitReg(regPair);
		byte b = mmu.Read(address);
		int num = A | b;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = false;
		carry = false;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int OR_A_U8()
	{
		byte b = Fetch();
		int num = A | b;
		zero = (num & 0xFF) == 0;
		negative = false;
		halfCarry = false;
		carry = false;
		UpdateFFromFlags();
		A = (byte)num;
		return 8;
	}

	private int CP_A_R(ref byte r)
	{
		int num = A - r;
		zero = (num & 0xFF) == 0;
		negative = true;
		halfCarry = (A & 0xF) < (r & 0xF);
		carry = num < 0;
		UpdateFFromFlags();
		return 4;
	}

	private int CP_A_ARR(string regPair)
	{
		ushort address = Get16BitReg(regPair);
		byte b = mmu.Read(address);
		int num = A - b;
		zero = num == 0;
		negative = true;
		halfCarry = (A & 0xF) < (b & 0xF);
		carry = A < b;
		UpdateFFromFlags();
		return 8;
	}

	private int CP_A_U8()
	{
		byte b = Fetch();
		int num = A - b;
		zero = num == 0;
		negative = true;
		halfCarry = (A & 0xF) < (b & 0xF);
		carry = A < b;
		UpdateFFromFlags();
		return 8;
	}

	private int CPL()
	{
		A = (byte)(~A);
		negative = true;
		halfCarry = true;
		UpdateFFromFlags();
		return 4;
	}

	private int SCF()
	{
		carry = true;
		negative = false;
		halfCarry = false;
		UpdateFFromFlags();
		return 4;
	}

	private int CCF()
	{
		carry = !carry;
		negative = false;
		halfCarry = false;
		UpdateFFromFlags();
		return 4;
	}

	private int DAA()
	{
		byte b = (byte)(carry ? 96u : 0u);
		if (halfCarry)
		{
			b = (byte)(b | 6u);
		}
		if (negative)
		{
			A -= b;
		}
		else
		{
			if ((A & 0xF) > 9)
			{
				b = (byte)(b | 6u);
			}
			if (A > 153)
			{
				b = (byte)(b | 0x60u);
			}
			A += b;
		}
		zero = A == 0;
		halfCarry = false;
		carry = b >= 96;
		UpdateFFromFlags();
		return 4;
	}

	private int INC_RR(string regPair)
	{
		ushort num = Get16BitReg(regPair);
		num++;
		Load16BitReg(regPair, num);
		return 8;
	}

	private int INC_SP()
	{
		SP++;
		return 8;
	}

	private int DEC_RR(string regPair)
	{
		ushort num = Get16BitReg(regPair);
		num--;
		Load16BitReg(regPair, num);
		return 8;
	}

	private int DEC_SP()
	{
		SP--;
		return 8;
	}

	private int ADD_HL_RR(string regPair)
	{
		ushort num = Get16BitReg("hl");
		ushort num2 = Get16BitReg(regPair);
		int num3 = num + num2;
		negative = false;
		halfCarry = (num & 0xFFF) + (num2 & 0xFFF) > 4095;
		carry = num3 > 65535;
		UpdateFFromFlags();
		Load16BitReg("hl", (ushort)num3);
		return 8;
	}

	private int ADD_HL_SP()
	{
		ushort num = Get16BitReg("hl");
		int num2 = num + SP;
		negative = false;
		halfCarry = (num & 0xFFF) + (SP & 0xFFF) > 4095;
		carry = num2 > 65535;
		UpdateFFromFlags();
		Load16BitReg("hl", (ushort)num2);
		return 8;
	}

	private int ADD_SP_I8()
	{
		byte b = (byte)(SP & 0xFFu);
		byte b2 = (byte)((uint)(SP >> 8) & 0xFFu);
		sbyte b3 = (sbyte)Fetch();
		int num = SP + (ushort)b3;
		zero = false;
		negative = false;
		halfCarry = ((SP ^ b3 ^ (num & 0xFFFF)) & 0x10) == 16;
		carry = ((SP ^ b3 ^ (num & 0xFFFF)) & 0x100) == 256;
		UpdateFFromFlags();
		SP = (ushort)num;
		return 16;
	}

	private int LD_HL_SP_I8()
	{
		sbyte b = (sbyte)Fetch();
		int num = SP + b;
		zero = false;
		negative = false;
		halfCarry = ((SP ^ b ^ (num & 0xFFFF)) & 0x10) == 16;
		carry = ((SP ^ b ^ (num & 0xFFFF)) & 0x100) == 256;
		UpdateFFromFlags();
		Load16BitReg("hl", (ushort)num);
		return 12;
	}

	private int RLCA()
	{
		byte b = (byte)(A & 0x80u);
		byte a = (byte)((A << 1) | (b >> 7));
		zero = false;
		negative = false;
		halfCarry = false;
		carry = (A & 0x80) != 0;
		UpdateFFromFlags();
		A = a;
		return 4;
	}

	private int RRCA()
	{
		byte b = (byte)(A & 1u);
		byte a = (byte)((A >> 1) | (b << 7));
		zero = false;
		negative = false;
		halfCarry = false;
		carry = (A & 1) != 0;
		UpdateFFromFlags();
		A = a;
		return 4;
	}

	private int RLA()
	{
		byte b = (byte)(carry ? 1u : 0u);
		byte a = (byte)((A << 1) | b);
		zero = false;
		negative = false;
		halfCarry = false;
		carry = (A & 0x80) != 0;
		UpdateFFromFlags();
		A = a;
		return 4;
	}

	private int RRA()
	{
		byte b = (byte)(carry ? 1u : 0u);
		byte a = (byte)((A >> 1) | (b << 7));
		zero = false;
		negative = false;
		halfCarry = false;
		carry = (A & 1) != 0;
		UpdateFFromFlags();
		A = a;
		return 4;
	}

	private int RLC_R(ref byte r)
	{
		byte b = (byte)(r & 0x80u);
		byte b2 = (byte)((r << 1) | (b >> 7));
		zero = b2 == 0;
		negative = false;
		halfCarry = false;
		carry = (r & 0x80) != 0;
		UpdateFFromFlags();
		r = b2;
		return 8;
	}

	private int RLC_AHL()
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(b & 0x80u);
		byte b3 = (byte)((b << 1) | (b2 >> 7));
		zero = b3 == 0;
		negative = false;
		halfCarry = false;
		carry = (b & 0x80) != 0;
		UpdateFFromFlags();
		mmu.Write(address, b3);
		return 16;
	}

	private int RRC_R(ref byte r)
	{
		byte b = (byte)(r & 1u);
		byte b2 = (byte)((r >> 1) | (b << 7));
		zero = b2 == 0;
		negative = false;
		halfCarry = false;
		carry = (r & 1) != 0;
		UpdateFFromFlags();
		r = b2;
		return 8;
	}

	private int RRC_AHL()
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(b & 1u);
		byte b3 = (byte)((b >> 1) | (b2 << 7));
		zero = b3 == 0;
		negative = false;
		halfCarry = false;
		carry = (b & 1) != 0;
		UpdateFFromFlags();
		mmu.Write(address, b3);
		return 16;
	}

	private int RL_R(ref byte r)
	{
		byte b = (byte)(carry ? 1u : 0u);
		byte b2 = (byte)((r << 1) | b);
		zero = b2 == 0;
		negative = false;
		halfCarry = false;
		carry = (r & 0x80) != 0;
		UpdateFFromFlags();
		r = b2;
		return 8;
	}

	private int RL_AHL()
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(carry ? 1u : 0u);
		byte b3 = (byte)((b << 1) | b2);
		zero = b3 == 0;
		negative = false;
		halfCarry = false;
		carry = (b & 0x80) != 0;
		UpdateFFromFlags();
		mmu.Write(address, b3);
		return 16;
	}

	private int RR_R(ref byte r)
	{
		byte b = (byte)(carry ? 1u : 0u);
		byte b2 = (byte)((r >> 1) | (b << 7));
		zero = b2 == 0;
		negative = false;
		halfCarry = false;
		carry = (r & 1) != 0;
		UpdateFFromFlags();
		r = b2;
		return 8;
	}

	private int RR_AHL()
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(carry ? 1u : 0u);
		byte b3 = (byte)((b >> 1) | (b2 << 7));
		zero = b3 == 0;
		negative = false;
		halfCarry = false;
		carry = (b & 1) != 0;
		UpdateFFromFlags();
		mmu.Write(address, b3);
		return 16;
	}

	private int SLA_R(ref byte r)
	{
		byte b = (byte)(r & 0x80u);
		byte b2 = (byte)(r << 1);
		zero = b2 == 0;
		negative = false;
		halfCarry = false;
		carry = b != 0;
		UpdateFFromFlags();
		r = b2;
		return 8;
	}

	private int SLA_AHL()
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(b & 0x80u);
		byte b3 = (byte)(b << 1);
		zero = b3 == 0;
		negative = false;
		halfCarry = false;
		carry = b2 != 0;
		UpdateFFromFlags();
		mmu.Write(address, b3);
		return 16;
	}

	private int SRA_R(ref byte r)
	{
		byte b = (byte)(r & 0x80u);
		byte b2 = (byte)(r & 1u);
		byte b3 = (byte)((r >> 1) | b);
		zero = b3 == 0;
		negative = false;
		halfCarry = false;
		carry = b2 != 0;
		UpdateFFromFlags();
		r = b3;
		return 8;
	}

	private int SRA_AHL()
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(b & 0x80u);
		byte b3 = (byte)(b & 1u);
		byte b4 = (byte)((b >> 1) | b2);
		zero = b4 == 0;
		negative = false;
		halfCarry = false;
		carry = b3 != 0;
		UpdateFFromFlags();
		mmu.Write(address, b4);
		return 16;
	}

	private int SRL_R(ref byte r)
	{
		byte b = (byte)(r & 1u);
		byte b2 = (byte)(r >> 1);
		zero = b2 == 0;
		negative = false;
		halfCarry = false;
		carry = b != 0;
		UpdateFFromFlags();
		r = b2;
		return 8;
	}

	private int SRL_AHL()
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(b & 1u);
		byte b3 = (byte)(b >> 1);
		zero = b3 == 0;
		negative = false;
		halfCarry = false;
		carry = b2 != 0;
		UpdateFFromFlags();
		mmu.Write(address, b3);
		return 16;
	}

	private int SWAP_R(ref byte r)
	{
		byte b = (byte)(r & 0xF0u);
		byte b2 = (byte)(r & 0xFu);
		byte b3 = (byte)((b2 << 4) | (b >> 4));
		zero = b3 == 0;
		negative = false;
		halfCarry = false;
		carry = false;
		UpdateFFromFlags();
		r = b3;
		return 8;
	}

	private int SWAP_AHL()
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(b & 0xF0u);
		byte b3 = (byte)(b & 0xFu);
		byte b4 = (byte)((b3 << 4) | (b2 >> 4));
		zero = b4 == 0;
		negative = false;
		halfCarry = false;
		carry = false;
		UpdateFFromFlags();
		mmu.Write(address, b4);
		return 16;
	}

	private int BIT_N_R(byte n, ref byte r)
	{
		byte b = (byte)(r & (1 << (int)n));
		zero = b == 0;
		negative = false;
		halfCarry = true;
		UpdateFFromFlags();
		return 8;
	}

	private int BIT_N_AHL(byte n)
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(b & (1 << (int)n));
		zero = b2 == 0;
		negative = false;
		halfCarry = true;
		UpdateFFromFlags();
		return 12;
	}

	private int RES_N_R(byte n, ref byte r)
	{
		byte b = (byte)(~(1 << (int)n));
		r &= b;
		return 8;
	}

	private int RES_N_AHL(byte n)
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(~(1 << (int)n));
		b &= b2;
		mmu.Write(address, b);
		return 16;
	}

	private int SET_N_R(byte n, ref byte r)
	{
		byte b = (byte)(1 << (int)n);
		r |= b;
		return 8;
	}

	private int SET_N_AHL(byte n)
	{
		ushort address = Get16BitReg("hl");
		byte b = mmu.Read(address);
		byte b2 = (byte)(1 << (int)n);
		b |= b2;
		mmu.Write(address, b);
		return 16;
	}

	private int JR_CON_I8(bool flag)
	{
		byte b = Fetch();
		if (flag)
		{
			sbyte b2 = (sbyte)b;
			PC = (ushort)(PC + b2);
			return 12;
		}
		return 8;
	}

	private int CALL_U16()
	{
		byte b = Fetch();
		byte b2 = Fetch();
		ushort pC = (ushort)((b2 << 8) | b);
		ushort pC2 = PC;
		SP--;
		mmu.Write(SP, (byte)(pC2 >> 8));
		SP--;
		mmu.Write(SP, (byte)(pC2 & 0xFFu));
		PC = pC;
		return 24;
	}

	private int CALL_CON_U16(bool flag)
	{
		byte b = Fetch();
		byte b2 = Fetch();
		ushort pC = (ushort)((b2 << 8) | b);
		if (flag)
		{
			ushort pC2 = PC;
			SP--;
			mmu.Write(SP, (byte)(pC2 >> 8));
			SP--;
			mmu.Write(SP, (byte)(pC2 & 0xFFu));
			PC = pC;
			return 24;
		}
		return 12;
	}

	private int RET()
	{
		byte b = mmu.Read(SP);
		SP++;
		byte b2 = mmu.Read(SP);
		SP++;
		ushort pC = (ushort)((b2 << 8) | b);
		PC = pC;
		return 16;
	}

	private int RET_CON(bool flag)
	{
		if (flag)
		{
			byte b = mmu.Read(SP);
			SP++;
			byte b2 = mmu.Read(SP);
			SP++;
			ushort pC = (ushort)((b2 << 8) | b);
			PC = pC;
			return 20;
		}
		return 8;
	}

	private int RETI()
	{
		IME = true;
		byte b = mmu.Read(SP);
		SP++;
		byte b2 = mmu.Read(SP);
		SP++;
		ushort pC = (ushort)((b2 << 8) | b);
		PC = pC;
		return 16;
	}

	private int JP_CON_U16(bool flag)
	{
		byte b = Fetch();
		byte b2 = Fetch();
		ushort pC = (ushort)((b2 << 8) | b);
		if (flag)
		{
			PC = pC;
			return 16;
		}
		return 12;
	}

	private int JP_HL()
	{
		ushort pC = Get16BitReg("hl");
		PC = pC;
		return 4;
	}

	private int RST(ushort vector)
	{
		ushort pC = PC;
		SP--;
		mmu.Write(SP, (byte)(pC >> 8));
		SP--;
		mmu.Write(SP, (byte)(pC & 0xFFu));
		PC = vector;
		return 16;
	}

	private int NOP()
	{
		return 4;
	}

	private int DI()
	{
		IME = false;
		return 4;
	}

	private int EI()
	{
		IME = true;
		return 4;
	}

	private int HALT()
	{
		halted = true;
		return 4;
	}

	private int STOP()
	{
		if (mmu.IsGbc && ((uint)mmu.Read(65357) & (true ? 1u : 0u)) != 0)
		{
			mmu.ExecuteSpeedSwitch();
			return 4;
		}
		halted = true;
		return 4;
	}

	private int DMG_EXIT(byte op)
	{
		Console.WriteLine("Unimplemented Opcode: " + op.ToString("X2") + " , PC: " + (PC - 1).ToString("X4") + " (In opcode switch)");
		Environment.Exit(1);
		return 0;
	}

	public void SaveState(BinaryWriter writer)
	{
		writer.Write(A);
		writer.Write(B);
		writer.Write(C);
		writer.Write(D);
		writer.Write(E);
		writer.Write(H);
		writer.Write(L);
		writer.Write(F);
		writer.Write(PC);
		writer.Write(SP);
		writer.Write(zero);
		writer.Write(negative);
		writer.Write(halfCarry);
		writer.Write(carry);
		writer.Write(IME);
		writer.Write(halted);
	}

	public void LoadState(BinaryReader reader)
	{
		A = reader.ReadByte();
		B = reader.ReadByte();
		C = reader.ReadByte();
		D = reader.ReadByte();
		E = reader.ReadByte();
		H = reader.ReadByte();
		L = reader.ReadByte();
		F = reader.ReadByte();
		PC = reader.ReadUInt16();
		SP = reader.ReadUInt16();
		zero = reader.ReadBoolean();
		negative = reader.ReadBoolean();
		halfCarry = reader.ReadBoolean();
		carry = reader.ReadBoolean();
		IME = reader.ReadBoolean();
		halted = reader.ReadBoolean();
		UpdateFFromFlags();
	}
}
internal class MBC
{
	private byte[] rom;

	public byte[] ramBanks;

	private int romBank = 1;

	private int ramBank = 0;

	private bool ramEnabled = false;

	public int mbcType;

	private int romSize;

	private int ramSize;

	private int romBankCount;

	private int ramBankCount;

	private byte cartType;

	private bool hasRtc;

	private Mbc3Rtc rtc;

	private string rtcSavePath;

	public MBC(byte[] romData)
	{
		rom = romData;
		cartType = romData[327];
		romSize = CalculateRomSize(romData[328]);
		romBankCount = Math.Max(2, rom.Length / 16384);
		byte b = cartType;
		byte b2 = b;
		if ((uint)b2 <= 3u)
		{
			if (b2 != 0)
			{
				if ((uint)(b2 - 1) > 2u)
				{
					goto IL_00ae;
				}
				mbcType = 1;
			}
			else
			{
				mbcType = 0;
			}
		}
		else if ((uint)(b2 - 15) > 4u)
		{
			if ((uint)(b2 - 25) > 5u)
			{
				goto IL_00ae;
			}
			mbcType = 5;
		}
		else
		{
			mbcType = 3;
		}
		goto IL_00c2;
		IL_00ae:
		mbcType = 0;
		Console.WriteLine("Error: Unknown/Unsupported MBC, using MBC0/ROM Only");
		goto IL_00c2;
		IL_00c2:
		hasRtc = cartType == 15 || cartType == 16;
		if (hasRtc)
		{
			rtc = new Mbc3Rtc();
		}
		switch (rom[329])
		{
		case 1:
			ramSize = 2048;
			ramBankCount = 1;
			break;
		case 2:
			ramSize = 8192;
			ramBankCount = 1;
			break;
		case 3:
			ramSize = 32768;
			ramBankCount = 4;
			break;
		case 4:
			ramSize = 131072;
			ramBankCount = 16;
			break;
		case 5:
			ramSize = 65536;
			ramBankCount = 8;
			break;
		default:
			ramSize = 0;
			ramBankCount = 0;
			break;
		}
		ramBanks = new byte[ramSize];
	}

	public void SetRtcSavePath(string path)
	{
		rtcSavePath = path;
	}

	private int CalculateRomSize(byte headerValue)
	{
		return 32768 * (1 << (int)headerValue);
	}

	public string GetTitle()
	{
		byte[] array = new byte[16];
		Array.Copy(rom, 308, array, 0, 16);
		string text = Encoding.ASCII.GetString(array).TrimEnd(new char[1]);
		if (text.Length > 15)
		{
			text = text.Substring(0, 15);
		}
		return "Title: " + text;
	}

	public string GetCartridgeType()
	{
		return "Cartridge Type: " + rom[327] switch
		{
			0 => "MBC0/ROM ONLY", 
			1 => "MBC1", 
			2 => "MBC1+RAM", 
			3 => "MBC1+RAM+BATTERY", 
			5 => "MBC2", 
			6 => "MBC2+BATTERY", 
			8 => "ROM+RAM", 
			9 => "ROM+RAM+BATTERY", 
			11 => "MMM01", 
			12 => "MMM01+RAM", 
			13 => "MMM01+RAM+BATTERY", 
			15 => "MBC3+TIMER+BATTERY", 
			16 => "MBC3+TIMER+RAM+BATTERY", 
			17 => "MBC3", 
			18 => "MBC3+RAM", 
			19 => "MBC3+RAM+BATTERY", 
			25 => "MBC5", 
			26 => "MBC5+RAM", 
			27 => "MBC5+RAM+BATTERY", 
			28 => "MBC5+RUMBLE", 
			29 => "MBC5+RUMBLE+RAM", 
			30 => "MBC5+RUMBLE+RAM+BATTERY", 
			32 => "MBC6", 
			34 => "MBC7+SENSOR+RUMBLE+RAM+BATTERY", 
			252 => "POCKET CAMERA", 
			253 => "BANDAI TAMA5", 
			254 => "HuC3", 
			byte.MaxValue => "HuC1+RAM+BATTERY", 
			_ => "Unknown cartridge type", 
		};
	}

	public string GetRomSize()
	{
		return "ROM Size: " + rom[328] switch
		{
			0 => "32 KiB (2 ROM banks, No Banking)", 
			1 => "64 KiB (4 ROM banks)", 
			2 => "128 KiB (8 ROM banks)", 
			3 => "256 KiB (16 ROM banks)", 
			4 => "512 KiB (32 ROM banks)", 
			5 => "1 MiB (64 ROM banks)", 
			6 => "2 MiB (128 ROM banks)", 
			7 => "4 MiB (256 ROM banks)", 
			8 => "8 MiB (512 ROM banks)", 
			_ => "Unknown ROM size", 
		};
	}

	public string GetRamSize()
	{
		return "RAM Size: " + rom[329] switch
		{
			0 => "No RAM", 
			1 => "Unused (2 KB?)", 
			2 => "8 KiB (1 bank)", 
			3 => "32 KiB (4 banks of 8 KiB each)", 
			4 => "128 KiB (16 banks of 8 KiB each)", 
			5 => "64 KiB (8 banks of 8 KiB each)", 
			_ => "Unknown RAM size", 
		};
	}

	public string GetChecksum()
	{
		return "Checksum: " + rom[333].ToString("X2");
	}

	public byte Read(ushort address)
	{
		if (address < 16384)
		{
			return (address < rom.Length) ? rom[address] : byte.MaxValue;
		}
		if (address < 32768)
		{
			int num = romBank % romBankCount * 16384;
			int num2 = num + (address - 16384);
			return (num2 < rom.Length) ? rom[num2] : byte.MaxValue;
		}
		if (address >= 40960 && address < 49152)
		{
			if (!ramEnabled)
			{
				return byte.MaxValue;
			}
			if (mbcType == 3 && hasRtc && ramBank >= 8 && ramBank <= 12)
			{
				return rtc.ReadSelected();
			}
			if (ramBankCount > 0)
			{
				int num3 = ramBank % ramBankCount * 8192;
				int num4 = num3 + (address - 40960);
				return (num4 < ramBanks.Length) ? ramBanks[num4] : byte.MaxValue;
			}
			return byte.MaxValue;
		}
		return byte.MaxValue;
	}

	public void Write(ushort address, byte value)
	{
		if (address < 8192)
		{
			ramEnabled = (value & 0xF) == 10;
		}
		else if (address < 16384)
		{
			if (mbcType == 1)
			{
				romBank = value & 0x1F;
				if (romBank == 0)
				{
					romBank = 1;
				}
			}
			else if (mbcType == 3)
			{
				romBank = value & 0x7F;
				if (romBank == 0)
				{
					romBank = 1;
				}
			}
			else if (mbcType == 5)
			{
				if (address < 12288)
				{
					romBank = (romBank & 0x100) | value;
				}
				else
				{
					romBank = (romBank & 0xFF) | ((value & 1) << 8);
				}
			}
		}
		else if (address < 24576)
		{
			if (mbcType == 1)
			{
				ramBank = value & 3;
			}
			else if (mbcType == 3 || mbcType == 5)
			{
				ramBank = value & 0xF;
				if (mbcType == 3 && hasRtc && ramBank >= 8 && ramBank <= 12)
				{
					rtc.SelectRegister((byte)ramBank);
				}
			}
		}
		else if (address < 32768)
		{
			if (mbcType == 3 && hasRtc)
			{
				rtc.Latch(value);
			}
		}
		else
		{
			if (address < 40960 || address >= 49152 || !ramEnabled)
			{
				return;
			}
			if (mbcType == 3 && hasRtc && ramBank >= 8 && ramBank <= 12)
			{
				rtc.WriteSelected(value);
			}
			else if (ramBankCount > 0)
			{
				int num = ramBank % ramBankCount * 8192;
				int num2 = num + (address - 40960);
				if (num2 < ramBanks.Length)
				{
					ramBanks[num2] = value;
				}
			}
		}
	}

	public void SaveBatteryData(string ramPath)
	{
		if (mbcType != 0 && ramBanks != null)
		{
			File.WriteAllBytes(ramPath, ramBanks);
		}
		if (!hasRtc || rtc == null || string.IsNullOrEmpty(rtcSavePath))
		{
			return;
		}
		using FileStream output = File.Create(rtcSavePath);
		using BinaryWriter binaryWriter = new BinaryWriter(output);
		binaryWriter.Write(1381253939);
		rtc.Save(binaryWriter);
	}

	public void LoadBatteryData(string ramPath)
	{
		if (File.Exists(ramPath) && mbcType != 0)
		{
			ramBanks = File.ReadAllBytes(ramPath);
		}
		if (hasRtc && rtc != null && !string.IsNullOrEmpty(rtcSavePath) && File.Exists(rtcSavePath))
		{
			using (FileStream input = File.OpenRead(rtcSavePath))
			{
				using BinaryReader binaryReader = new BinaryReader(input);
				int num = binaryReader.ReadInt32();
				if (num == 1381253939)
				{
					rtc.Load(binaryReader);
				}
				return;
			}
		}
		if (hasRtc && rtc != null)
		{
			rtc.SeedFromHostClock();
		}
	}

	public void SaveState(BinaryWriter writer)
	{
		writer.Write(romBank);
		writer.Write(ramBank);
		writer.Write(ramEnabled);
		writer.Write(mbcType);
		writer.Write(romSize);
		writer.Write(ramSize);
		writer.Write(romBankCount);
		writer.Write(ramBankCount);
		if (ramBanks == null)
		{
			writer.Write(-1);
		}
		else
		{
			writer.Write(ramBanks.Length);
			writer.Write(ramBanks);
		}
		writer.Write(cartType);
		writer.Write(hasRtc);
		writer.Write(rtc != null);
		if (rtc != null)
		{
			rtc.Save(writer);
		}
	}

	public void LoadState(BinaryReader reader)
	{
		romBank = reader.ReadInt32();
		ramBank = reader.ReadInt32();
		ramEnabled = reader.ReadBoolean();
		mbcType = reader.ReadInt32();
		romSize = reader.ReadInt32();
		ramSize = reader.ReadInt32();
		romBankCount = reader.ReadInt32();
		ramBankCount = reader.ReadInt32();
		int num = reader.ReadInt32();
		if (num >= 0)
		{
			ramBanks = reader.ReadBytes(num);
		}
		else
		{
			ramBanks = null;
		}
		cartType = reader.ReadByte();
		hasRtc = reader.ReadBoolean();
		if (reader.ReadBoolean())
		{
			if (rtc == null)
			{
				rtc = new Mbc3Rtc();
			}
			rtc.Load(reader);
		}
		else
		{
			rtc = null;
		}
	}
}
public class MMU
{
	private byte[] rom;

	private byte[] wram;

	private byte[] vram;

	private byte[] vramBank1;

	private byte[] oam;

	private byte[] hram;

	private byte[] io;

	private byte[] bootRom;

	private bool bootEnabled;

	private MBC mbc;

	private const int BOOT_ROM_SIZE = 256;

	private const int WRAM_SIZE = 32768;

	private const int VRAM_SIZE = 8192;

	private const int OAM_SIZE = 160;

	private const int HRAM_SIZE = 127;

	private const int IO_SIZE = 128;

	public byte IE;

	public byte IF;

	public byte JOYP;

	public byte DIV;

	public byte TIMA;

	public byte TMA;

	public byte TAC;

	public byte LCDC;

	public byte STAT;

	public byte SCY;

	public byte SCX;

	public byte LY;

	public byte LYC;

	public byte BGP;

	public byte OBP0;

	public byte OBP1;

	public byte WY;

	public byte WX;

	public byte NR50;

	public byte NR51;

	public byte NR52;

	public byte joypadState = byte.MaxValue;

	private int vramBankIndex;

	private int wramBankIndex;

	private readonly byte[] cgbBgPaletteData = new byte[64];

	private readonly byte[] cgbObjPaletteData = new byte[64];

	private byte cgbBcps;

	private byte cgbOcps;

	private byte hdmaSrcHi;

	private byte hdmaSrcLo;

	private byte hdmaDstHi;

	private byte hdmaDstLo;

	private int hdmaBlocksLeft;

	private bool hdmaHBlankMode;

	private bool cgbDoubleSpeed;

	private bool cgbSpeedSwitchPending;

	public byte[] ram;

	public bool mode;

	public APU Apu;

	public Timer Timer;

	public bool IsCGBMode { get; private set; }

	public bool IsGbc => IsCGBMode;

	public bool CgbDoubleSpeed => cgbDoubleSpeed;

	public void ExecuteSpeedSwitch()
	{
		if (IsCGBMode && cgbSpeedSwitchPending)
		{
			cgbDoubleSpeed = !cgbDoubleSpeed;
			cgbSpeedSwitchPending = false;
		}
	}

	public MMU(byte[] gameRom, byte[] bootRomData, bool flatRamMode)
	{
		rom = gameRom;
		bootRom = bootRomData;
		wram = new byte[32768];
		vram = new byte[8192];
		vramBank1 = new byte[8192];
		oam = new byte[160];
		hram = new byte[127];
		io = new byte[128];
		bootEnabled = true;
		ram = new byte[65536];
		mode = flatRamMode;
		mbc = new MBC(rom);
		IsCGBMode = rom.Length > 323 && (rom[323] == 128 || rom[323] == 192);
		wramBankIndex = 1;
		vramBankIndex = 0;
		hdmaBlocksLeft = -1;
		Console.WriteLine($"MMU init – CGB mode: {IsCGBMode}");
	}

	private static string GetRtcPath(string path)
	{
		return path + ".rtc";
	}

	public byte ReadVramDirect(int relAddr, int bank)
	{
		int num = relAddr & 0x1FFF;
		if (bank == 0)
		{
			return vram[num];
		}
		if (IsCGBMode)
		{
			return vramBank1[num];
		}
		return byte.MaxValue;
	}

	public Color32 GetCGBBgColor(int paletteIndex, int colorIndex)
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		int num = (paletteIndex * 4 + colorIndex) * 2;
		int c = cgbBgPaletteData[num] | (cgbBgPaletteData[num + 1] << 8);
		return Color15ToColor32(c);
	}

	public Color32 GetCGBObjColor(int paletteIndex, int colorIndex)
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		int num = (paletteIndex * 4 + colorIndex) * 2;
		int c = cgbObjPaletteData[num] | (cgbObjPaletteData[num + 1] << 8);
		return Color15ToColor32(c);
	}

	private static Color32 Color15ToColor32(int c)
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		int num = c & 0x1F;
		int num2 = (c >> 5) & 0x1F;
		int num3 = (c >> 10) & 0x1F;
		byte b = (byte)((num << 3) | (num >> 2));
		byte b2 = (byte)((num2 << 3) | (num2 >> 2));
		byte b3 = (byte)((num3 << 3) | (num3 >> 2));
		return new Color32(b, b2, b3, byte.MaxValue);
	}

	public void ExecuteHBlankDMA()
	{
		if (IsCGBMode && hdmaHBlankMode && hdmaBlocksLeft >= 0)
		{
			ushort num = (ushort)((hdmaSrcHi << 8) | hdmaSrcLo);
			ushort num2 = (ushort)(0x8000u | (uint)((hdmaDstHi & 0x1F) << 8) | hdmaDstLo);
			for (int i = 0; i < 16; i++)
			{
				byte value = Read((ushort)((uint)(num + i) & 0xFFFFu));
				WriteVramDirect((ushort)((uint)(num2 + i) & 0x9FFFu), value);
			}
			num = (ushort)((uint)(num + 16) & 0xFFFFu);
			num2 = (ushort)((uint)(num2 + 16) & 0xFFFFu);
			hdmaSrcHi = (byte)(num >> 8);
			hdmaSrcLo = (byte)(num & 0xF0u);
			hdmaDstHi = (byte)((uint)(num2 >> 8) & 0x1Fu);
			hdmaDstLo = (byte)(num2 & 0xF0u);
			hdmaBlocksLeft--;
		}
	}

	private void WriteVramDirect(ushort address, byte value)
	{
		int num = (address - 32768) & 0x1FFF;
		if (vramBankIndex == 0 || !IsCGBMode)
		{
			vram[num] = value;
		}
		else
		{
			vramBank1[num] = value;
		}
	}

	private void StartHDMA(byte value)
	{
		if (hdmaHBlankMode && hdmaBlocksLeft >= 0 && (value & 0x80) == 0)
		{
			hdmaBlocksLeft = -1;
			return;
		}
		hdmaHBlankMode = (value & 0x80) != 0;
		hdmaBlocksLeft = value & 0x7F;
		if (!hdmaHBlankMode)
		{
			ExecuteGeneralDMA(hdmaBlocksLeft + 1);
			hdmaBlocksLeft = -1;
		}
	}

	private void ExecuteGeneralDMA(int blockCount)
	{
		ushort num = (ushort)((hdmaSrcHi << 8) | hdmaSrcLo);
		ushort num2 = (ushort)(0x8000u | (uint)((hdmaDstHi & 0x1F) << 8) | hdmaDstLo);
		int num3 = blockCount * 16;
		for (int i = 0; i < num3; i++)
		{
			byte value = Read((ushort)((uint)(num + i) & 0xFFFFu));
			WriteVramDirect((ushort)((uint)(num2 + i) & 0x9FFFu), value);
		}
	}

	private void CompleteSerialTransfer(byte receivedByte = byte.MaxValue)
	{
		io[1] = receivedByte;
		io[2] = (byte)((io[2] & 1u) | 0x7Cu);
		IF |= 8;
	}

	private void BeginSerialTransfer(byte controlValue)
	{
		io[2] = (byte)((controlValue & 0x83u) | 0x7Cu);
		if ((controlValue & 0x80u) != 0 && ((uint)controlValue & (true ? 1u : 0u)) != 0)
		{
			CompleteSerialTransfer();
		}
	}

	public void Save(string path)
	{
		if (mbc.mbcType != 0)
		{
			Console.WriteLine("Writing save to: " + path);
			mbc.SetRtcSavePath(GetRtcPath(path));
			mbc.SaveBatteryData(path);
		}
	}

	public void Load(string path)
	{
		if (mbc.mbcType != 0)
		{
			mbc.SetRtcSavePath(GetRtcPath(path));
			mbc.LoadBatteryData(path);
			if (!File.Exists(path))
			{
				Console.WriteLine("Save not found at: " + path);
			}
			else
			{
				Console.WriteLine("Loading save: " + path);
			}
		}
	}

	public string HeaderInfo()
	{
		return mbc.GetTitle() + "\n" + mbc.GetCartridgeType() + "\n" + mbc.GetRomSize() + "\n" + mbc.GetRamSize() + "\n" + mbc.GetChecksum();
	}

	public void InitializeCGBRegisters()
	{
		if (!IsCGBMode)
		{
			return;
		}
		LCDC = 145;
		STAT = 133;
		SCY = 0;
		SCX = 0;
		LY = 0;
		LYC = 0;
		BGP = 252;
		OBP0 = byte.MaxValue;
		OBP1 = byte.MaxValue;
		WY = 0;
		WX = 0;
		NR52 = 241;
		NR50 = 119;
		NR51 = 243;
		IF = 225;
		IE = 0;
		ushort[] array = new ushort[4] { 32767, 22197, 10570, 0 };
		for (int i = 0; i < 8; i++)
		{
			for (int j = 0; j < 4; j++)
			{
				int num = (i * 4 + j) * 2;
				ushort num2 = (ushort)((i == 0) ? array[j] : 0);
				cgbBgPaletteData[num] = (byte)(num2 & 0xFFu);
				cgbBgPaletteData[num + 1] = (byte)(num2 >> 8);
				cgbObjPaletteData[num] = (byte)(num2 & 0xFFu);
				cgbObjPaletteData[num + 1] = (byte)(num2 >> 8);
			}
		}
	}

	public byte Read(ushort address)
	{
		return mode ? Read2(address) : Read1(address);
	}

	public void Write(ushort address, byte value)
	{
		if (mode)
		{
			Write2(address, value);
		}
		else
		{
			Write1(address, value);
		}
	}

	public byte Read2(ushort address)
	{
		return ram[address];
	}

	public void Write2(ushort address, byte value)
	{
		ram[address] = value;
	}

	public byte Read1(ushort address)
	{
		if (bootEnabled && address < 256)
		{
			return bootRom[address];
		}
		if (address < 32768 || (address >= 40960 && address < 49152))
		{
			return mbc.Read(address);
		}
		if (address >= 32768 && address < 40960)
		{
			int num = address - 32768;
			return (vramBankIndex == 0 || !IsCGBMode) ? vram[num] : vramBank1[num];
		}
		if (address >= 49152 && address < 53248)
		{
			return wram[address - 49152];
		}
		if (address >= 53248 && address < 57344)
		{
			return wram[(wramBankIndex << 12) + (address - 53248)];
		}
		if (address >= 57344 && address < 65024)
		{
			ushort num2 = (ushort)(address - 8192);
			if (num2 < 53248)
			{
				return wram[num2 - 49152];
			}
			return wram[(wramBankIndex << 12) + (num2 - 53248)];
		}
		if (address >= 65024 && address < 65184)
		{
			return oam[address - 65024];
		}
		if (address >= 65408 && address < ushort.MaxValue)
		{
			return hram[address - 65408];
		}
		if (address >= 65328 && address <= 65343)
		{
			return (Apu != null) ? Apu.ReadRegister(address) : io[address - 65280];
		}
		switch (address)
		{
		case 65280:
			if ((JOYP & 0x10) == 0)
			{
				return (byte)((uint)(joypadState >> 4) | 0x20u);
			}
			if ((JOYP & 0x20) == 0)
			{
				return (byte)((joypadState & 0xFu) | 0x10u);
			}
			return (byte)(JOYP | 0xFFu);
		case 65281:
			return io[1];
		case 65282:
			return (byte)(io[2] | 0x7Cu);
		case 65284:
			return DIV;
		case 65285:
			return TIMA;
		case 65286:
			return TMA;
		case 65287:
			return TAC;
		case 65295:
			return IF;
		case 65296:
		case 65297:
		case 65298:
		case 65299:
		case 65300:
		case 65302:
		case 65303:
		case 65304:
		case 65305:
		case 65306:
		case 65307:
		case 65308:
		case 65309:
		case 65310:
		case 65312:
		case 65313:
		case 65314:
		case 65315:
		case 65316:
		case 65317:
		case 65318:
			return (Apu != null) ? Apu.ReadRegister(address) : io[address - 65280];
		case 65301:
		case 65311:
		case 65319:
		case 65320:
		case 65321:
		case 65322:
		case 65323:
		case 65324:
		case 65325:
		case 65326:
		case 65327:
			return byte.MaxValue;
		case 65344:
			return LCDC;
		case 65345:
			return STAT;
		case 65346:
			return SCY;
		case 65347:
			return SCX;
		case 65348:
			return LY;
		case 65349:
			return LYC;
		case 65351:
			return BGP;
		case 65352:
			return OBP0;
		case 65353:
			return OBP1;
		case 65354:
			return WY;
		case 65355:
			return WX;
		case 65359:
			return (byte)((uint)vramBankIndex | 0xFEu);
		case 65357:
			if (!IsCGBMode)
			{
				return byte.MaxValue;
			}
			return (byte)((cgbDoubleSpeed ? 128u : 0u) | (cgbSpeedSwitchPending ? 1u : 0u) | 0x7Eu);
		case 65361:
			return byte.MaxValue;
		case 65362:
			return byte.MaxValue;
		case 65363:
			return byte.MaxValue;
		case 65364:
			return byte.MaxValue;
		case 65365:
			if (!IsCGBMode)
			{
				return byte.MaxValue;
			}
			if (hdmaBlocksLeft < 0)
			{
				return byte.MaxValue;
			}
			return (byte)(((!hdmaHBlankMode) ? 128u : 0u) | ((uint)hdmaBlocksLeft & 0x7Fu));
		case 65384:
			return IsCGBMode ? cgbBcps : byte.MaxValue;
		case 65385:
			return IsCGBMode ? cgbBgPaletteData[cgbBcps & 0x3F] : byte.MaxValue;
		case 65386:
			return IsCGBMode ? cgbOcps : byte.MaxValue;
		case 65387:
			return IsCGBMode ? cgbObjPaletteData[cgbOcps & 0x3F] : byte.MaxValue;
		case 65392:
			return IsCGBMode ? ((byte)((uint)wramBankIndex | 0xF8u)) : byte.MaxValue;
		case ushort.MaxValue:
			return IE;
		default:
			if (address >= 65280 && address < 65408)
			{
				return io[address - 65280];
			}
			return byte.MaxValue;
		}
	}

	public void Write1(ushort address, byte value)
	{
		if (address == 65360)
		{
			bootEnabled = false;
			return;
		}
		if (address < 32768 || (address >= 40960 && address < 49152))
		{
			mbc.Write(address, value);
			return;
		}
		if (address >= 32768 && address < 40960)
		{
			int num = address - 32768;
			if (vramBankIndex == 0 || !IsCGBMode)
			{
				vram[num] = value;
			}
			else
			{
				vramBank1[num] = value;
			}
			return;
		}
		if (address >= 49152 && address < 53248)
		{
			wram[address - 49152] = value;
			return;
		}
		if (address >= 53248 && address < 57344)
		{
			wram[(wramBankIndex << 12) + (address - 53248)] = value;
			return;
		}
		if (address >= 57344 && address < 65024)
		{
			ushort num2 = (ushort)(address - 8192);
			if (num2 < 53248)
			{
				wram[num2 - 49152] = value;
			}
			else
			{
				wram[(wramBankIndex << 12) + (num2 - 53248)] = value;
			}
			return;
		}
		if (address >= 65024 && address < 65184)
		{
			oam[address - 65024] = value;
			return;
		}
		if (address >= 65408 && address < ushort.MaxValue)
		{
			hram[address - 65408] = value;
			return;
		}
		if (address >= 65328 && address <= 65343)
		{
			Apu?.WriteRegister(address, value);
			return;
		}
		switch (address)
		{
		case 65280:
			JOYP = (byte)(value & 0x30u);
			break;
		case 65281:
			io[1] = value;
			break;
		case 65282:
			BeginSerialTransfer(value);
			break;
		case 65284:
			Timer?.ResetDiv();
			if (Timer == null)
			{
				DIV = 0;
			}
			break;
		case 65285:
			TIMA = value;
			break;
		case 65286:
			TMA = value;
			break;
		case 65287:
			TAC = (byte)(value & 7u);
			break;
		case 65295:
			IF = value;
			break;
		case 65296:
		case 65297:
		case 65298:
		case 65299:
		case 65300:
		case 65302:
		case 65303:
		case 65304:
		case 65305:
		case 65306:
		case 65307:
		case 65308:
		case 65309:
		case 65310:
		case 65312:
		case 65313:
		case 65314:
		case 65315:
		case 65316:
		case 65317:
		case 65318:
			Apu?.WriteRegister(address, value);
			break;
		case 65301:
		case 65311:
		case 65319:
		case 65320:
		case 65321:
		case 65322:
		case 65323:
		case 65324:
		case 65325:
		case 65326:
		case 65327:
			break;
		case 65344:
			LCDC = value;
			if ((value & 0x80) == 0)
			{
				STAT &= 124;
				LY = 0;
			}
			break;
		case 65345:
			STAT = value;
			break;
		case 65346:
			SCY = value;
			break;
		case 65347:
			SCX = value;
			break;
		case 65348:
			LY = value;
			break;
		case 65349:
			LYC = value;
			break;
		case 65350:
		{
			ushort num5 = (ushort)(value << 8);
			for (ushort num6 = 0; num6 < 160; num6++)
			{
				Write((ushort)(65024 + num6), Read((ushort)(num5 + num6)));
			}
			break;
		}
		case 65351:
			BGP = value;
			break;
		case 65352:
			OBP0 = value;
			break;
		case 65353:
			OBP1 = value;
			break;
		case 65354:
			WY = value;
			break;
		case 65355:
			WX = value;
			break;
		case 65359:
			if (IsCGBMode)
			{
				vramBankIndex = value & 1;
			}
			break;
		case 65357:
			if (IsCGBMode)
			{
				cgbSpeedSwitchPending = (value & 1) != 0;
			}
			break;
		case 65361:
			hdmaSrcHi = value;
			break;
		case 65362:
			hdmaSrcLo = (byte)(value & 0xF0u);
			break;
		case 65363:
			hdmaDstHi = (byte)(value & 0x1Fu);
			break;
		case 65364:
			hdmaDstLo = (byte)(value & 0xF0u);
			break;
		case 65365:
			if (IsCGBMode)
			{
				StartHDMA(value);
			}
			break;
		case 65384:
			if (IsCGBMode)
			{
				cgbBcps = (byte)(value & 0xBFu);
			}
			break;
		case 65385:
			if (IsCGBMode)
			{
				int num4 = cgbBcps & 0x3F;
				cgbBgPaletteData[num4] = value;
				if ((cgbBcps & 0x80u) != 0)
				{
					cgbBcps = (byte)((cgbBcps & 0x80u) | ((uint)(num4 + 1) & 0x3Fu));
				}
			}
			break;
		case 65386:
			if (IsCGBMode)
			{
				cgbOcps = (byte)(value & 0xBFu);
			}
			break;
		case 65387:
			if (IsCGBMode)
			{
				int num3 = cgbOcps & 0x3F;
				cgbObjPaletteData[num3] = value;
				if ((cgbOcps & 0x80u) != 0)
				{
					cgbOcps = (byte)((cgbOcps & 0x80u) | ((uint)(num3 + 1) & 0x3Fu));
				}
			}
			break;
		case 65392:
			if (IsCGBMode)
			{
				wramBankIndex = value & 7;
				if (wramBankIndex == 0)
				{
					wramBankIndex = 1;
				}
			}
			break;
		case ushort.MaxValue:
			IE = value;
			break;
		default:
			if (address >= 65280 && address < 65408)
			{
				io[address - 65280] = value;
			}
			break;
		}
	}

	private static void WriteByteArray(BinaryWriter w, byte[] data)
	{
		if (data == null)
		{
			w.Write(-1);
			return;
		}
		w.Write(data.Length);
		w.Write(data);
	}

	private static byte[] ReadByteArray(BinaryReader r)
	{
		int num = r.ReadInt32();
		if (num < 0)
		{
			return null;
		}
		return r.ReadBytes(num);
	}

	public void SaveState(BinaryWriter writer)
	{
		WriteByteArray(writer, wram);
		WriteByteArray(writer, vram);
		WriteByteArray(writer, vramBank1);
		WriteByteArray(writer, oam);
		WriteByteArray(writer, hram);
		WriteByteArray(writer, io);
		WriteByteArray(writer, ram);
		writer.Write(bootEnabled);
		writer.Write(mode);
		writer.Write(IsCGBMode);
		writer.Write(IE);
		writer.Write(IF);
		writer.Write(JOYP);
		writer.Write(DIV);
		writer.Write(TIMA);
		writer.Write(TMA);
		writer.Write(TAC);
		writer.Write(LCDC);
		writer.Write(STAT);
		writer.Write(SCY);
		writer.Write(SCX);
		writer.Write(LY);
		writer.Write(LYC);
		writer.Write(BGP);
		writer.Write(OBP0);
		writer.Write(OBP1);
		writer.Write(WY);
		writer.Write(WX);
		writer.Write(NR50);
		writer.Write(NR51);
		writer.Write(NR52);
		writer.Write(joypadState);
		writer.Write(vramBankIndex);
		writer.Write(wramBankIndex);
		WriteByteArray(writer, cgbBgPaletteData);
		WriteByteArray(writer, cgbObjPaletteData);
		writer.Write(cgbBcps);
		writer.Write(cgbOcps);
		writer.Write(hdmaSrcHi);
		writer.Write(hdmaSrcLo);
		writer.Write(hdmaDstHi);
		writer.Write(hdmaDstLo);
		writer.Write(hdmaBlocksLeft);
		writer.Write(hdmaHBlankMode);
		writer.Write(cgbDoubleSpeed);
		writer.Write(cgbSpeedSwitchPending);
		mbc.SaveState(writer);
	}

	public void LoadState(BinaryReader reader)
	{
		byte[] array = ReadByteArray(reader);
		byte[] array2 = ReadByteArray(reader);
		byte[] array3 = ReadByteArray(reader);
		byte[] array4 = ReadByteArray(reader);
		byte[] array5 = ReadByteArray(reader);
		byte[] array6 = ReadByteArray(reader);
		byte[] array7 = ReadByteArray(reader);
		if (array == null || array.Length != wram.Length)
		{
			throw new InvalidOperationException("Invalid WRAM in savestate.");
		}
		if (array2 == null || array2.Length != vram.Length)
		{
			throw new InvalidOperationException("Invalid VRAM in savestate.");
		}
		if (array3 == null || array3.Length != vramBank1.Length)
		{
			throw new InvalidOperationException("Invalid VRAM1 in savestate.");
		}
		if (array4 == null || array4.Length != oam.Length)
		{
			throw new InvalidOperationException("Invalid OAM in savestate.");
		}
		if (array5 == null || array5.Length != hram.Length)
		{
			throw new InvalidOperationException("Invalid HRAM in savestate.");
		}
		if (array6 == null || array6.Length != io.Length)
		{
			throw new InvalidOperationException("Invalid IO in savestate.");
		}
		if (array7 == null || array7.Length != ram.Length)
		{
			throw new InvalidOperationException("Invalid RAM in savestate.");
		}
		Array.Copy(array, wram, wram.Length);
		Array.Copy(array2, vram, vram.Length);
		Array.Copy(array3, vramBank1, vramBank1.Length);
		Array.Copy(array4, oam, oam.Length);
		Array.Copy(array5, hram, hram.Length);
		Array.Copy(array6, io, io.Length);
		Array.Copy(array7, ram, ram.Length);
		bootEnabled = reader.ReadBoolean();
		mode = reader.ReadBoolean();
		reader.ReadBoolean();
		IE = reader.ReadByte();
		IF = reader.ReadByte();
		JOYP = reader.ReadByte();
		DIV = reader.ReadByte();
		TIMA = reader.ReadByte();
		TMA = reader.ReadByte();
		TAC = reader.ReadByte();
		LCDC = reader.ReadByte();
		STAT = reader.ReadByte();
		SCY = reader.ReadByte();
		SCX = reader.ReadByte();
		LY = reader.ReadByte();
		LYC = reader.ReadByte();
		BGP = reader.ReadByte();
		OBP0 = reader.ReadByte();
		OBP1 = reader.ReadByte();
		WY = reader.ReadByte();
		WX = reader.ReadByte();
		NR50 = reader.ReadByte();
		NR51 = reader.ReadByte();
		NR52 = reader.ReadByte();
		joypadState = reader.ReadByte();
		vramBankIndex = reader.ReadInt32();
		wramBankIndex = reader.ReadInt32();
		byte[] array8 = ReadByteArray(reader);
		byte[] array9 = ReadByteArray(reader);
		if (array8 != null && array8.Length == 64)
		{
			Array.Copy(array8, cgbBgPaletteData, 64);
		}
		if (array9 != null && array9.Length == 64)
		{
			Array.Copy(array9, cgbObjPaletteData, 64);
		}
		cgbBcps = reader.ReadByte();
		cgbOcps = reader.ReadByte();
		hdmaSrcHi = reader.ReadByte();
		hdmaSrcLo = reader.ReadByte();
		hdmaDstHi = reader.ReadByte();
		hdmaDstLo = reader.ReadByte();
		hdmaBlocksLeft = reader.ReadInt32();
		hdmaHBlankMode = reader.ReadBoolean();
		cgbDoubleSpeed = reader.ReadBoolean();
		cgbSpeedSwitchPending = reader.ReadBoolean();
		mbc.LoadState(reader);
	}
}
public sealed class NoiseChannel
{
	private int timer;

	private int lengthCounter;

	private int volume;

	private int envelopeTimer;

	private bool envelopeEnabled;

	private ushort lfsr;

	public bool Enabled { get; private set; }

	public byte NR41 { get; private set; }

	public byte NR42 { get; private set; }

	public byte NR43 { get; private set; }

	public byte NR44 { get; private set; }

	public bool LengthWasZeroOnTrigger { get; private set; }

	public int LengthCounter => lengthCounter;

	public bool DacEnabled => (NR42 & 0xF8) != 0;

	public void PowerOff()
	{
		Enabled = false;
		byte b2 = (NR44 = 0);
		byte b4 = (NR43 = b2);
		byte nR = (NR42 = b4);
		NR41 = nR;
		timer = 0;
		volume = 0;
		envelopeTimer = 0;
		envelopeEnabled = false;
		lfsr = 32767;
	}

	public void ResetAfterPowerOn(bool isCgbMode)
	{
		Enabled = false;
		timer = 0;
		volume = 0;
		envelopeTimer = 0;
		envelopeEnabled = false;
		lfsr = 32767;
		if (isCgbMode)
		{
			lengthCounter = 0;
		}
	}

	public void Reset()
	{
		Enabled = false;
		timer = 0;
		lengthCounter = 0;
		volume = 0;
		envelopeTimer = 0;
		envelopeEnabled = false;
		lfsr = 32767;
	}

	public void WriteNR41(byte value)
	{
		NR41 = value;
		lengthCounter = 64 - (value & 0x3F);
	}

	public void WriteLengthOnly(byte value)
	{
		lengthCounter = 64 - (value & 0x3F);
	}

	public void WriteNR42(byte value)
	{
		byte nR = NR42;
		NR42 = value;
		if (!DacEnabled)
		{
			Enabled = false;
		}
		if (Enabled)
		{
			ApplyZombieEnvelopeWrite(nR, value);
		}
	}

	public void WriteNR43(byte value)
	{
		NR43 = value;
	}

	public void WriteNR44(byte value)
	{
		NR44 = value;
		if ((value & 0x80u) != 0)
		{
			Trigger();
		}
	}

	public void StepTimer(int tCycles)
	{
		if (!Enabled)
		{
			return;
		}
		int noisePeriod = GetNoisePeriod();
		if (noisePeriod == int.MaxValue)
		{
			return;
		}
		timer -= tCycles;
		while (timer <= 0)
		{
			timer += noisePeriod;
			int num = (lfsr & 1) ^ ((lfsr >> 1) & 1);
			lfsr >>= 1;
			lfsr |= (ushort)(num << 14);
			if ((NR43 & 8u) != 0)
			{
				lfsr &= 65471;
				lfsr |= (ushort)(num << 6);
			}
		}
	}

	public void ClockLength()
	{
		if ((NR44 & 0x40u) != 0 && lengthCounter > 0)
		{
			lengthCounter--;
			if (lengthCounter == 0)
			{
				Enabled = false;
			}
		}
	}

	public void ExtraLengthClock()
	{
		if (lengthCounter > 0)
		{
			lengthCounter--;
			if (lengthCounter == 0)
			{
				Enabled = false;
			}
		}
	}

	public void ClockEnvelope()
	{
		if (!Enabled || !envelopeEnabled)
		{
			return;
		}
		envelopeTimer--;
		if (envelopeTimer > 0)
		{
			return;
		}
		int envelopePeriodRaw = GetEnvelopePeriodRaw();
		envelopeTimer = GetEnvelopeTimerReload();
		if (envelopePeriodRaw != 0)
		{
			int num = (((NR42 & 8u) != 0) ? (volume + 1) : (volume - 1));
			if (num >= 0 && num <= 15)
			{
				volume = num;
			}
			else
			{
				envelopeEnabled = false;
			}
		}
	}

	public void DelayEnvelopeTimerForObscureTrigger()
	{
		if (envelopeTimer > 0)
		{
			envelopeTimer++;
		}
	}

	private int GetEnvelopePeriodRaw()
	{
		return NR42 & 7;
	}

	private int GetEnvelopeTimerReload()
	{
		int envelopePeriodRaw = GetEnvelopePeriodRaw();
		return (envelopePeriodRaw == 0) ? 8 : envelopePeriodRaw;
	}

	private void Trigger()
	{
		LengthWasZeroOnTrigger = lengthCounter == 0;
		if (lengthCounter == 0)
		{
			lengthCounter = 64;
		}
		volume = (NR42 >> 4) & 0xF;
		envelopeTimer = GetEnvelopeTimerReload();
		envelopeEnabled = true;
		lfsr = 32767;
		timer = GetNoisePeriod();
		Enabled = DacEnabled;
	}

	public int GetDigitalOutput()
	{
		if (!Enabled || !DacEnabled)
		{
			return 0;
		}
		return (((uint)(~lfsr) & (true ? 1u : 0u)) != 0) ? volume : 0;
	}

	private void ApplyZombieEnvelopeWrite(byte oldValue, byte newValue)
	{
		int num = oldValue & 7;
		bool flag = (oldValue & 8) == 0;
		bool flag2 = !flag;
		bool flag3 = (newValue & 8) != 0;
		int num2 = volume;
		if (num == 0 && envelopeEnabled)
		{
			num2++;
		}
		else if (flag)
		{
			num2 += 2;
		}
		if (flag2 != flag3)
		{
			num2 = 16 - num2;
		}
		volume = num2 & 0xF;
	}

	private int GetNoisePeriod()
	{
		int num = NR43 & 7;
		int num2 = (NR43 >> 4) & 0xF;
		if (num2 >= 14)
		{
			return int.MaxValue;
		}
		return num switch
		{
			0 => 8, 
			1 => 16, 
			2 => 32, 
			3 => 48, 
			4 => 64, 
			5 => 80, 
			6 => 96, 
			7 => 112, 
			_ => 8, 
		} << num2;
	}

	private int GetEnvelopePeriod()
	{
		int num = NR42 & 7;
		return (num == 0) ? 8 : num;
	}

	public void SaveState(BinaryWriter writer)
	{
		writer.Write(Enabled);
		writer.Write(NR41);
		writer.Write(NR42);
		writer.Write(NR43);
		writer.Write(NR44);
		writer.Write(timer);
		writer.Write(lengthCounter);
		writer.Write(volume);
		writer.Write(envelopeTimer);
		writer.Write(envelopeEnabled);
		writer.Write(lfsr);
	}

	public void LoadState(BinaryReader reader)
	{
		Enabled = reader.ReadBoolean();
		NR41 = reader.ReadByte();
		NR42 = reader.ReadByte();
		NR43 = reader.ReadByte();
		NR44 = reader.ReadByte();
		timer = reader.ReadInt32();
		lengthCounter = reader.ReadInt32();
		volume = reader.ReadInt32();
		envelopeTimer = reader.ReadInt32();
		envelopeEnabled = reader.ReadBoolean();
		lfsr = reader.ReadUInt16();
	}
}
public sealed class SquareChannel
{
	private static readonly byte[][] DutyTable = new byte[4][]
	{
		new byte[8] { 0, 0, 0, 0, 0, 0, 0, 1 },
		new byte[8] { 1, 0, 0, 0, 0, 0, 0, 1 },
		new byte[8] { 1, 0, 0, 0, 0, 1, 1, 1 },
		new byte[8] { 0, 1, 1, 1, 1, 1, 1, 0 }
	};

	private readonly bool hasSweep;

	private int timer;

	private int dutyStep;

	private int lengthCounter;

	private int volume;

	private int envelopeTimer;

	private bool envelopeEnabled;

	private int sweepTimer;

	private int shadowFrequency;

	private bool sweepEnabled;

	private bool sweepNegateUsed;

	public bool Enabled { get; private set; }

	public byte NR10 { get; private set; }

	public byte NR11 { get; private set; }

	public byte NR12 { get; private set; }

	public byte NR13 { get; private set; }

	public byte NR14 { get; private set; }

	public bool LengthWasZeroOnTrigger { get; private set; }

	public int LengthCounter => lengthCounter;

	public bool DacEnabled => (NR12 & 0xF8) != 0;

	public SquareChannel(bool hasSweep)
	{
		this.hasSweep = hasSweep;
		PowerOff();
	}

	public void PowerOff()
	{
		Enabled = false;
		byte b2 = (NR14 = 0);
		byte b4 = (NR13 = b2);
		byte b6 = (NR12 = b4);
		byte nR = (NR11 = b6);
		NR10 = nR;
		timer = 0;
		dutyStep = 0;
		lengthCounter = 0;
		volume = 0;
		envelopeTimer = 0;
		envelopeEnabled = false;
		sweepTimer = 0;
		shadowFrequency = 0;
		sweepEnabled = false;
		sweepNegateUsed = false;
	}

	public void ResetAfterPowerOn(bool isCgbMode)
	{
		Enabled = false;
		timer = 0;
		dutyStep = 0;
		volume = 0;
		envelopeTimer = 0;
		envelopeEnabled = false;
		sweepTimer = 0;
		shadowFrequency = 0;
		sweepEnabled = false;
		sweepNegateUsed = false;
		if (isCgbMode)
		{
			lengthCounter = 0;
		}
	}

	public void ResetDutyStep()
	{
		dutyStep = 0;
	}

	public void WriteNR10(byte value)
	{
		if (hasSweep)
		{
			bool flag = (NR10 & 8) != 0;
			bool flag2 = (value & 8) != 0;
			if (flag && !flag2 && sweepNegateUsed)
			{
				Enabled = false;
			}
			NR10 = value;
		}
	}

	public void WriteNR11(byte value)
	{
		NR11 = value;
		lengthCounter = 64 - (value & 0x3F);
	}

	public void WriteLengthOnly(byte value)
	{
		lengthCounter = 64 - (value & 0x3F);
	}

	public void WriteNR12(byte value)
	{
		byte nR = NR12;
		NR12 = value;
		if (!DacEnabled)
		{
			Enabled = false;
		}
		if (Enabled)
		{
			ApplyZombieEnvelopeWrite(nR, value);
		}
	}

	public void WriteNR13(byte value)
	{
		NR13 = value;
	}

	public void WriteNR14(byte value)
	{
		NR14 = value;
		if ((value & 0x80u) != 0)
		{
			Trigger();
		}
	}

	public void StepTimer(int tCycles)
	{
		if (Enabled)
		{
			timer -= tCycles;
			while (timer <= 0)
			{
				timer += GetPeriod();
				dutyStep = (dutyStep + 1) & 7;
			}
		}
	}

	public void ClockLength()
	{
		if ((NR14 & 0x40u) != 0 && lengthCounter > 0)
		{
			lengthCounter--;
			if (lengthCounter == 0)
			{
				Enabled = false;
			}
		}
	}

	public void ExtraLengthClock()
	{
		if (lengthCounter > 0)
		{
			lengthCounter--;
			if (lengthCounter == 0)
			{
				Enabled = false;
			}
		}
	}

	public void ClockEnvelope()
	{
		if (!Enabled || !envelopeEnabled)
		{
			return;
		}
		envelopeTimer--;
		if (envelopeTimer > 0)
		{
			return;
		}
		int envelopePeriodRaw = GetEnvelopePeriodRaw();
		envelopeTimer = GetEnvelopeTimerReload();
		if (envelopePeriodRaw != 0)
		{
			int num = (((NR12 & 8u) != 0) ? (volume + 1) : (volume - 1));
			if (num >= 0 && num <= 15)
			{
				volume = num;
			}
			else
			{
				envelopeEnabled = false;
			}
		}
	}

	public void DelayEnvelopeTimerForObscureTrigger()
	{
		if (envelopeTimer > 0)
		{
			envelopeTimer++;
		}
	}

	public void ClockSweep()
	{
		if (!hasSweep || !Enabled)
		{
			return;
		}
		sweepTimer--;
		if (sweepTimer > 0)
		{
			return;
		}
		sweepTimer = GetSweepPeriod();
		if (!sweepEnabled || ((NR10 >> 4) & 7) == 0)
		{
			return;
		}
		int num = CalculateSweepFrequency();
		if (num > 2047)
		{
			Enabled = false;
		}
		else if ((NR10 & 7u) != 0)
		{
			shadowFrequency = num;
			NR13 = (byte)((uint)num & 0xFFu);
			NR14 = (byte)((NR14 & 0xF8u) | ((uint)(num >> 8) & 7u));
			if (CalculateSweepFrequency() > 2047)
			{
				Enabled = false;
			}
		}
	}

	private int CalculateSweepFrequency()
	{
		int num = shadowFrequency >> (NR10 & 7);
		bool flag = (NR10 & 8) != 0;
		if (flag)
		{
			sweepNegateUsed = true;
		}
		return flag ? (shadowFrequency - num) : (shadowFrequency + num);
	}

	public int GetDigitalOutput()
	{
		if (!Enabled || !DacEnabled)
		{
			return 0;
		}
		int num = (NR11 >> 6) & 3;
		return (DutyTable[num][dutyStep] != 0) ? volume : 0;
	}

	private void Trigger()
	{
		LengthWasZeroOnTrigger = lengthCounter == 0;
		if (lengthCounter == 0)
		{
			lengthCounter = 64;
		}
		volume = (NR12 >> 4) & 0xF;
		envelopeTimer = GetEnvelopeTimerReload();
		envelopeEnabled = true;
		timer = GetPeriod() | (timer & 3);
		Enabled = DacEnabled;
		if (hasSweep)
		{
			shadowFrequency = GetFrequency();
			sweepTimer = GetSweepPeriod();
			sweepEnabled = ((uint)(NR10 >> 4) & 7u) != 0 || (NR10 & 7) != 0;
			sweepNegateUsed = false;
			if ((NR10 & 7u) != 0 && CalculateSweepFrequency() > 2047)
			{
				Enabled = false;
			}
		}
	}

	private void ApplyZombieEnvelopeWrite(byte oldValue, byte newValue)
	{
		int num = oldValue & 7;
		bool flag = (oldValue & 8) == 0;
		bool flag2 = !flag;
		bool flag3 = (newValue & 8) != 0;
		int num2 = volume;
		if (num == 0 && envelopeEnabled)
		{
			num2++;
		}
		else if (flag)
		{
			num2 += 2;
		}
		if (flag2 != flag3)
		{
			num2 = 16 - num2;
		}
		volume = num2 & 0xF;
	}

	private int GetFrequency()
	{
		return ((NR14 & 7) << 8) | NR13;
	}

	private int GetPeriod()
	{
		return (2048 - GetFrequency()) * 4;
	}

	private int GetEnvelopePeriodRaw()
	{
		return NR12 & 7;
	}

	private int GetEnvelopeTimerReload()
	{
		int envelopePeriodRaw = GetEnvelopePeriodRaw();
		return (envelopePeriodRaw == 0) ? 8 : envelopePeriodRaw;
	}

	private int GetSweepPeriod()
	{
		int num = (NR10 >> 4) & 7;
		return (num == 0) ? 8 : num;
	}

	public void SaveState(BinaryWriter writer)
	{
		writer.Write(Enabled);
		writer.Write(NR10);
		writer.Write(NR11);
		writer.Write(NR12);
		writer.Write(NR13);
		writer.Write(NR14);
		writer.Write(timer);
		writer.Write(dutyStep);
		writer.Write(lengthCounter);
		writer.Write(volume);
		writer.Write(envelopeTimer);
		writer.Write(envelopeEnabled);
		writer.Write(sweepTimer);
		writer.Write(shadowFrequency);
		writer.Write(sweepEnabled);
		writer.Write(sweepNegateUsed);
	}

	public void LoadState(BinaryReader reader)
	{
		Enabled = reader.ReadBoolean();
		NR10 = reader.ReadByte();
		NR11 = reader.ReadByte();
		NR12 = reader.ReadByte();
		NR13 = reader.ReadByte();
		NR14 = reader.ReadByte();
		timer = reader.ReadInt32();
		dutyStep = reader.ReadInt32();
		lengthCounter = reader.ReadInt32();
		volume = reader.ReadInt32();
		envelopeTimer = reader.ReadInt32();
		envelopeEnabled = reader.ReadBoolean();
		sweepTimer = reader.ReadInt32();
		shadowFrequency = reader.ReadInt32();
		sweepEnabled = reader.ReadBoolean();
		sweepNegateUsed = reader.ReadBoolean();
	}
}
public class Timer
{
	private readonly MMU mmu;

	private readonly APU apu;

	private ushort divCounter;

	public Timer(MMU mmu, APU apu)
	{
		this.mmu = mmu;
		this.apu = apu;
		divCounter = 0;
	}

	public void Step(int elapsedCycles)
	{
		for (int i = 0; i < elapsedCycles; i++)
		{
			ushort num = divCounter;
			divCounter++;
			mmu.DIV = (byte)(divCounter >> 8);
			int num2 = (mmu.CgbDoubleSpeed ? 8192 : 4096);
			if ((num & num2) != 0 && (divCounter & num2) == 0)
			{
				apu.ClockDivApu();
			}
			if ((mmu.TAC & 4u) != 0)
			{
				int timerBitMask = GetTimerBitMask(mmu.TAC & 3);
				if ((num & timerBitMask) != 0 && (divCounter & timerBitMask) == 0)
				{
					IncrementTima();
				}
			}
		}
	}

	public void ResetDiv()
	{
		ushort num = divCounter;
		divCounter = 0;
		mmu.DIV = 0;
		int num2 = (mmu.CgbDoubleSpeed ? 8192 : 4096);
		if ((num & num2) != 0)
		{
			apu.ClockDivApu();
		}
		if ((mmu.TAC & 4u) != 0)
		{
			int timerBitMask = GetTimerBitMask(mmu.TAC & 3);
			if ((num & timerBitMask) != 0)
			{
				IncrementTima();
			}
		}
	}

	private void IncrementTima()
	{
		if (mmu.TIMA == byte.MaxValue)
		{
			mmu.TIMA = mmu.TMA;
			mmu.IF |= 4;
		}
		else
		{
			mmu.TIMA++;
		}
	}

	private int GetTimerBitMask(int tacClock)
	{
		int num = (mmu.CgbDoubleSpeed ? 1 : 0);
		return tacClock switch
		{
			0 => 512 << num, 
			1 => 8 << num, 
			2 => 32 << num, 
			3 => 128 << num, 
			_ => 512 << num, 
		};
	}

	public void SaveState(BinaryWriter writer)
	{
		writer.Write(divCounter);
	}

	public void LoadState(BinaryReader reader)
	{
		divCounter = reader.ReadUInt16();
		mmu.DIV = (byte)(divCounter >> 8);
	}
}
public sealed class WaveChannel
{
	private readonly bool isCgbMode;

	private readonly byte[] waveRam = new byte[16];

	private int lengthCounter;

	private int position;

	private byte sampleBuffer;

	private int timer;

	private long apuCycle;

	private readonly long[] fetchCycles = new long[4];

	private readonly int[] fetchIndices = new int[4];

	private int fetchHistoryCount;

	private int currentWaveByteIndex;

	public bool Enabled { get; private set; }

	public byte NR30 { get; private set; }

	public byte NR31 { get; private set; }

	public byte NR32 { get; private set; }

	public byte NR33 { get; private set; }

	public byte NR34 { get; private set; }

	public bool LengthWasZeroOnTrigger { get; private set; }

	public int LengthCounter => lengthCounter;

	public bool DacEnabled => (NR30 & 0x80) != 0;

	public long CurrentApuCycle => apuCycle;

	public WaveChannel(bool isCgbMode)
	{
		this.isCgbMode = isCgbMode;
		PowerOff();
	}

	public void PowerOff()
	{
		Enabled = false;
		byte b2 = (NR34 = 0);
		byte b4 = (NR33 = b2);
		byte b6 = (NR32 = b4);
		byte nR = (NR31 = b6);
		NR30 = nR;
		lengthCounter = 0;
		position = 0;
		sampleBuffer = 0;
		timer = 0;
		apuCycle = 0L;
		fetchHistoryCount = 0;
		currentWaveByteIndex = 0;
	}

	public void ResetAfterPowerOn(bool isCgbMode)
	{
		Enabled = false;
		position = 0;
		sampleBuffer = 0;
		timer = 0;
		fetchHistoryCount = 0;
		currentWaveByteIndex = 0;
		if (isCgbMode)
		{
			lengthCounter = 0;
		}
	}

	public void Reset()
	{
		Enabled = false;
		lengthCounter = 0;
		position = 0;
		sampleBuffer = 0;
		timer = 0;
		fetchHistoryCount = 0;
		currentWaveByteIndex = 0;
	}

	public void WriteNR30(byte value)
	{
		NR30 = value;
		if (!DacEnabled)
		{
			Enabled = false;
		}
	}

	public void WriteNR31(byte value)
	{
		NR31 = value;
		lengthCounter = 256 - value;
	}

	public void WriteLengthOnly(byte value)
	{
		lengthCounter = 256 - value;
	}

	public void WriteNR32(byte value)
	{
		NR32 = value;
	}

	public void WriteNR33(byte value)
	{
		NR33 = value;
	}

	public void WriteNR34(byte value)
	{
		WriteNR34(value, apuCycle);
	}

	public void WriteNR34(byte value, long triggerWriteCycle)
	{
		NR34 = value;
		if ((value & 0x80u) != 0)
		{
			Trigger(triggerWriteCycle);
		}
	}

	public void WriteWaveRam(ushort address, byte value)
	{
		int num = address - 65328;
		if ((uint)num < 16u)
		{
			int index;
			if (!Enabled)
			{
				waveRam[num] = value;
			}
			else if (isCgbMode)
			{
				waveRam[(position >> 1) & 0xF] = value;
			}
			else if (TryGetDmgCpuAccessibleIndex(out index))
			{
				waveRam[index] = value;
			}
		}
	}

	public byte ReadWaveRam(ushort address)
	{
		int num = address - 65328;
		if ((uint)num >= 16u)
		{
			return byte.MaxValue;
		}
		if (!Enabled)
		{
			return waveRam[num];
		}
		if (isCgbMode)
		{
			return waveRam[(position >> 1) & 0xF];
		}
		int index;
		return TryGetDmgCpuAccessibleIndex(out index) ? waveRam[index] : byte.MaxValue;
	}

	public void StepTimer(int tCycles)
	{
		if (tCycles <= 0)
		{
			return;
		}
		int num = tCycles;
		while (num > 0)
		{
			int num2 = ((num >= 2) ? 2 : num);
			num -= num2;
			apuCycle += num2;
			if (Enabled)
			{
				timer -= num2;
				while (timer <= 0)
				{
					timer += GetTimerPeriod();
					position = (position + 1) & 0x1F;
					currentWaveByteIndex = (position >> 1) & 0xF;
					sampleBuffer = waveRam[currentWaveByteIndex];
					RecordFetch(currentWaveByteIndex, apuCycle);
				}
			}
		}
	}

	public void ClockLength()
	{
		if ((NR34 & 0x40u) != 0 && lengthCounter > 0)
		{
			lengthCounter--;
			if (lengthCounter == 0)
			{
				Enabled = false;
			}
		}
	}

	public void ExtraLengthClock()
	{
		if (lengthCounter > 0)
		{
			lengthCounter--;
			if (lengthCounter == 0)
			{
				Enabled = false;
			}
		}
	}

	public int GetDigitalOutput()
	{
		if (!Enabled || !DacEnabled)
		{
			return 0;
		}
		int num = (((position & 1) == 0) ? ((sampleBuffer >> 4) & 0xF) : (sampleBuffer & 0xF));
		return ((NR32 >> 5) & 3) switch
		{
			0 => 0, 
			1 => num, 
			2 => num >> 1, 
			3 => num >> 2, 
			_ => 0, 
		};
	}

	private void Trigger(long triggerWriteCycle)
	{
		if (!isCgbMode && Enabled && timer == 2)
		{
			ApplyDmgRetriggerCorruptionFromNextPosition();
		}
		LengthWasZeroOnTrigger = lengthCounter == 0;
		if (lengthCounter == 0)
		{
			lengthCounter = 256;
		}
		position = 0;
		currentWaveByteIndex = 0;
		timer = GetTimerPeriod() + 6;
		Enabled = DacEnabled;
	}

	private void RecordFetch(int fetchIndex, long cycle)
	{
		for (int num = fetchCycles.Length - 1; num > 0; num--)
		{
			fetchCycles[num] = fetchCycles[num - 1];
			fetchIndices[num] = fetchIndices[num - 1];
		}
		fetchCycles[0] = cycle;
		fetchIndices[0] = fetchIndex;
		if (fetchHistoryCount < fetchCycles.Length)
		{
			fetchHistoryCount++;
		}
	}

	private bool TryGetDmgCpuAccessibleIndex(out int index)
	{
		long num = apuCycle;
		for (int i = 0; i < fetchHistoryCount; i++)
		{
			long num2 = fetchCycles[i];
			if (num2 == num)
			{
				index = fetchIndices[i];
				return true;
			}
		}
		index = 0;
		return false;
	}

	private void ApplyDmgRetriggerCorruptionFromNextPosition()
	{
		int num = (position + 1) & 0x1F;
		int num2 = (num >> 1) & 0xF;
		byte b = waveRam[num2];
		if (num < 8)
		{
			waveRam[0] = b;
			return;
		}
		int num3 = num2 & 0xC;
		for (int i = 0; i < 4; i++)
		{
			waveRam[i] = waveRam[num3 + i];
		}
	}

	private int GetFrequency()
	{
		return ((NR34 & 7) << 8) | NR33;
	}

	private int GetTimerPeriod()
	{
		return (2048 - GetFrequency()) * 2;
	}

	public void SaveState(BinaryWriter writer)
	{
		writer.Write(Enabled);
		writer.Write(NR30);
		writer.Write(NR31);
		writer.Write(NR32);
		writer.Write(NR33);
		writer.Write(NR34);
		writer.Write(lengthCounter);
		writer.Write(position);
		writer.Write(sampleBuffer);
		writer.Write(timer);
		writer.Write(apuCycle);
		writer.Write(fetchHistoryCount);
		for (int i = 0; i < fetchCycles.Length; i++)
		{
			writer.Write(fetchCycles[i]);
			writer.Write(fetchIndices[i]);
		}
		writer.Write(currentWaveByteIndex);
		writer.Write(waveRam.Length);
		writer.Write(waveRam);
	}

	public void LoadState(BinaryReader reader)
	{
		Enabled = reader.ReadBoolean();
		NR30 = reader.ReadByte();
		NR31 = reader.ReadByte();
		NR32 = reader.ReadByte();
		NR33 = reader.ReadByte();
		NR34 = reader.ReadByte();
		lengthCounter = reader.ReadInt32();
		position = reader.ReadInt32();
		sampleBuffer = reader.ReadByte();
		timer = reader.ReadInt32();
		apuCycle = reader.ReadInt64();
		fetchHistoryCount = reader.ReadInt32();
		for (int i = 0; i < fetchCycles.Length; i++)
		{
			fetchCycles[i] = reader.ReadInt64();
			fetchIndices[i] = reader.ReadInt32();
		}
		currentWaveByteIndex = read