Decompiled source of Game Boy Emulator v1.5.0
plugins/Game_Boy_Emulator/CodeDMGEmu.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
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