using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BRCCodeDmg; using BRCCodeDmg.Patches; using BRCCodeDmg.Transport; using BepInEx; using BepInEx.Configuration; using BombRushMP.Common.Networking; using BombRushMP.Plugin; 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] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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 & 0x80) != 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 | 0x80); case 65297: return (byte)(ch1.NR11 | 0x3F); case 65298: return ch1.NR12; case 65299: return byte.MaxValue; case 65300: return (byte)(ch1.NR14 | 0xBF); case 65302: return (byte)(ch2.NR11 | 0x3F); case 65303: return ch2.NR12; case 65304: return byte.MaxValue; case 65305: return (byte)(ch2.NR14 | 0xBF); case 65306: return (byte)(ch3.NR30 | 0x7F); case 65307: return byte.MaxValue; case 65308: return (byte)(ch3.NR32 | 0x9F); case 65309: return byte.MaxValue; case 65310: return (byte)(ch3.NR34 | 0xBF); case 65312: return byte.MaxValue; case 65313: return ch4.NR42; case 65314: return ch4.NR43; case 65315: return (byte)(ch4.NR44 | 0xBF); case 65316: return mmu.NR50; case 65317: return mmu.NR51; case 65318: return (byte)((mmu.NR52 & 0x80) | 0x70 | (ch1.Enabled ? 1 : 0) | (ch2.Enabled ? 2 : 0) | (ch3.Enabled ? 4 : 0) | (ch4.Enabled ? 8 : 0)); 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 & 0x10) != 0) { num += (double)num3; } if ((mmu.NR51 & 0x20) != 0) { num += (double)num4; } if ((mmu.NR51 & 0x40) != 0) { num += (double)num5; } if ((mmu.NR51 & 0x80) != 0) { num += (double)num6; } if ((mmu.NR51 & 1) != 0) { num2 += (double)num3; } if ((mmu.NR51 & 2) != 0) { num2 += (double)num4; } if ((mmu.NR51 & 4) != 0) { num2 += (double)num5; } if ((mmu.NR51 & 8) != 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)((PC >> 8) & 0xFF)); SP--; mmu.Write(SP, (byte)(PC & 0xFF)); 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 & 0xFF); break; case "de": D = (byte)(value >> 8); E = (byte)(value & 0xFF); break; case "hl": H = (byte)(value >> 8); L = (byte)(value & 0xFF); break; case "af": A = (byte)(value >> 8); F = (byte)(value & 0xFF); 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 & 0xFF); byte value2 = (byte)((SP >> 8) & 0xFF); 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 & 0xFF)); 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 & 0xF0); 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)(num & 0xFF); 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)(num & 0xFF); 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)(num & 0xFF); 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)(num & 0xFF); 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 |= 6; } if (negative) { A -= b; } else { if ((A & 0xF) > 9) { b |= 6; } if (A > 153) { b |= 0x60; } 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 & 0xFF); byte b2 = (byte)((SP >> 8) & 0xFF); 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 & 0x80); 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 & 1); 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 & 0x80); 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 & 0x80); 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 & 1); 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 & 1); 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 & 0x80); 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 & 0x80); 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 & 0x80); byte b2 = (byte)(r & 1); 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 & 0x80); byte b3 = (byte)(b & 1); 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 & 1); 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 & 1); 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 & 0xF0); byte b2 = (byte)(r & 0xF); 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 & 0xF0); byte b3 = (byte)(b & 0xF); 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 & 0xFF)); 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 & 0xFF)); 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 & 0xFF)); 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 && (mmu.Read(65357) & 1) != 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_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]; return; IL_00ae: mbcType = 0; Console.WriteLine("Error: Unknown/Unsupported MBC, using MBC0/ROM Only"); goto IL_00c2; } 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; private const int SerialMasterIntThreshold = 75000; private const int SerialSlaveCheckInterval = 5000; private bool _serialMasterActive; private int _serialMasterCycles; private byte _serialOutByte; private int _linkBitCounter; private bool _serialPeerByteReady; internal bool IsLocalLinked; private byte _serialPeerByte; private bool _serialSlaveActive; private int _serialSlaveInCycles; public bool IsCGBMode { get; private set; } public bool IsGbc => IsCGBMode; public bool CgbDoubleSpeed => cgbDoubleSpeed; private byte SerialScMask => (byte)(IsCGBMode ? 131 : 129); private byte SerialUnusedBits => (byte)(IsCGBMode ? 124 : 126); internal byte SB { get { return io[1]; } set { io[1] = value; } } internal byte SC => io[2]; internal int SerialClockCycles => (IsCGBMode && (io[2] & 2) != 0) ? 16 : 512; 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)(0x8000 | ((hdmaDstHi & 0x1F) << 8) | hdmaDstLo); for (int i = 0; i < 16; i++) { byte value = Read((ushort)((num + i) & 0xFFFF)); WriteVramDirect((ushort)((num2 + i) & 0x9FFF), value); } num = (ushort)((num + 16) & 0xFFFF); num2 = (ushort)((num2 + 16) & 0xFFFF); hdmaSrcHi = (byte)(num >> 8); hdmaSrcLo = (byte)(num & 0xF0); hdmaDstHi = (byte)((num2 >> 8) & 0x1F); hdmaDstLo = (byte)(num2 & 0xF0); 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)(0x8000 | ((hdmaDstHi & 0x1F) << 8) | hdmaDstLo); int num3 = blockCount * 16; for (int i = 0; i < num3; i++) { byte value = Read((ushort)((num + i) & 0xFFFF)); WriteVramDirect((ushort)((num2 + i) & 0x9FFF), value); } } public void CancelSerial() { ResetSerialState(); io[2] &= (byte)((!IsCGBMode) ? 1 : 3); } public byte ReadSerialData() { return io[1]; } internal int ShiftBit(int inBit) { int result = (io[1] >> 7) & 1; io[1] = (byte)((io[1] << 1) | (inBit & 1)); if (++_linkBitCounter >= 8) { _linkBitCounter = 0; io[2] = (byte)(io[2] & -129); IF |= 8; } return result; } internal void LinkReset() { _linkBitCounter = 0; } public void DeliverRemoteSerialByte(byte value) { if (!_serialPeerByteReady) { _serialPeerByteReady = true; _serialPeerByte = value; } } private void ResetSerialState() { _serialMasterActive = false; _serialMasterCycles = 0; _serialOutByte = byte.MaxValue; _serialPeerByteReady = false; _serialPeerByte = byte.MaxValue; _serialSlaveActive = false; _serialSlaveInCycles = 0; } private void BeginSerialTransfer(byte controlValue) { io[2] = (byte)(controlValue & SerialScMask); if ((controlValue & 0x80) == 0) { _serialMasterActive = false; _serialSlaveActive = false; } else if ((controlValue & 1) != 0) { if (!_serialMasterActive) { _serialMasterActive = true; _serialMasterCycles = 0; _serialOutByte = io[1]; _serialPeerByteReady = false; _serialPeerByte = byte.MaxValue; _serialSlaveActive = false; } } else if (!_serialSlaveActive) { _serialSlaveActive = true; _serialSlaveInCycles = 0; _serialMasterActive = false; } } public void StepSerialLink(int cycles) { if (IsLocalLinked) { return; } if (_serialMasterActive) { _serialMasterCycles += cycles; if (_serialPeerByteReady) { io[1] = _serialPeerByte; io[2] &= (byte)((!IsCGBMode) ? 1 : 3); ResetSerialState(); IF |= 8; } else if (_serialMasterCycles >= 75000) { io[1] = byte.MaxValue; io[2] &= (byte)((!IsCGBMode) ? 1 : 3); ResetSerialState(); IF |= 8; } } if (_serialSlaveActive) { _serialSlaveInCycles += cycles; if (_serialPeerByteReady) { byte serialPeerByte = _serialPeerByte; io[1] = serialPeerByte; io[2] &= (byte)((!IsCGBMode) ? 1 : 3); ResetSerialState(); IF |= 8; } else if (_serialSlaveInCycles >= 5000) { _serialSlaveInCycles = 0; } } } public void TickSerialLinkWait(float deltaSeconds) { } 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 & 0xFF); cgbBgPaletteData[num + 1] = (byte)(num2 >> 8); cgbObjPaletteData[num] = (byte)(num2 & 0xFF); 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)((joypadState >> 4) | 0x20); } if ((JOYP & 0x20) == 0) { return (byte)((joypadState & 0xF) | 0x10); } return (byte)(JOYP | 0xFF); case 65281: return io[1]; case 65282: return (byte)(io[2] | SerialUnusedBits); 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)(vramBankIndex | 0xFE); case 65357: if (!IsCGBMode) { return byte.MaxValue; } return (byte)((cgbDoubleSpeed ? 128 : 0) | (cgbSpeedSwitchPending ? 1 : 0) | 0x7E); 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) ? 128 : 0) | (hdmaBlocksLeft & 0x7F)); 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)(wramBankIndex | 0xF8)) : 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 & 0x30); 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 & 7); 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 & 0xF0); break; case 65363: hdmaDstHi = (byte)(value & 0x1F); break; case 65364: hdmaDstLo = (byte)(value & 0xF0); break; case 65365: if (IsCGBMode) { StartHDMA(value); } break; case 65384: if (IsCGBMode) { cgbBcps = (byte)(value & 0xBF); } break; case 65385: if (IsCGBMode) { int num4 = cgbBcps & 0x3F; cgbBgPaletteData[num4] = value; if ((cgbBcps & 0x80) != 0) { cgbBcps = (byte)((cgbBcps & 0x80) | ((num4 + 1) & 0x3F)); } } break; case 65386: if (IsCGBMode) { cgbOcps = (byte)(value & 0xBF); } break; case 65387: if (IsCGBMode) { int num3 = cgbOcps & 0x3F; cgbObjPaletteData[num3] = value; if ((cgbOcps & 0x80) != 0) { cgbOcps = (byte)((cgbOcps & 0x80) | ((num3 + 1) & 0x3F)); } } 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 b = (NR44 = 0); byte b3 = (NR43 = b); byte nR = (NR42 = b3); 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 & 0x80) != 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 & 8) != 0) { lfsr &= 65471; lfsr |= (ushort)(num << 6); } } } public void ClockLength() { if ((NR44 & 0x40) != 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 & 8) != 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 ((~lfsr & 1) != 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 b = (NR14 = 0); byte b3 = (NR13 = b); byte b5 = (NR12 = b3); byte nR = (NR11 = b5); 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 & 0x80) != 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 & 0x40) != 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 & 8) != 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 & 7) != 0) { shadowFrequency = num; NR13 = (byte)(num & 0xFF); NR14 = (byte)((NR14 & 0xF8) | ((num >> 8) & 7)); 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 = ((NR10 >> 4) & 7) != 0 || (NR10 & 7) != 0; sweepNegateUsed = false; if ((NR10 & 7) != 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 & 4) != 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 & 4) != 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 b = (NR34 = 0); byte b3 = (NR33 = b); byte b5 = (NR32 = b3); byte nR = (NR31 = b5); 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 & 0x80) != 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 & 0x40) != 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 = reader.ReadInt32(); int count = reader.ReadInt32(); byte[] array = reader.ReadBytes(count); for (int j = 0; j < waveRam.Length && j < array.Length; j++) { waveRam[j] = array[j]; } } } namespace BRCCodeDmg { public class AppCodeDmg : CustomApp { private static Sprite IconSprite; private static bool _initialized; private static AppCodeDmg _activeInstance; private CodeDmgEmulator _emulator; private CodeDmgRenderer _renderer; private CodeDmgAudioDriver _audioDriver; private string _loadedRomPath; private static PropertyInfo _slopChatInputBlockedProperty; private static bool _slopChatReflectionInitialized; private bool _wasChatActive; private const float TargetGameBoyFps = 59.7275f; private const float TargetFrameTime = 0.016742706f; private float _emulationTimeAccumulator = 0f; private const float LinkHoldRequired = 1f; private float _linkHoldTimer = 0f; private bool _linkHoldFired = false; private float _suppressEmulatorInputUntil = -1f; private static float _rightMenuOpenProbeUntil = -1f; private static bool _rightMenuOpenBlockedUntilRelease; public override bool Available => true; public CodeDmgEmulator GetEmulator() { return _emulator; } public static void Initialize() { if (!_initialized) { _initialized = true; string text = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "appicon.png"); if (File.Exists(text)) { IconSprite = TextureUtility.LoadSprite(text); } if ((Object)(object)IconSprite != (Object)null) { PhoneAPI.RegisterApp("gb emu", IconSprite); } else { PhoneAPI.RegisterApp("gb emu", (Sprite)null); } } } public override void OnAppInit() { ((CustomApp)this).OnAppInit(); _activeInstance = this; if ((Object)(object)IconSprite != (Object)null) { ((CustomApp)this).CreateTitleBar("GB-EMU", IconSprite, 80f); } else { ((CustomApp)this).CreateIconlessTitleBar("GB-EMU", 80f); } _renderer = new CodeDmgRenderer(this); _renderer.Build(); _audioDriver = ((Component)this).gameObject.GetComponent(); if ((Object)(object)_audioDriver == (Object)null) { _audioDriver = ((Component)this).gameObject.AddComponent(); } _audioDriver.SetMuted(muted: true); RenderNow(); } public override void OnAppEnable() { ((App)this).OnAppEnable(); _activeInstance = this; BeginRightMenuOpenReleaseGuard(); ((BaseUnityPlugin)CodeDmgPlugin.Instance).Config.Reload(); CodeDmgPlugin.ConfigSettings = new CodeDmgConfig(((BaseUnityPlugin)CodeDmgPlugin.Instance).Config); Helper.paletteName = CodeDmgPlugin.ConfigSettings?.Palette?.Value; CodeDmgState.AppActive = true; GBEmuCurrentState.AppActive = true; FlushCurrentPlayerInput(); _emulationTimeAccumulator = 0f; if (_renderer == null) { _renderer = new CodeDmgRenderer(this); _renderer.Build(); } if ((Object)(object)_audioDriver != (Object)null) { _audioDriver.SetMuted(muted: false); } string configuredRomPath = GetConfiguredRomPath(); string path = NormalizeConfiguredRomPath(_loadedRomPath ?? string.Empty); if (_emulator != null && !string.Equals(Path.GetFullPath(configuredRomPath), Path.GetFullPath(path), StringComparison.OrdinalIgnoreCase)) { if (ReadBoolFromConfig("SaveStates", "AutoSaveOnClose", defaultValue: true)) { SaveState(); } if (ReadBoolFromConfig("SaveStates", "BatterySaveAutoSave", defaultValue: true)) { _emulator.SaveRam(); } _emulator = null; } if (_emulator == null) { TryBootEmulator(); } bool flag = ReadBoolFromConfig("SaveStates", "AutoLoadOnOpen", defaultValue: true); if (_emulator != null && flag) { TryLoadState(); } CodeDmgPlugin.LinkCable?.SetApp(this); if (_emulator != null && CodeDmgPlugin.LinkCable != null) { CodeDmgPlugin.LinkCable.SetLocalRomHash(_emulator.GetRomHash()); } if (CodeDmgPlugin.LinkCable != null) { CodeDmgPlugin.LinkCable.StateChanged += OnLinkCableStateChanged; } CheckPendingInviteOnOpen(); RenderNow(); } public override void OnAppDisable() { ((App)this).OnAppDisable(); if ((Object)(object)_activeInstance == (Object)(object)this) { _activeInstance = null; } _renderer?.Teardown(); if ((Object)(object)_audioDriver != (Object)null) { _audioDriver.SetMuted(muted: true); } CodeDmgState.AppActive = false; GBEmuCurrentState.AppActive = false; FlushCurrentPlayerInput(); if (_emulator != null) { if (CodeDmgPlugin.ConfigSettings != null && CodeDmgPlugin.ConfigSettings.AutoSaveOnClose.Value) { SaveState(); } if (CodeDmgPlugin.ConfigSettings != null && CodeDmgPlugin.ConfigSettings.AutoSaveOnClose.Value) { _emulator.SaveRam(); } } CodeDmgPlugin.LinkCable?.Drop(); if (CodeDmgPlugin.LinkCable != null) { CodeDmgPlugin.LinkCable.StateChanged -= OnLinkCableStateChanged; } _emulator = null; _emulationTimeAccumulator = 0f; _linkHoldTimer = 0f; _linkHoldFired = false; if ((Object)(object)_audioDriver != (Object)null) { _audioDriver.SetEmulator(null); } } public override void OnAppUpdate() { //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) ((App)this).OnAppUpdate(); CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; if (configSettings != null && configSettings.LinkCableEnabled.Value) { CodeDmgPlugin.LinkCable?.Tick(Time.unscaledDeltaTime); } CodeDmgConfig configSettings2 = CodeDmgPlugin.ConfigSettings; if (configSettings2 != null && configSettings2.LinkCableEnabled.Value && CodeDmgPlugin.LinkCable != null && CodeDmgPlugin.LinkCable.HasPendingInvite && !LinkCableConnectDialog.IsVisible && !MasterMenu.IsVisible && !RomSelectMenu.IsVisible && !VolumeMenu.IsVisible && !GBPaletteMenu.IsVisible) { ShowConnectDialog(); } if (_emulator == null || _renderer == null) { return; } UpdateRightMenuOpenReleaseGuard(); if (Input.GetKeyDown((KeyCode)275) && !ShouldBlockRightMenuOpenSignal()) { TryOpenMasterMenuFromRightInput(); } bool suppressStartSelect = false; if (!MasterMenu.IsVisible && !LinkCablePlayerList.IsVisible && !LinkCableConnectDialog.IsVisible && !RomSelectMenu.IsVisible && !VolumeMenu.IsVisible && !GBPaletteMenu.IsVisible) { CodeDmgConfig configSettings3 = CodeDmgPlugin.ConfigSettings; bool flag = configSettings3 != null && (Input.GetKey(configSettings3.Start.Value) || Input.GetKey((KeyCode)333)); bool flag2 = configSettings3 != null && (Input.GetKey(configSettings3.Select.Value) || Input.GetKey((KeyCode)332)); if (flag && flag2) { suppressStartSelect = true; _linkHoldTimer += Time.unscaledDeltaTime; if (_linkHoldTimer >= 1f && !_linkHoldFired) { _linkHoldFired = true; OpenMasterMenu(); } } else { _linkHoldTimer = 0f; _linkHoldFired = false; } } HandleEmulatorInput(suppressStartSelect); _emulationTimeAccumulator += Time.unscaledDeltaTime; if (_emulationTimeAccumulator > 0.05022812f) { _emulationTimeAccumulator = 0.05022812f; } bool flag3 = false; while (_emulationTimeAccumulator >= 0.016742706f) { if (_emulator.ShouldYieldForSerialLink()) { _emulator.TickSerialLinkWait(Time.unscaledDeltaTime); _emulationTimeAccumulator = 0f; break; } _emulationTimeAccumulator -= 0.016742706f; _emulator.StepFrame(); flag3 = true; } if (flag3 && _emulator.FrameDirty) { _renderer.Render(_emulator); } } internal static void PlayMenuSelectSFX() { AppCodeDmg activeInstance = _activeInstance; if (activeInstance != null) { ((App)activeInstance).m_AudioManager.PlaySfxGameplay((SfxCollectionID)14, (AudioClipID)308, 0f); } } internal static void PlayMenuConfirmSFX() { AppCodeDmg activeInstance = _activeInstance; if (activeInstance != null) { ((App)activeInstance).m_AudioManager.PlaySfxGameplay((SfxCollectionID)14, (AudioClipID)306, 0f); } } internal static void PlayMenuBackSFX() { AppCodeDmg activeInstance = _activeInstance; if (activeInstance != null) { ((App)activeInstance).m_AudioManager.PlaySfxGameplay((SfxCollectionID)14, (AudioClipID)305, 0f); } } internal static void HandleGamePauseStarted() { AppCodeDmg activeInstance = _activeInstance; if ((Object)(object)activeInstance == (Object)null) { return; } Core instance = Core.Instance; if (!((Object)(object)instance != (Object)null) || instance.IsCorePaused) { activeInstance._renderer?.HideSecondPlayerScreen(); activeInstance._emulationTimeAccumulator = 0f; LinkCableManager linkCable = CodeDmgPlugin.LinkCable; if (linkCable != null && (linkCable.State != LinkCableState.Disconnected || linkCable.HasPendingInvite)) { linkCable.Drop(); } } } private void OnLinkCableStateChanged() { _renderer?.RenderLinkCableStatus(CodeDmgPlugin.LinkCable); } private void CheckPendingInviteOnOpen() { LinkCableManager linkCable = CodeDmgPlugin.LinkCable; if (linkCable != null && linkCable.HasPendingInvite) { ShowConnectDialog(); } } private void ShowConnectDialog() { LinkCableManager lc = CodeDmgPlugin.LinkCable; CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; if (configSettings != null && configSettings.LinkCableEnabled.Value && lc != null) { string hostName = lc.PendingInviterName ?? "Host"; LinkCableConnectDialog.Show(hostName, delegate { lc.ClientAcceptInvite(); }, delegate { lc.ClientDeclineInvite(); }); } } internal void OnLinkButtonPressed() { LinkCableManager lc = CodeDmgPlugin.LinkCable; CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; if (configSettings == null || !configSettings.LinkCableEnabled.Value || lc == null) { return; } if (lc.State != LinkCableState.Disconnected) { lc.Drop(); return; } BackupSaveData(); string localName = GetLocalPlayerDisplayName(); LinkCablePlayerList.Show(delegate(ushort targetId, string targetName) { lc.HostSendInvite(targetId, localName, targetName); _renderer?.RenderLinkCableStatus(lc); }); } private static string GetLocalPlayerDisplayName() { try { ClientController instance = ClientController.Instance; if ((Object)(object)instance == (Object)null) { return string.Empty; } ushort localID = instance.LocalID; if (instance.Players == null || !instance.Players.TryGetValue(localID, out var value) || value == null) { return string.Empty; } string playerDisplayName = MPUtility.GetPlayerDisplayName(value.ClientState); return string.IsNullOrWhiteSpace(playerDisplayName) ? string.Empty : playerDisplayName; } catch { } return string.Empty; } private void RebootEmulator() { CodeDmgPlugin.LinkCable?.Drop(); if (_emulator != null) { CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; if (configSettings != null && configSettings.BatterySaveAutoSave.Value) { _emulator.SaveRam(); } string text = _loadedRomPath ?? GetConfiguredRomPath(); string bootRomPath = _emulator.BootRomPath; string savePath = _emulator.SavePath; if ((Object)(object)_audioDriver != (Object)null) { _audioDriver.SetEmulator(null); } _emulator = new CodeDmgEmulator(text, bootRomPath, savePath); if (CodeDmgPlugin.ConfigSettings != null) { _emulator.SetAudioEnabled(CodeDmgPlugin.ConfigSettings.EnableAudio.Value); } CodeDmgConfig configSettings2 = CodeDmgPlugin.ConfigSettings; if (configSettings2 != null && configSettings2.BatterySaveAutoLoad.Value) { _emulator.LoadSaveRam(); } if ((Object)(object)_audioDriver != (Object)null) { _audioDriver.SetEmulator(_emulator); } _loadedRomPath = text; _emulationTimeAccumulator = 0f; _linkHoldTimer = 0f; _linkHoldFired = false; CodeDmgPlugin.LinkCable?.SetApp(this); CodeDmgPlugin.LinkCable?.SetLocalRomHash(_emulator.GetRomHash()); RenderNow(); } } private void OpenMasterMenu() { MasterMenu.Show(delegate { RomSelectMenu.Show(BootRom); }, delegate { OnLinkButtonPressed(); }, delegate { VolumeMenu.Show(); }, delegate { GBPaletteMenu.Show(); }, delegate { RebootEmulator(); }); } internal static bool TryOpenMasterMenuFromRightInput() { AppCodeDmg activeInstance = _activeInstance; if ((Object)(object)activeInstance == (Object)null || activeInstance._emulator == null || activeInstance._renderer == null) { return false; } if (IsChatInputActive() || PopupState.AnyVisible || ShouldBlockRightMenuOpenSignal()) { return false; } activeInstance.OpenMasterMenu(); activeInstance.SuppressEmulatorInputFor(0.2f); PopupState.SuppressPhoneNavFor(); return true; } internal void BootRom(string romPath) { if (string.IsNullOrWhiteSpace(romPath) || !File.Exists(romPath)) { return; } SuppressEmulatorInputFor(0.5f); CodeDmgPlugin.LinkCable?.Drop(); if (_emulator != null) { CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; if (configSettings != null && configSettings.AutoSaveOnClose.Value) { SaveState(); } CodeDmgConfig configSettings2 = CodeDmgPlugin.ConfigSettings; if (configSettings2 != null && configSettings2.BatterySaveAutoSave.Value) { _emulator.SaveRam(); } } if ((Object)(object)_audioDriver != (Object)null) { _audioDriver.SetEmulator(null); } string bootRomPath = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "dmg_boot.bin"); string batterySavePath = GetBatterySavePath(romPath); _emulator = new CodeDmgEmulator(romPath, bootRomPath, batterySavePath); _loadedRomPath = romPath; SaveLastRomPath(romPath); if (CodeDmgPlugin.ConfigSettings != null) { _emulator.SetAudioEnabled(CodeDmgPlugin.ConfigSettings.EnableAudio.Value); } CodeDmgConfig configSettings3 = CodeDmgPlugin.ConfigSettings; if (configSettings3 != null && configSettings3.BatterySaveAutoLoad.Value) { _emulator.LoadSaveRam(); } if ((Object)(object)_audioDriver != (Object)null) { _audioDriver.SetEmulator(_emulator); } _emulationTimeAccumulator = 0f; _linkHoldTimer = 0f; _linkHoldFired = false; CodeDmgConfig configSettings4 = CodeDmgPlugin.ConfigSettings; if (configSettings4 != null && configSettings4.AutoLoadOnOpen.Value) { TryLoadState(); } CodeDmgPlugin.LinkCable?.SetApp(this); CodeDmgPlugin.LinkCable?.SetLocalRomHash(_emulator.GetRomHash()); RenderNow(); } private void BackupSaveData() { if (_emulator == null) { return; } try { string statePath = GetStatePath(_loadedRomPath ?? GetConfiguredRomPath()); if (File.Exists(statePath)) { string destFileName = statePath + ".linkbackup"; File.Copy(statePath, destFileName, overwrite: true); } string romPath = _loadedRomPath ?? GetConfiguredRomPath(); string batterySavePath = GetBatterySavePath(romPath); if (File.Exists(batterySavePath)) { string destFileName2 = batterySavePath + ".linkbackup"; File.Copy(batterySavePath, destFileName2, overwrite: true); } } catch (Exception ex) { Debug.LogWarning((object)("[CODE-DMG] Link cable save backup failed: " + ex.Message)); } } private void TryBootEmulator() { string text = (_loadedRomPath = GetConfiguredRomPath()); string bootRomPath = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "dmg_boot.bin"); string batterySavePath = GetBatterySavePath(text); if (!File.Exists(text)) { Debug.LogWarning((object)("[CODE-DMG] Missing ROM: " + text)); _emulator = null; _loadedRomPath = null; return; } _emulator = new CodeDmgEmulator(text, bootRomPath, batterySavePath); SaveLastRomPath(text); if (ReadBoolFromConfig("SaveStates", "BatterySaveAutoLoad", defaultValue: true)) { _emulator.LoadSaveRam(); } if (CodeDmgPlugin.ConfigSettings != null) { _emulator.SetAudioEnabled(CodeDmgPlugin.ConfigSettings.EnableAudio.Value); } else { _emulator.SetAudioEnabled(enabled: false); } if ((Object)(object)_audioDriver != (Object)null) { _audioDriver.SetEmulator(_emulator); } } private void SaveState() { if (_emulator == null) { return; } string text = ((!string.IsNullOrWhiteSpace(_loadedRomPath)) ? _loadedRomPath : GetConfiguredRomPath()); if (string.IsNullOrWhiteSpace(text)) { return; } try { File.WriteAllBytes(GetStatePath(text), _emulator.SerializeState()); } catch (Exception ex) { Debug.LogWarning((object)("[CODE-DMG] Failed to save state: " + ex.Message)); } } private void TryLoadState() { if (_emulator == null) { return; } string text = ((!string.IsNullOrWhiteSpace(_loadedRomPath)) ? _loadedRomPath : GetConfiguredRomPath()); if (string.IsNullOrWhiteSpace(text)) { return; } string statePath = GetStatePath(text); if (!File.Exists(statePath)) { return; } try { byte[] data = File.ReadAllBytes(statePath); CodeDmgEmulator codeDmgEmulator = new CodeDmgEmulator(text, _emulator.BootRomPath, _emulator.SavePath); if (CodeDmgPlugin.ConfigSettings != null) { codeDmgEmulator.SetAudioEnabled(CodeDmgPlugin.ConfigSettings.EnableAudio.Value); } codeDmgEmulator.DeserializeState(data); _emulator = codeDmgEmulator; _loadedRomPath = text; if ((Object)(object)_audioDriver != (Object)null) { _audioDriver.SetEmulator(_emulator); } } catch (Exception ex) { Debug.LogWarning((object)("[CODE-DMG] Failed to load state: " + ex.Message)); } } private static string GetDataFolder() { string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Bomb Rush Cyberfunk Modding", "BRCGameBoyEmu"); Directory.CreateDirectory(text); return text; } private static string GetSavesFolder() { string text = Path.Combine(GetDataFolder(), "Saves"); Directory.CreateDirectory(text); return text; } private static string GetLastRomPathFile() { return Path.Combine(GetDataFolder(), "LastRomDir"); } private static string ReadLastRomPath() { try { string lastRomPathFile = GetLastRomPathFile(); if (!File.Exists(lastRomPathFile)) { return string.Empty; } string text = NormalizeConfiguredRomPath(File.ReadAllText(lastRomPathFile)); if (string.IsNullOrWhiteSpace(text) || !File.Exists(text)) { return string.Empty; } string text2 = Path.GetExtension(text).ToLowerInvariant(); return (text2 == ".gb" || text2 == ".gbc") ? text : string.Empty; } catch { } return string.Empty; } private static void SaveLastRomPath(string romPath) { try { if (!string.IsNullOrWhiteSpace(romPath) && File.Exists(romPath)) { File.WriteAllText(GetLastRomPathFile(), romPath); } } catch (Exception ex) { Debug.LogWarning((object)("[CODE-DMG] Failed to save last ROM path: " + ex.Message)); } } private static string GetRomTitle(string romPath) { try { using FileStream fileStream = File.OpenRead(romPath); if (fileStream.Length < 323) { throw new Exception("ROM too short."); } fileStream.Seek(308L, SeekOrigin.Begin); byte[] array = new byte[15]; fileStream.Read(array, 0, array.Length); StringBuilder stringBuilder = new StringBuilder(15); byte[] array2 = array; foreach (byte b in array2) { if (b == 0) { break; } if (b >= 32 && b < 127) { stringBuilder.Append((char)b); } } string text = stringBuilder.ToString().Trim(); if (text.Length == 0) { throw new Exception("Empty title."); } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char c in invalidFileNameChars) { text = text.Replace(c.ToString(), ""); } text = text.Trim(); if (text.Length == 0) { throw new Exception("Title all-invalid."); } return text; } catch { return Path.GetFileNameWithoutExtension(romPath); } } private static string GetBatterySavePath(string romPath) { return Path.Combine(GetSavesFolder(), GetRomTitle(romPath) + ".sav"); } private static string GetStatePath(string romPath) { if (string.IsNullOrEmpty(romPath)) { return string.Empty; } return Path.Combine(GetSavesFolder(), GetRomTitle(romPath) + ".state"); } private string GetConfiguredRomPath() { if (CodeDmgPlugin.ConfigSettings != null) { if (CodeDmgPlugin.ConfigSettings.LoadLastPlayed.Value) { string text = ReadLastRomPath(); if (!string.IsNullOrWhiteSpace(text)) { return text; } } string value = CodeDmgPlugin.ConfigSettings.RomPath.Value; string text2 = NormalizeConfiguredRomPath(value); if (!string.Equals(value, text2, StringComparison.Ordinal)) { CodeDmgPlugin.ConfigSettings.RomPath.Value = text2; try { ((BaseUnityPlugin)CodeDmgPlugin.Instance).Config.Save(); } catch (Exception ex) { Debug.LogWarning((object)("[CODE-DMG] Failed to save normalized RomPath: " + ex.Message)); } } if (!string.IsNullOrWhiteSpace(text2) && File.Exists(text2)) { string text3 = Path.GetExtension(text2).ToLowerInvariant(); if (text3 == ".gb" || text3 == ".gbc") { return text2; } } } string text4 = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "rom.gbc"); if (File.Exists(text4)) { return text4; } return Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "rom.gb"); } private static string NormalizeConfiguredRomPath(string path) { if (string.IsNullOrWhiteSpace(path)) { return string.Empty; } string text = path.Trim(); text = text.Replace("\\'", "'"); text = text.Replace("\\\"", "\""); if (text.Length >= 2 && text[0] == '"' && text[text.Length - 1] == '"') { text = text.Substring(1, text.Length - 2); } return text; } private static bool ReadBoolFromConfig(string section, string key, bool defaultValue) { try { string configFilePath = ((BaseUnityPlugin)CodeDmgPlugin.Instance).Config.ConfigFilePath; if (!File.Exists(configFilePath)) { return defaultValue; } bool flag = false; string[] array = File.ReadAllLines(configFilePath); foreach (string text in array) { string text2 = text.Trim(); if (text2.StartsWith("[") && text2.EndsWith("]")) { flag = string.Equals(text2.Substring(1, text2.Length - 2), section, StringComparison.OrdinalIgnoreCase); } else { if (!flag) { continue; } int num = text2.IndexOf('='); if (num < 0) { continue; } string a = text2.Substring(0, num).Trim(); string value = text2.Substring(num + 1).Trim(); if (string.Equals(a, key, StringComparison.OrdinalIgnoreCase)) { if (bool.TryParse(value, out var result)) { return result; } return defaultValue; } } } } catch { } return defaultValue; } private void ShutdownEmulator(bool saveState, bool saveBatteryRam) { if (_emulator != null) { if (saveState) { SaveState(); } if (saveBatteryRam) { _emulator.SaveRam(); } } _emulator = null; _loadedRomPath = null; _emulationTimeAccumulator = 0f; if ((Object)(object)_audioDriver != (Object)null) { _audioDriver.SetEmulator(null); } } private void HandleEmulatorInput(bool suppressStartSelect = false) { //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) if (_emulator != null && CodeDmgPlugin.ConfigSettings != null) { if (Time.unscaledTime <= _suppressEmulatorInputUntil) { ReleaseAllButtons(); return; } if (MasterMenu.IsVisible || LinkCablePlayerList.IsVisible || LinkCableConnectDialog.IsVisible || RomSelectMenu.IsVisible || VolumeMenu.IsVisible || GBPaletteMenu.IsVisible) { ReleaseAllButtons(); return; } if (IgnoreInputForChat()) { ReleaseAllButtons(); return; } CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; float axisRaw = Input.GetAxisRaw("Horizontal"); float axisRaw2 = Input.GetAxisRaw("Vertical"); bool flag = configSettings.SwapButtons != null && configSettings.SwapButtons.Value; _emulator.SetButton(GameBoyButton.A, (!flag) ? (Input.GetKey(configSettings.A.Value) || Input.GetKey((KeyCode)330)) : (Input.GetKey(configSettings.A.Value) || Input.GetKey((KeyCode)331))); _emulator.SetButton(GameBoyButton.B, (!flag) ? (Input.GetKey(configSettings.B.Value) || Input.GetKey((KeyCode)331)) : (Input.GetKey(configSettings.B.Value) || Input.GetKey((KeyCode)330))); _emulator.SetButton(GameBoyButton.Start, !suppressStartSelect && (Input.GetKey(configSettings.Start.Value) || Input.GetKey((KeyCode)333))); _emulator.SetButton(GameBoyButton.Select, !suppressStartSelect && (Input.GetKey(configSettings.Select.Value) || Input.GetKey((KeyCode)332))); _emulator.SetButton(GameBoyButton.Right, Input.GetKey(configSettings.Right.Value) || axisRaw > 0.5f); _emulator.SetButton(GameBoyButton.Left, Input.GetKey(configSettings.Left.Value) || axisRaw < -0.5f); _emulator.SetButton(GameBoyButton.Up, Input.GetKey(configSettings.Up.Value) || axisRaw2 > 0.5f); _emulator.SetButton(GameBoyButton.Down, Input.GetKey(configSettings.Down.Value) || axisRaw2 < -0.5f); } } internal static void BeginRightMenuOpenReleaseGuard() { float unscaledTime = Time.unscaledTime; _rightMenuOpenProbeUntil = unscaledTime + 0.25f; _rightMenuOpenBlockedUntilRelease = Input.GetKey((KeyCode)275); } private static void UpdateRightMenuOpenReleaseGuard() { if (_rightMenuOpenBlockedUntilRelease && !Input.GetKey((KeyCode)275)) { _rightMenuOpenBlockedUntilRelease = false; } } internal static bool ShouldBlockRightMenuOpenSignal() { if (_rightMenuOpenBlockedUntilRelease) { return true; } if (Time.unscaledTime <= _rightMenuOpenProbeUntil) { _rightMenuOpenBlockedUntilRelease = true; return true; } return false; } internal static void MarkRightMenuOpenReleased() { _rightMenuOpenBlockedUntilRelease = false; _rightMenuOpenProbeUntil = -1f; } internal static bool IsChatInputActive() { if (TryGetSlopChatInputBlocked(out var blocked) && blocked) { return true; } if ((Object)(object)EventSystem.current != (Object)null) { GameObject currentSelectedGameObject = EventSystem.current.currentSelectedGameObject; if ((Object)(object)currentSelectedGameObject != (Object)null) { if ((Object)(object)currentSelectedGameObject.GetComponent() != (Object)null) { return true; } if ((Object)(object)currentSelectedGameObject.GetComponent() != (Object)null) { return true; } } } return false; } private bool IgnoreInputForChat() { bool flag = IsChatInputActive(); if (_wasChatActive && !flag) { SuppressEmulatorInputFor(0.3f); _wasChatActive = false; return true; } _wasChatActive = flag; return flag; } private static bool TryGetSlopChatInputBlocked(out bool blocked) { blocked = false; try { if (!_slopChatReflectionInitialized) { _slopChatReflectionInitialized = true; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = assembly.GetType("SlopChat.InputUtils", throwOnError: false); if (!(type == null)) { _slopChatInputBlockedProperty = type.GetProperty("InputBlocked", BindingFlags.Static | BindingFlags.Public); break; } } } if (_slopChatInputBlockedProperty == null) { return false; } if (_slopChatInputBlockedProperty.GetValue(null, null) is bool flag) { blocked = flag; return true; } } catch { } return false; } private void SuppressEmulatorInputFor(float seconds) { _suppressEmulatorInputUntil = Mathf.Max(_suppressEmulatorInputUntil, Time.unscaledTime + seconds); ReleaseAllButtons(); } private void ReleaseAllButtons() { _emulator.SetButton(GameBoyButton.A, pressed: false); _emulator.SetButton(GameBoyButton.B, pressed: false); _emulator.SetButton(GameBoyButton.Start, pressed: false); _emulator.SetButton(GameBoyButton.Select, pressed: false); _emulator.SetButton(GameBoyButton.Right, pressed: false); _emulator.SetButton(GameBoyButton.Left, pressed: false); _emulator.SetButton(GameBoyButton.Up, pressed: false); _emulator.SetButton(GameBoyButton.Down, pressed: false); } private void RenderNow() { _renderer?.Render(_emulator); } private void FlushCurrentPlayerInput() { WorldHandler instance = WorldHandler.instance; Player val = ((instance != null) ? instance.GetCurrentPlayer() : null); if ((Object)(object)val != (Object)null) { val.FlushInput(); } } } [RequireComponent(typeof(AudioSource))] public sealed class CodeDmgAudioDriver : MonoBehaviour { private AudioSource _audioSource; private CodeDmgEmulator _emulator; private bool _muted = true; private float[] _stereoTemp = new float[0]; private float _lastLeft; private float _lastRight; private void Awake() { _audioSource = ((Component)this).GetComponent(); _audioSource.playOnAwake = true; _audioSource.loop = true; _audioSource.mute = false; _audioSource.volume = 1f; _audioSource.pitch = 1f; _audioSource.spatialBlend = 0f; _audioSource.dopplerLevel = 0f; _audioSource.spread = 0f; _audioSource.reverbZoneMix = 0f; _audioSource.bypassEffects = true; _audioSource.bypassListenerEffects = true; _audioSource.bypassReverbZones = true; _audioSource.outputAudioMixerGroup = null; if (!_audioSource.isPlaying) { _audioSource.Play(); } } public void SetEmulator(CodeDmgEmulator emulator) { _emulator = emulator; } public void SetMuted(bool muted) { _muted = muted; } private void OnAudioFilterRead(float[] data, int channels) { if (channels <= 0) { return; } int num = data.Length / channels; int num2 = num * 2; if (_stereoTemp.Length != num2) { _stereoTemp = new float[num2]; } if (_muted || _emulator == null || _emulator.Apu == null || !_emulator.AudioEnabled) { WriteHeldSamples(data, channels, num, 0f); return; } float num3 = 1f; if (CodeDmgPlugin.ConfigSettings != null) { num3 = Mathf.Clamp01((float)CodeDmgPlugin.ConfigSettings.Volume.Value / 100f); } int num4 = _emulator.Apu.ReadSamples(_stereoTemp, 0, num2); for (int i = num4; i < num2; i += 2) { _stereoTemp[i] = _lastLeft; if (i + 1 < num2) { _stereoTemp[i + 1] = _lastRight; } } if (num2 >= 2) { _lastLeft = _stereoTemp[num2 - 2]; _lastRight = _stereoTemp[num2 - 1]; } if (channels == 1) { for (int j = 0; j < num; j++) { float num5 = _stereoTemp[j * 2]; float num6 = _stereoTemp[j * 2 + 1]; data[j] = (num5 + num6) * 0.5f * num3; } return; } for (int k = 0; k < num; k++) { float num7 = _stereoTemp[k * 2] * num3; float num8 = _stereoTemp[k * 2 + 1] * num3; int num9 = k * channels; data[num9] = num7; data[num9 + 1] = num8; float num10 = (num7 + num8) * 0.5f; for (int l = 2; l < channels; l++) { data[num9 + l] = num10; } } } private void WriteHeldSamples(float[] data, int channels, int frameCount, float gain) { if (channels == 1) { float num = (_lastLeft + _lastRight) * 0.5f * gain; for (int i = 0; i < frameCount; i++) { data[i] = num; } return; } float num2 = _lastLeft * gain; float num3 = _lastRight * gain; for (int j = 0; j < frameCount; j++) { int num4 = j * channels; data[num4] = num2; data[num4 + 1] = num3; float num5 = (num2 + num3) * 0.5f; for (int k = 2; k < channels; k++) { data[num4 + k] = num5; } } } } public class CodeDmgConfig { public ConfigEntry RomPath; public ConfigEntry RomDirectory; public ConfigEntry LoadLastPlayed; public ConfigEntry Up; public ConfigEntry Down; public ConfigEntry Left; public ConfigEntry Right; public ConfigEntry A; public ConfigEntry B; public ConfigEntry Start; public ConfigEntry Select; public ConfigEntry SwapButtons; public ConfigEntry AutoSaveOnClose; public ConfigEntry AutoLoadOnOpen; public ConfigEntry BatterySaveAutoSave; public ConfigEntry BatterySaveAutoLoad; public ConfigEntry BackgroundColor; public ConfigEntry PixelGrid; public ConfigEntry Palette; public ConfigEntry EnableAudio; public ConfigEntry Volume; public ConfigEntry AudioLatency; public ConfigEntry LinkCableEnabled; public ConfigEntry LinkInputDelayFrames; public ConfigEntry HideLinkStatusWhenDisconnected; public ConfigEntry ShowPeerScreen; public ConfigEntry PeerScreenPalette; public CodeDmgConfig(ConfigFile config) { //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Expected O, but got Unknown //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02e6: Expected O, but got Unknown //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Expected O, but got Unknown RomPath = config.Bind("Change Game ROM", "RomPath", "", "Path to ROM file (.gb or .gbc). Leave empty to use rom.gbc / rom.gb in plugin folder."); RomDirectory = config.Bind("Change Game ROM", "RomDirectory", "", "Folder to scan for ROM files shown in the Change Game menu. \n Leave this field empty to use the default ROMs folder: \n \\Documents\\Bomb Rush Cyberfunk Modding\\BRCGameBoyEmu\\Roms"); LoadLastPlayed = config.Bind("Change Game ROM", "LoadLastPlayed", true, "Load last ROM played automatically when booting the emulator. Overrides the RomPath above."); Up = config.Bind("Controls", "Up", (KeyCode)119, "D-pad Up"); Down = config.Bind("Controls", "Down", (KeyCode)115, "D-pad Down"); Left = config.Bind("Controls", "Left", (KeyCode)97, "D-pad Left"); Right = config.Bind("Controls", "Right", (KeyCode)100, "D-pad Right"); A = config.Bind("Controls", "A", (KeyCode)46, "GB A button"); B = config.Bind("Controls", "B", (KeyCode)44, "GB B button"); Start = config.Bind("Controls", "Start", (KeyCode)13, "GB Start button"); Select = config.Bind("Controls", "Select", (KeyCode)303, "GB Select button"); SwapButtons = config.Bind("Controls", "SwapButtons", false, "Swap A/B buttons on controllers. Useful for pads that use a Switch button layout, but are in XInput mode."); AutoSaveOnClose = config.Bind("SaveStates", "AutoSaveOnClose", true, "Automatically save state when closing the app."); AutoLoadOnOpen = config.Bind("SaveStates", "AutoLoadOnOpen", true, "Automatically load state when opening the app."); BatterySaveAutoSave = config.Bind("SaveStates", "BatterySaveAutoSave", true, "Automatically write the battery save (.sav) file when closing the app."); BatterySaveAutoLoad = config.Bind("SaveStates", "BatterySaveAutoLoad", true, "Automatically load the battery save (.sav) file when starting the emulator."); Palette = config.Bind("Display", "Palette", "dmg", new ConfigDescription("Select the palette used for Game Boy games. Default is the original olive-green DMG palette.", (AcceptableValueBase)(object)new AcceptableValueList(Helper.PaletteNames), Array.Empty())); BackgroundColor = config.Bind("Display", "BackgroundColor", "#000000", "Background color behind the Game Boy screen, as a hex value (e.g. #000000 for black, #1a1a2e for dark navy gray)."); PixelGrid = config.Bind("Display", "PixelGrid", false, "Enable a GBC-style pixel grid overlay on the screen."); EnableAudio = config.Bind("Music/Audio", "EnableAudio", true, "Enable Game Boy audio emulation."); Volume = config.Bind("Music/Audio", "Volume", 80, new ConfigDescription("Audio volume (0 = silent, 100 = full volume).", (AcceptableValueBase)(object)new AcceptableValueRange(0, 100), Array.Empty())); AudioLatency = config.Bind("Music/Audio", "AudioLatency", "Normal", new ConfigDescription("Audio latency preset. The lower the value, the more chance for popping.", (AcceptableValueBase)(object)new AcceptableValueList(new string[4] { "Very High", "High", "Normal", "Low" }), Array.Empty())); LinkCableEnabled = config.Bind("Link Cable", "LinkCableEnabled", true, "Emulate the Game Boy Link Cable. Requires ACN. \n (Experimental feature!! Backup your saves before using!!)"); LinkInputDelayFrames = config.Bind("Link Cable", "LinkInputDelayFrames", 8, "Input delay setting for Link Cable emulation. The higher the value, the higher the input delay. Lower values will make gameplay choppy, lag, and much more likely to desync. Host's value is used."); HideLinkStatusWhenDisconnected = config.Bind("Link Cable", "HideLinkStatusWhenDisconnected", true, "Hide the Link Cable status text when not connected with another player."); ShowPeerScreen = config.Bind("Link Cable", "ShowPeerScreen", true, "Renders the second GB / GBC emulator used for link cable emulation. This emulator attempts to clone the other player's emulator state, but can desync. Disable for a slight performance increase."); PeerScreenPalette = config.Bind("Link Cable", "PeerScreenPalette", "bombrush-blue", new ConfigDescription("Select the palette used by the second emulator. Default is a BRC-inspired DMG palette.", (AcceptableValueBase)(object)new AcceptableValueList(Helper.PaletteNames), Array.Empty())); } public int GetSampleFifoSize() { string text = AudioLatency?.Value; if (string.IsNullOrWhiteSpace(text)) { return 8192; } return text.Trim().ToLowerInvariant() switch { "very high" => 32768, "high" => 16384, "low" => 4096, _ => 8192, }; } } public sealed class CodeDmgEmulator { private struct LinkJoypadPacket { public readonly uint Frame; public readonly byte State; public LinkJoypadPacket(uint frame, byte state) { Frame = frame; State = state; } } private const int CyclesPerFrameNormal = 70224; private const int SaveStateVersion = 4; private const int DefaultLinkInputDelayFrames = 4; private const int RollbackDepth = 16; private const int MaxBufferedLinkInputs = 240; private int _linkInputDelayFrames = 4; private byte _localJoypad = byte.MaxValue; private uint _linkFrame; private uint _nextQueuedLocalInputFrame; private readonly Dictionary _localInputFrames = new Dictionary(); private readonly Dictionary _peerInputFrames = new Dictionary(); private readonly Queue _pendingJoypadPackets = new Queue(); private ushort _mainLinkPlayerId; private ushort _linkedLinkPlayerId; private int _activeSerialMaster; private byte _lastKnownPeerJoypad = byte.MaxValue; private readonly Dictionary _predictedPeerInputs = new Dictionary(); private int _linkCycleAccum; private const int SnapshotSlotSize = 524288; private readonly byte[][] _snapshotSlots = new byte[16][]; private readonly uint[] _snapshotFrames = new uint[16]; private readonly Dictionary _unverifiedPredictions = new Dictionary(); private readonly List _confirmedFramesScratch = new List(32); private readonly List _pruneScratch = new List(64); private readonly MemoryStream _snapshotStream = new MemoryStream(524288); private readonly int[] _snapshotLengths = new int[16]; private readonly string _savePath; private readonly string _romPath; private readonly string _bootRomPath; private readonly string _romTitle; internal int _lastCycles = 4; private MMU Mmu { get; } private CPU Cpu { get; } public PPU Ppu { get; } public APU Apu { get; } private Joypad Joypad { get; } private Timer Timer { get; } public bool AudioEnabled { get; private set; } = false; internal CodeDmgEmulator LinkedEmulator { get; private set; } public bool DesyncDetected { get; internal set; } public string RomPath => _romPath; public string BootRomPath => _bootRomPath; public string SavePath => _savePath; public bool FrameDirty => Ppu.FrameDirty; public bool IsTetrisRom => _romTitle.StartsWith("TETRIS", StringComparison.OrdinalIgnoreCase); public CodeDmgEmulator(string romPath, string bootRomPath, string savePath) { _romPath = romPath; _bootRomPath = bootRomPath; _savePath = savePath; byte[] array = File.ReadAllBytes(romPath); _romTitle = ReadRomTitle(array); byte[] bootRomData = (File.Exists(bootRomPath) ? File.ReadAllBytes(bootRomPath) : new byte[256]); Helper.scale = 1; Helper.paletteName = CodeDmgPlugin.ConfigSettings?.Palette?.Value; Mmu = new MMU(array, bootRomData, flatRamMode: false); Cpu = new CPU(Mmu); Ppu = new PPU(Mmu); Joypad = new Joypad(Mmu); Apu = new APU(Mmu, AudioSettings.outputSampleRate); Timer = new Timer(Mmu, Apu); Mmu.Apu = Apu; Mmu.Timer = Timer; if (!File.Exists(bootRomPath)) { bool flag = array.Length > 323 && (array[323] == 128 || array[323] == 192); Cpu.Reset(flag); if (flag) { Mmu.InitializeCGBRegisters(); Mmu.NR52 = 0; Apu.WriteRegister(65318, 128); Apu.WriteRegister(65316, 119); Apu.WriteRegister(65317, 243); } } } public void SetAudioEnabled(bool enabled) { AudioEnabled = enabled; Apu?.SetEnabled(enabled); } public byte ReadSerialData() { return Mmu.ReadSerialData(); } public bool ShouldYieldForSerialLink() { return CodeDmgPlugin.LinkCable?.WaitingForSync ?? false; } public void TickSerialLinkWait(float deltaSeconds) { } public void DeliverRemoteSerialByte(byte value) { Mmu.DeliverRemoteSerialByte(value); } public void CancelSerialTransfer() { Mmu.CancelSerial(); Mmu.IF = (byte)(Mmu.IF & -9); } private static string ReadRomTitle(byte[] rom) { if (rom == null || rom.Length < 323) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(16); for (int i = 308; i < 323; i++) { byte b = rom[i]; if (b == 0) { break; } if (b >= 32 && b < 127) { stringBuilder.Append((char)b); } } return stringBuilder.ToString().Trim(); } public string GetRomHash() { try { byte[] array = File.ReadAllBytes(_romPath); if (array.Length < 323) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(16); for (int i = 308; i < 323; i++) { byte b = array[i]; if (b == 0) { break; } if (b >= 32 && b < 127) { stringBuilder.Append((char)b); } } uint num = ComputeCrc32Public(array); return stringBuilder.ToString().Trim() + ":" + num.ToString("X8"); } catch { return string.Empty; } } internal static uint ComputeCrc32Public(byte[] data) { uint num = uint.MaxValue; foreach (byte b in data) { num ^= b; for (int j = 0; j < 8; j++) { num = (((num & 1) != 0) ? ((num >> 1) ^ 0xEDB88320u) : (num >> 1)); } } return num ^ 0xFFFFFFFFu; } public void StepFrame() { if (LinkedEmulator != null) { StepLinkedFrame(); return; } int num = 0; int num2 = (Mmu.CgbDoubleSpeed ? 140448 : 70224); int num3 = 4; while (num < num2) { int num4 = (Mmu.CgbDoubleSpeed ? (num3 / 2) : num3); Mmu.StepSerialLink(num3); Timer.Step(num3); Apu.Step(num4); Ppu.Step(num4); int num5 = Cpu.ExecuteInstruction(); num += num5; num3 = num5; } } private void StepLinkedFrame() { QueueLocalInputForFutureFrame(); FlushJoypadPackets(); ProcessConfirmedInputs(); if (!_localInputFrames.TryGetValue(_linkFrame, out var value)) { value = _localJoypad; } byte value2; bool flag = _peerInputFrames.TryGetValue(_linkFrame, out value2); if (!flag) { value2 = _lastKnownPeerJoypad; } else { _lastKnownPeerJoypad = value2; } if (!_predictedPeerInputs.ContainsKey(_linkFrame)) { _predictedPeerInputs[_linkFrame] = value2; if (!flag) { _unverifiedPredictions[_linkFrame] = value2; } } SaveRollbackSnapshot(_linkFrame); Joypad.SetStateRaw(value); LinkedEmulator.SetJoypadStateRaw(value2); StepBothEmulators(); PruneLinkInputs(_linkFrame); _linkFrame++; } private void ProcessConfirmedInputs() { uint num = uint.MaxValue; _confirmedFramesScratch.Clear(); foreach (KeyValuePair unverifiedPrediction in _unverifiedPredictions) { uint key = unverifiedPrediction.Key; if (_peerInputFrames.TryGetValue(key, out var value)) { _confirmedFramesScratch.Add(key); if (value != unverifiedPrediction.Value && key < num) { num = key; } } } for (int i = 0; i < _confirmedFramesScratch.Count; i++) { _unverifiedPredictions.Remove(_confirmedFramesScratch[i]); } if (num == uint.MaxValue) { return; } if (_linkFrame - num > 16) { if (_peerInputFrames.TryGetValue(num, out var value2)) { _lastKnownPeerJoypad = value2; } DesyncDetected = true; return; } int num2 = (int)(num % 16); if (_snapshotFrames[num2] != num) { return; } uint num3 = num; uint linkFrame = _linkFrame; RestoreRollbackSnapshot(num3); _linkFrame = num3; _unverifiedPredictions.Clear(); while (_linkFrame < linkFrame) { if (!_localInputFrames.TryGetValue(_linkFrame, out var value3)) { value3 = byte.MaxValue; } if (_peerInputFrames.TryGetValue(_linkFrame, out var value4)) { _lastKnownPeerJoypad = value4; } else { value4 = _lastKnownPeerJoypad; } _predictedPeerInputs[_linkFrame] = value4; _unverifiedPredictions[_linkFrame] = value4; SaveRollbackSnapshot(_linkFrame); Joypad.SetStateRaw(value3); LinkedEmulator.SetJoypadStateRaw(value4); StepBothEmulators(); PruneLinkInputs(_linkFrame); _linkFrame++; } } public void SetButton(GameBoyButton button, bool pressed) { if (LinkedEmulator != null) { byte b = (byte)button; if (pressed) { _localJoypad &= (byte)(~b); } else { _localJoypad |= b; } } else { Joypad.SetButton(button, pressed); } } public byte GetJoypadState() { return Joypad.GetState(); } internal void SetJoypadStateRaw(byte state) { Joypad.SetStateRaw(state); } private void StepBothEmulators() { int num = 0; int num2 = 0; int num3 = (Mmu.CgbDoubleSpeed ? 140448 : 70224); int num4 = (LinkedEmulator.Mmu.CgbDoubleSpeed ? 140448 : 70224); while (num < num3 || num2 < num4) { if ((num <= num2 && num < num3) || num2 >= num4) { int num5 = StepSelfOneInstruction(); num += num5; LocalLinkStep(mainStepped: true, num5); } else { int num6 = LinkedEmulator.StepSelfOneInstruction(); num2 += num6; LocalLinkStep(mainStepped: false, num6); } } } private void FlushJoypadPackets() { LinkCableManager linkCable = CodeDmgPlugin.LinkCable; if (linkCable != null && linkCable.State == LinkCableState.Connected) { uint frame; byte state; while (TryDequeueJoypadPacket(out frame, out state)) { linkCable.FlushJoypadPacket(frame, state); } } } internal bool TryDequeueJoypadPacket(out uint frame, out byte state) { if (_pendingJoypadPackets.Count == 0) { frame = 0u; state = byte.MaxValue; return false; } LinkJoypadPacket linkJoypadPacket = _pendingJoypadPackets.Dequeue(); frame = linkJoypadPacket.Frame; state = linkJoypadPacket.State; return true; } internal void QueuePeerJoypad(uint frame, byte state) { if (frame + 240 >= _linkFrame) { _peerInputFrames[frame] = state; PruneLinkInputs(_linkFrame); } } internal void SetLinkPlayerIds(ushort mainPlayerId, ushort linkedPlayerId) { _mainLinkPlayerId = mainPlayerId; _linkedLinkPlayerId = linkedPlayerId; } internal void SetLinkInputDelayFrames(int frames) { int num = NormalizeLinkInputDelayFrames(frames); if (_linkInputDelayFrames != num) { _linkInputDelayFrames = num; if (LinkedEmulator != null) { ResetLocalLinkState(); } } } internal static int NormalizeLinkInputDelayFrames(int frames) { if (frames < 1) { return 1; } if (frames > 60) { return 60; } return frames; } internal void AttachLinkedEmulator(string romPath, string bootRomPath, string savePath) { DetachLinkedEmulator(); CodeDmgEmulator codeDmgEmulator = new CodeDmgEmulator(romPath, bootRomPath, savePath); codeDmgEmulator.SetAudioEnabled(enabled: false); codeDmgEmulator.Mmu.IsLocalLinked = true; codeDmgEmulator.Ppu.PaletteOverride = Helper.NormalizePaletteName(CodeDmgPlugin.ConfigSettings?.PeerScreenPalette?.Value, "bombrush-blue"); LinkedEmulator = codeDmgEmulator; ResetLocalLinkState(); } internal void DetachLinkedEmulator() { LinkedEmulator = null; ResetLocalLinkState(); } internal void DeserializeLinkedState(byte[] data) { if (LinkedEmulator == null || data == null) { return; } using (MemoryStream input = new MemoryStream(data)) { using BinaryReader binaryReader = new BinaryReader(input); int num = binaryReader.ReadInt32(); if (num != 4) { return; } binaryReader.ReadString(); binaryReader.ReadString(); LinkedEmulator.Mmu.LoadState(binaryReader); LinkedEmulator.Cpu.LoadState(binaryReader); LinkedEmulator.Ppu.LoadState(binaryReader); LinkedEmulator.Joypad.LoadState(binaryReader); LinkedEmulator.Timer.LoadState(binaryReader); LinkedEmulator.Apu.LoadState(binaryReader); } Debug.Log((object)"[CodeDMG] Linked emulator state loaded from peer savestate"); } private int StepSelfOneInstruction() { int num = (Mmu.CgbDoubleSpeed ? (_lastCycles / 2) : _lastCycles); Timer.Step(_lastCycles); Apu.Step(num); Ppu.Step(num); return _lastCycles = Cpu.ExecuteInstruction(); } private void LocalLinkStep(bool mainStepped, int cycles) { if (LinkedEmulator == null || cycles <= 0) { return; } if (_activeSerialMaster != 0 && !IsActiveMasterStillRequesting()) { _activeSerialMaster = 0; _linkCycleAccum = 0; } if (_activeSerialMaster == 0) { _activeSerialMaster = PickSerialMaster(); } if (_activeSerialMaster == 0 || _activeSerialMaster == 1 != mainStepped) { return; } MMU mMU = ((_activeSerialMaster == 1) ? Mmu : LinkedEmulator.Mmu); MMU mMU2 = ((_activeSerialMaster == 1) ? LinkedEmulator.Mmu : Mmu); _linkCycleAccum += cycles; int serialClockCycles = mMU.SerialClockCycles; while (_linkCycleAccum >= serialClockCycles && IsSerialMasterRequesting(mMU)) { _linkCycleAccum -= serialClockCycles; int inBit = (mMU.SB >> 7) & 1; int inBit2 = mMU2.ShiftBit(inBit); mMU.ShiftBit(inBit2); if (!IsSerialMasterRequesting(mMU)) { _activeSerialMaster = 0; _linkCycleAccum = 0; break; } } } private int PickSerialMaster() { bool flag = IsSerialMasterRequesting(Mmu); bool flag2 = IsSerialMasterRequesting(LinkedEmulator.Mmu); if (flag && !flag2) { return 1; } if (flag2 && !flag) { return 2; } if (!flag) { return 0; } if (_mainLinkPlayerId != 0 && _linkedLinkPlayerId != 0) { return (_mainLinkPlayerId <= _linkedLinkPlayerId) ? 1 : 2; } return 1; } private bool IsActiveMasterStillRequesting() { if (_activeSerialMaster == 1) { return IsSerialMasterRequesting(Mmu); } if (_activeSerialMaster == 2 && LinkedEmulator != null) { return IsSerialMasterRequesting(LinkedEmulator.Mmu); } return false; } private static bool IsSerialMasterRequesting(MMU mmu) { return mmu != null && (mmu.SC & 0x81) == 129; } private void QueueLocalInputForFutureFrame() { uint num = _linkFrame + (uint)_linkInputDelayFrames; if (num >= _nextQueuedLocalInputFrame) { while (_nextQueuedLocalInputFrame <= num) { _localInputFrames[_nextQueuedLocalInputFrame] = _localJoypad; _pendingJoypadPackets.Enqueue(new LinkJoypadPacket(_nextQueuedLocalInputFrame, _localJoypad)); _nextQueuedLocalInputFrame++; } PruneLinkInputs(_linkFrame); } } private void ResetLocalLinkState() { _linkFrame = 0u; _nextQueuedLocalInputFrame = (uint)_linkInputDelayFrames; _activeSerialMaster = 0; _linkCycleAccum = 0; _lastCycles = 4; if (LinkedEmulator != null) { LinkedEmulator._lastCycles = 4; } DesyncDetected = false; _localInputFrames.Clear(); _peerInputFrames.Clear(); _pendingJoypadPackets.Clear(); for (int i = 0; i < 16; i++) { if (_snapshotSlots[i] == null) { _snapshotSlots[i] = new byte[524288]; } _snapshotFrames[i] = uint.MaxValue; _snapshotLengths[i] = 0; } _unverifiedPredictions.Clear(); _predictedPeerInputs.Clear(); _lastKnownPeerJoypad = byte.MaxValue; for (uint num = 0u; num < _linkInputDelayFrames; num++) { _localInputFrames[num] = byte.MaxValue; _peerInputFrames[num] = byte.MaxValue; } Mmu.LinkReset(); LinkedEmulator?.Mmu.LinkReset(); } private void PruneLinkInputs(uint frame) { if (frame > 240) { uint minFrame = frame - 240; PruneInputDictionary(_localInputFrames, minFrame); PruneInputDictionary(_peerInputFrames, minFrame); PruneInputDictionary(_predictedPeerInputs, minFrame); PruneInputDictionary(_unverifiedPredictions, minFrame); } } private void PruneInputDictionary(Dictionary dict, uint minFrame) { if (dict.Count == 0) { return; } _pruneScratch.Clear(); foreach (KeyValuePair item in dict) { if (item.Key < minFrame) { _pruneScratch.Add(item.Key); } } for (int i = 0; i < _pruneScratch.Count; i++) { dict.Remove(_pruneScratch[i]); } } private void SaveRollbackSnapshot(uint frame) { int num = (int)(frame % 16); if (_snapshotSlots[num] == null) { _snapshotSlots[num] = new byte[524288]; } byte[] array = _snapshotSlots[num]; _snapshotFrames[num] = frame; _snapshotStream.SetLength(0L); using (BinaryWriter binaryWriter = new BinaryWriter(_snapshotStream, Encoding.UTF8, leaveOpen: true)) { Mmu.SaveState(binaryWriter); Cpu.SaveState(binaryWriter); Ppu.SaveStateNoBuffers(binaryWriter); Timer.SaveState(binaryWriter); binaryWriter.Write(_lastCycles); binaryWriter.Write(_linkCycleAccum); binaryWriter.Write(_activeSerialMaster); LinkedEmulator.Mmu.SaveState(binaryWriter); LinkedEmulator.Cpu.SaveState(binaryWriter); LinkedEmulator.Ppu.SaveStateNoBuffers(binaryWriter); LinkedEmulator.Timer.SaveState(binaryWriter); binaryWriter.Write(LinkedEmulator._lastCycles); } int num2 = (int)_snapshotStream.Length; if (array.Length < num2) { array = new byte[num2 + 65536]; _snapshotSlots[num] = array; } Array.Copy(_snapshotStream.GetBuffer(), 0, array, 0, num2); _snapshotLengths[num] = num2; if (frame >= 16) { _predictedPeerInputs.Remove(frame - 16); } } private void RestoreRollbackSnapshot(uint frame) { int num = (int)(frame % 16); if (_snapshotFrames[num] != frame) { return; } byte[] buffer = _snapshotSlots[num]; int num2 = _snapshotLengths[num]; if (num2 == 0) { return; } using MemoryStream input = new MemoryStream(buffer, 0, num2, writable: false); using BinaryReader binaryReader = new BinaryReader(input); Mmu.LoadState(binaryReader); Cpu.LoadState(binaryReader); Ppu.LoadStateNoBuffers(binaryReader); Timer.LoadState(binaryReader); _lastCycles = binaryReader.ReadInt32(); _linkCycleAccum = binaryReader.ReadInt32(); _activeSerialMaster = binaryReader.ReadInt32(); LinkedEmulator.Mmu.LoadState(binaryReader); LinkedEmulator.Cpu.LoadState(binaryReader); LinkedEmulator.Ppu.LoadStateNoBuffers(binaryReader); LinkedEmulator.Timer.LoadState(binaryReader); LinkedEmulator._lastCycles = binaryReader.ReadInt32(); } public void SaveRam() { Mmu.Save(_savePath); } public void LoadSaveRam() { Mmu.Load(_savePath); } public byte[] SerializeState() { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(4); binaryWriter.Write(_romPath ?? string.Empty); binaryWriter.Write(_bootRomPath ?? string.Empty); Mmu.SaveState(binaryWriter); Cpu.SaveState(binaryWriter); Ppu.SaveState(binaryWriter); Joypad.SaveState(binaryWriter); Timer.SaveState(binaryWriter); Apu.SaveState(binaryWriter); binaryWriter.Flush(); return memoryStream.ToArray(); } public void DeserializeState(byte[] data, bool skipPathCheck = false) { using MemoryStream input = new MemoryStream(data); using BinaryReader binaryReader = new BinaryReader(input); int num = binaryReader.ReadInt32(); if (num != 4) { throw new InvalidOperationException("Unsupported savestate version: " + num); } string text = binaryReader.ReadString(); string text2 = binaryReader.ReadString(); if (!skipPathCheck && !string.Equals(text ?? string.Empty, _romPath ?? string.Empty, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("Savestate ROM mismatch."); } Mmu.LoadState(binaryReader); Cpu.LoadState(binaryReader); Ppu.LoadState(binaryReader); Joypad.LoadState(binaryReader); Timer.LoadState(binaryReader); Apu.LoadState(binaryReader); } } [BepInPlugin("transrights.codedmg", "CodeDMG", "1.6.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class CodeDmgPlugin : BaseUnityPlugin { public static CodeDmgConfig ConfigSettings; private Harmony _harmony; private bool _wasGamePaused; public static CodeDmgPlugin Instance { get; private set; } public static LinkCableTransport LinkTransport { get; private set; } public static LinkCableManager LinkCable { get; private set; } public string PluginDirectory => Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); private void Awake() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown Instance = this; ConfigSettings = new CodeDmgConfig(((BaseUnityPlugin)this).Config); _harmony = new Harmony("transrights.codedmg"); try { _harmony.PatchAll(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("CodeDMG Harmony patch failed: " + ex)); } if (ConfigSettings.LinkCableEnabled.Value && IsBombRushMPPresent()) { try { LinkTransport = new LinkCableTransport(); LinkCable = new LinkCableManager(LinkTransport); } catch { LinkTransport = null; LinkCable = null; } } AppCodeDmg.Initialize(); ((BaseUnityPlugin)this).Logger.LogMessage((object)"CodeDMG loaded."); } private void Update() { Core instance = Core.Instance; bool flag = (Object)(object)instance != (Object)null && instance.IsCorePaused; if (flag && !_wasGamePaused) { AppCodeDmg.HandleGamePauseStarted(); } _wasGamePaused = flag; } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } LinkCable?.Dispose(); LinkTransport?.Dispose(); } private static bool IsBombRushMPPresent() { try { Type type = Type.GetType("BombRushMP.Plugin.ClientController, BombRushMP.Plugin"); return type != null; } catch { return false; } } public static bool IsBombRushMPReady() { try { Type type = Type.GetType("BombRushMP.Plugin.ClientController, BombRushMP.Plugin"); if (type == null) { return false; } return type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) != null; } catch { return false; } } } public sealed class CodeDmgRenderer { private readonly AppCodeDmg _app; private GameObject _root; private RectTransform _rootRect; private RawImage _screenImage; private Texture2D _screenTexture; private RawImage _gridOverlay; private TextMeshProUGUI _missingRomText; private const int GbWidth = 160; private const int GbHeight = 144; private const int Scale = 6; private Image _fullScreenBlack; private RectTransform _fullScreenBlackRect; private RawImage _logoImage; private Color32[] _blitBuffer; private Canvas _p2Canvas; private RectTransform _p2PhoneRect; private RawImage _p2PhoneImage; private RawImage _p2ScreenImage; private Texture2D _p2ScreenTexture; private RectTransform _p2MiniLogoRect; private RawImage _p2MiniLogoImage; private Color32[] _p2BlitBuffer; private float _p2PhoneWidth; private float _p2PhoneHeight; private TextMeshProUGUI _linkStatusText; private TextMeshProUGUI _linkDelayText; private TextMeshProUGUI _holdHintText; private TextMeshProUGUI _romMismatchText; private TextMeshProUGUI _desyncWarningText; private float _linkPendingAnimTimer; private int _linkPendingAnimStep; private static TMP_FontAsset _pressStartFont; private static bool _pressStartFontLoadAttempted; private const float P2PhoneAspect = 0.7529138f; private const float P2ScreenUMin = 0.0712f; private const float P2ScreenUMax = 0.9288f; private const float P2ScreenVMin = 0.1958f; private const float P2ScreenVMax = 0.8834f; private const float P2ScreenYOffset = 4f; private const float P2MiniLogoGap = 6f; private static Color LinkBlue => HexColor(3299750); private static Color LinkBlueSecondary => HexColor(3689886); private static Color HexColor(int rgb) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) return new Color((float)((rgb >> 16) & 0xFF) / 255f, (float)((rgb >> 8) & 0xFF) / 255f, (float)(rgb & 0xFF) / 255f, 1f); } private static TMP_FontAsset GetPressStartFont() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown if (_pressStartFontLoadAttempted) { return _pressStartFont; } _pressStartFontLoadAttempted = true; try { string text = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "fonts", "PressStart2P.ttf"); if (!File.Exists(text)) { return null; } Font val = new Font(text); if ((Object)(object)val == (Object)null) { return null; } _pressStartFont = TMP_FontAsset.CreateFontAsset(val); if ((Object)(object)_pressStartFont != (Object)null) { ((Object)_pressStartFont).name = "PressStart2P"; } } catch { } return _pressStartFont; } private static void ApplyPressStartFont(TextMeshProUGUI text) { TMP_FontAsset pressStartFont = GetPressStartFont(); if (!((Object)(object)pressStartFont == (Object)null) && !((Object)(object)text == (Object)null)) { ((TMP_Text)text).font = pressStartFont; ((TMP_Text)text).enableWordWrapping = false; } } internal static string TruncatePlayerName(string name, int maxChars = 28, bool appendEllipsis = true) { if (string.IsNullOrEmpty(name)) { return string.Empty; } if (name.Length <= maxChars) { return name; } return appendEllipsis ? (name.Substring(0, maxChars) + "...") : name.Substring(0, maxChars); } public CodeDmgRenderer(AppCodeDmg app) { _app = app; } public void Build() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Expected O, but got Unknown //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Expected O, but got Unknown //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Expected O, but got Unknown //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_034d: Expected O, but got Unknown //IL_0361: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_037b: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Unknown result type (might be due to invalid IL or missing references) //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_03b1: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Unknown result type (might be due to invalid IL or missing references) //IL_0435: Unknown result type (might be due to invalid IL or missing references) //IL_0445: Unknown result type (might be due to invalid IL or missing references) //IL_045c: Unknown result type (might be due to invalid IL or missing references) //IL_046c: Unknown result type (might be due to invalid IL or missing references) //IL_0498: Unknown result type (might be due to invalid IL or missing references) //IL_04a8: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("FullScreenBlack", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); _fullScreenBlackRect = val.GetComponent(); ((Transform)_fullScreenBlackRect).SetParent(((Component)_app).transform, false); RectTransform fullScreenBlackRect = _fullScreenBlackRect; RectTransform fullScreenBlackRect2 = _fullScreenBlackRect; RectTransform fullScreenBlackRect3 = _fullScreenBlackRect; Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0.5f, 0.5f); fullScreenBlackRect3.pivot = val2; Vector2 anchorMin = (fullScreenBlackRect2.anchorMax = val2); fullScreenBlackRect.anchorMin = anchorMin; _fullScreenBlackRect.anchoredPosition = new Vector2(0f, -40f); _fullScreenBlackRect.sizeDelta = new Vector2(2000f, 2000f); ((Transform)_fullScreenBlackRect).SetAsFirstSibling(); _fullScreenBlack = val.GetComponent(); ((Graphic)_fullScreenBlack).color = GetConfiguredBackground(); ((Graphic)_fullScreenBlack).raycastTarget = false; if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = new GameObject("CodeDmgRoot", new Type[1] { typeof(RectTransform) }); _rootRect = _root.GetComponent(); ((Transform)_rootRect).SetParent(((Component)_app).transform, false); RectTransform rootRect = _rootRect; RectTransform rootRect2 = _rootRect; RectTransform rootRect3 = _rootRect; ((Vector2)(ref val2))..ctor(0.5f, 0.5f); rootRect3.pivot = val2; anchorMin = (rootRect2.anchorMax = val2); rootRect.anchorMin = anchorMin; _rootRect.anchoredPosition = new Vector2(0f, -70f); ((Transform)_rootRect).localScale = Vector3.one; _rootRect.sizeDelta = new Vector2(960f, 864f); GameObject val5 = new GameObject("Screen", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(RawImage) }); RectTransform component = val5.GetComponent(); ((Transform)component).SetParent((Transform)(object)_rootRect, false); ((Vector2)(ref val2))..ctor(0.5f, 0.5f); component.pivot = val2; anchorMin = (component.anchorMax = val2); component.anchorMin = anchorMin; component.anchoredPosition = Vector2.zero; component.sizeDelta = _rootRect.sizeDelta; _screenImage = val5.GetComponent(); ((Graphic)_screenImage).color = Color.white; ((Graphic)_screenImage).raycastTarget = false; _screenTexture = new Texture2D(160, 144, (TextureFormat)4, false, false); ((Texture)_screenTexture).filterMode = (FilterMode)0; ((Texture)_screenTexture).wrapMode = (TextureWrapMode)1; _screenImage.texture = (Texture)(object)_screenTexture; _blitBuffer = (Color32[])(object)new Color32[23040]; ClearTexture(); GameObject val7 = new GameObject("GridOverlay", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(RawImage) }); RectTransform component2 = val7.GetComponent(); ((Transform)component2).SetParent((Transform)(object)component, false); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = Vector2.zero; component2.offsetMax = Vector2.zero; ((Transform)component2).localPosition = new Vector3(0f, 0f, -1f); ((Transform)component2).localScale = Vector3.one; _gridOverlay = val7.GetComponent(); _gridOverlay.texture = (Texture)(object)CreateGridTexture(); _gridOverlay.uvRect = new Rect(0f, 0f, 160f, 144f); ((Graphic)_gridOverlay).raycastTarget = false; ((Behaviour)_gridOverlay).enabled = CodeDmgPlugin.ConfigSettings?.PixelGrid.Value ?? false; CreateLogo(_rootRect.sizeDelta.x, _rootRect.sizeDelta.y); CreateTopLogo(_rootRect.sizeDelta.x, _rootRect.sizeDelta.y); CreateMissingRomText(); if (CodeDmgPlugin.LinkCable != null) { CreateLinkCableUI(_rootRect.sizeDelta.x, _rootRect.sizeDelta.y); } Build2PScreen(); } private Texture2D CreateGridTexture() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected O, but got Unknown //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) int num = 16; Texture2D val = new Texture2D(num, num, (TextureFormat)4, true); ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)0; Color32 val2 = default(Color32); ((Color32)(ref val2))..ctor((byte)0, (byte)0, (byte)0, (byte)0); Color32 val3 = default(Color32); ((Color32)(ref val3))..ctor((byte)0, (byte)0, (byte)0, (byte)240); Color32[] array = (Color32[])(object)new Color32[num * num]; int num2 = 4; for (int i = 0; i < num; i++) { for (int j = 0; j < num; j++) { bool flag = j >= num - num2; bool flag2 = i < num2; array[i * num + j] = ((flag || flag2) ? val3 : val2); } } val.SetPixels32(array); val.Apply(true, true); return val; } public void Teardown() { HideSecondPlayerScreen(); } public void HideSecondPlayerScreen() { if ((Object)(object)_p2Canvas != (Object)null) { ((Behaviour)_p2Canvas).enabled = false; } if ((Object)(object)_p2ScreenImage != (Object)null) { ((Behaviour)_p2ScreenImage).enabled = false; } } private void Build2PScreen() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Expected O, but got Unknown //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown //IL_02a1: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Expected O, but got Unknown //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_0316: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_0367: Unknown result type (might be due to invalid IL or missing references) //IL_0371: Expected O, but got Unknown //IL_03a4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_p2Canvas != (Object)null) { Object.Destroy((Object)(object)((Component)_p2Canvas).gameObject); _p2Canvas = null; } GameObject val = new GameObject("P2PhoneCanvas"); _p2Canvas = val.AddComponent(); _p2Canvas.renderMode = (RenderMode)0; _p2Canvas.sortingOrder = 10; val.AddComponent(); val.AddComponent(); ((Behaviour)_p2Canvas).enabled = false; _p2PhoneHeight = 432f; _p2PhoneWidth = _p2PhoneHeight * 0.7529138f; string path = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "gfx", "flipPhoneTex_PhoneOpen2P.png"); Texture2D val2 = null; if (File.Exists(path)) { byte[] array = File.ReadAllBytes(path); val2 = new Texture2D(2, 2, (TextureFormat)4, false, false); ((Texture)val2).filterMode = (FilterMode)1; ((Texture)val2).wrapMode = (TextureWrapMode)1; if (!ImageConversion.LoadImage(val2, array, false)) { val2 = null; } } GameObject val3 = new GameObject("P2PhoneBody", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(RawImage) }); _p2PhoneRect = val3.GetComponent(); ((Transform)_p2PhoneRect).SetParent(((Component)_p2Canvas).transform, false); RectTransform p2PhoneRect = _p2PhoneRect; RectTransform p2PhoneRect2 = _p2PhoneRect; Vector2 val4 = default(Vector2); ((Vector2)(ref val4))..ctor(0f, 0f); p2PhoneRect2.anchorMax = val4; p2PhoneRect.anchorMin = val4; _p2PhoneRect.pivot = new Vector2(0f, 0.5f); _p2PhoneRect.sizeDelta = new Vector2(_p2PhoneWidth, _p2PhoneHeight); _p2PhoneImage = val3.GetComponent(); ((Graphic)_p2PhoneImage).color = Color.white; ((Graphic)_p2PhoneImage).raycastTarget = false; if ((Object)(object)val2 != (Object)null) { _p2PhoneImage.texture = (Texture)(object)val2; } float num = 0.8576f * _p2PhoneWidth; float num2 = 0.6876f * _p2PhoneHeight; int num3 = Mathf.Max(1, (int)Mathf.Min(num / 160f, num2 / 144f)); float num4 = 160 * num3; float num5 = 144 * num3; float num6 = 0.5f * _p2PhoneWidth; float num7 = 0.039600015f * _p2PhoneHeight + 4f; GameObject val5 = new GameObject("P2EmuScreen", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(RawImage) }); RectTransform component = val5.GetComponent(); ((Transform)component).SetParent((Transform)(object)_p2PhoneRect, false); Vector2 val6 = default(Vector2); ((Vector2)(ref val6))..ctor(0.5f, 0.5f); component.pivot = val6; val4 = (component.anchorMin = (component.anchorMax = val6)); component.anchoredPosition = new Vector2(num6 - _p2PhoneWidth * 0.5f, num7); component.sizeDelta = new Vector2(num4, num5); _p2ScreenImage = val5.GetComponent(); ((Graphic)_p2ScreenImage).color = Color.white; ((Graphic)_p2ScreenImage).raycastTarget = false; ((Behaviour)_p2ScreenImage).enabled = false; _p2ScreenTexture = new Texture2D(160, 144, (TextureFormat)4, false, false); ((Texture)_p2ScreenTexture).filterMode = (FilterMode)0; ((Texture)_p2ScreenTexture).wrapMode = (TextureWrapMode)1; _p2ScreenImage.texture = (Texture)(object)_p2ScreenTexture; CreateP2MiniLogo(num4, num5, component.anchoredPosition.y); _p2BlitBuffer = (Color32[])(object)new Color32[23040]; } private void CreateP2MiniLogo(float screenW, float screenH, float screenY) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) string path = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "gfx", "microboyminilogo.png"); if (File.Exists(path)) { byte[] array; try { array = File.ReadAllBytes(path); } catch { return; } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false, false); ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; if (ImageConversion.LoadImage(val, array, false)) { GameObject val2 = new GameObject("P2MicroBoyMiniLogo", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(RawImage) }); _p2MiniLogoRect = val2.GetComponent(); ((Transform)_p2MiniLogoRect).SetParent((Transform)(object)_p2PhoneRect, false); RectTransform p2MiniLogoRect = _p2MiniLogoRect; RectTransform p2MiniLogoRect2 = _p2MiniLogoRect; RectTransform p2MiniLogoRect3 = _p2MiniLogoRect; Vector2 val3 = default(Vector2); ((Vector2)(ref val3))..ctor(0.5f, 0.5f); p2MiniLogoRect3.pivot = val3; Vector2 anchorMin = (p2MiniLogoRect2.anchorMax = val3); p2MiniLogoRect.anchorMin = anchorMin; _p2MiniLogoImage = val2.GetComponent(); _p2MiniLogoImage.texture = (Texture)(object)val; ((Graphic)_p2MiniLogoImage).color = Color.white; ((Graphic)_p2MiniLogoImage).raycastTarget = false; UpdateP2MiniLogoLayout(screenW, screenH, screenY); } } } private void UpdateP2MiniLogoLayout(float screenW, float screenH, float screenY) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_p2MiniLogoRect == (Object)null) && !((Object)(object)_p2MiniLogoImage == (Object)null) && !((Object)(object)_p2MiniLogoImage.texture == (Object)null)) { float num = screenW * 0.5833f; float num2 = num * ((float)_p2MiniLogoImage.texture.height / (float)_p2MiniLogoImage.texture.width); _p2MiniLogoRect.sizeDelta = new Vector2(num, num2); _p2MiniLogoRect.anchoredPosition = new Vector2(0f, screenY - screenH * 0.5f - 6f - num2 * 0.5f); } } private void UpdateP2PhonePosition() { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_p2PhoneRect == (Object)null) { return; } Canvas componentInParent = ((Component)_app).GetComponentInParent(); Camera val = (((Object)(object)componentInParent != (Object)null) ? componentInParent.worldCamera : null); RectTransform component = ((Component)_app).GetComponent(); if (!((Object)(object)component == (Object)null)) { Vector3[] array = (Vector3[])(object)new Vector3[4]; component.GetWorldCorners(array); float num = (((Object)(object)val != (Object)null) ? val.WorldToScreenPoint(array[2]).x : array[2].x); float num2 = (((Object)(object)val != (Object)null) ? (val.WorldToScreenPoint(array[2]).y - val.WorldToScreenPoint(array[3]).y) : (array[2].y - array[3].y)); float num3 = Mathf.Max(1f, num2 * 0.5f); float num4 = num3 * 0.7529138f; _p2PhoneRect.sizeDelta = new Vector2(num4, num3); RawImage p2ScreenImage = _p2ScreenImage; RectTransform val2 = ((p2ScreenImage != null) ? ((Component)p2ScreenImage).GetComponent() : null); if ((Object)(object)val2 != (Object)null) { float num5 = 0.8576f * num4; float num6 = 0.6876f * num3; int num7 = Mathf.Max(1, (int)Mathf.Min(num5 / 160f, num6 / 144f)); val2.sizeDelta = new Vector2((float)(160 * num7), (float)(144 * num7)); float num8 = 0.039600015f * num3 + 4f; val2.anchoredPosition = new Vector2(0.5f * num4 - num4 * 0.5f, num8); UpdateP2MiniLogoLayout(val2.sizeDelta.x, val2.sizeDelta.y, num8); } _p2PhoneRect.anchoredPosition = new Vector2(num + 32f, num3 * 0.5f); } } public void Render(CodeDmgEmulator emulator) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_fullScreenBlack != (Object)null) { ((Graphic)_fullScreenBlack).color = GetConfiguredBackground(); } if ((Object)(object)_missingRomText != (Object)null) { ((Behaviour)_missingRomText).enabled = emulator == null; } if ((Object)(object)_linkStatusText != (Object)null) { RenderLinkCableStatus(CodeDmgPlugin.LinkCable); } if (emulator == null || (Object)(object)_screenTexture == (Object)null) { return; } bool flag = CodeDmgPlugin.ConfigSettings?.PixelGrid.Value ?? false; if ((Object)(object)_gridOverlay != (Object)null && ((Behaviour)_gridOverlay).enabled != flag) { ((Behaviour)_gridOverlay).enabled = flag; } Color32[] unityFrame = emulator.Ppu.GetUnityFrame(); FlipRowsForUnity(unityFrame, _blitBuffer); _screenTexture.SetPixels32(_blitBuffer); _screenTexture.Apply(false, false); emulator.Ppu.ClearDirtyFlag(); int num; if (CodeDmgPlugin.ConfigSettings?.ShowPeerScreen?.Value == true) { LinkCableManager linkCable = CodeDmgPlugin.LinkCable; num = ((linkCable != null && linkCable.State == LinkCableState.Connected) ? 1 : 0); } else { num = 0; } bool flag2 = (byte)num != 0; if (flag2) { Core instance = Core.Instance; if ((Object)(object)instance != (Object)null && instance.IsCorePaused) { AppCodeDmg.HandleGamePauseStarted(); flag2 = false; } } if ((Object)(object)_p2Canvas != (Object)null) { ((Behaviour)_p2Canvas).enabled = flag2; } if (flag2) { UpdateP2PhonePosition(); CodeDmgEmulator linkedEmulator = emulator.LinkedEmulator; bool flag3 = linkedEmulator != null; if ((Object)(object)_p2ScreenImage != (Object)null && ((Behaviour)_p2ScreenImage).enabled != flag3) { ((Behaviour)_p2ScreenImage).enabled = flag3; } if (flag3) { FlipRowsForUnity(linkedEmulator.Ppu.GetUnityFrame(), _p2BlitBuffer); linkedEmulator.Ppu.ClearDirtyFlag(); _p2ScreenTexture.SetPixels32(_p2BlitBuffer); _p2ScreenTexture.Apply(false, false); } } } private static void FlipRowsForUnity(Color32[] source, Color32[] dest) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < 144; i++) { int num = i * 160; int num2 = (143 - i) * 160; for (int j = 0; j < 160; j++) { dest[num2 + j] = source[num + j]; } } } private static Color GetConfiguredBackground() { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: 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) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) string text = CodeDmgPlugin.ConfigSettings?.BackgroundColor.Value ?? "#000000"; text = text.TrimStart(new char[1] { '#' }).Trim(); if (text.Length < 6) { return Color.black; } try { byte b = Convert.ToByte(text.Substring(0, 2), 16); byte b2 = Convert.ToByte(text.Substring(2, 2), 16); byte b3 = Convert.ToByte(text.Substring(4, 2), 16); byte b4 = ((text.Length >= 8) ? Convert.ToByte(text.Substring(6, 2), 16) : byte.MaxValue); return Color32.op_Implicit(new Color32(b, b2, b3, b4)); } catch { return Color.black; } } private void ClearTexture() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) Color32 val = default(Color32); ((Color32)(ref val))..ctor((byte)0, (byte)0, (byte)0, byte.MaxValue); for (int i = 0; i < _blitBuffer.Length; i++) { _blitBuffer[i] = val; } _screenTexture.SetPixels32(_blitBuffer); _screenTexture.Apply(false, false); } private void CreateLogo(float screenWidth, float screenHeight) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) string path = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "gfx", "microboylogo.png"); if (File.Exists(path)) { byte[] array; try { array = File.ReadAllBytes(path); } catch { return; } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false, false); ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; if (ImageConversion.LoadImage(val, array, false)) { float num = screenWidth * 0.7f; float num2 = num * ((float)((Texture)val).height / (float)((Texture)val).width); GameObject val2 = new GameObject("MicroBoyLogo", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(RawImage) }); RectTransform component = val2.GetComponent(); ((Transform)component).SetParent((Transform)(object)_rootRect, false); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.zero; component.pivot = new Vector2(0f, 1f); float num3 = 5f; float num4 = -15f; component.anchoredPosition = new Vector2(num3, num4); component.sizeDelta = new Vector2(num, num2); _logoImage = val2.GetComponent(); _logoImage.texture = (Texture)(object)val; ((Graphic)_logoImage).raycastTarget = false; } } } private void CreateTopLogo(float screenWidth, float screenHeight) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) string text = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "gfx", "toplogo.png"); if (!File.Exists(text)) { Debug.LogError((object)("[CodeDmgRenderer] Error: Required file 'gfx/toplogo.png' was not found at " + text + ".")); return; } byte[] array; try { array = File.ReadAllBytes(text); } catch (Exception ex) { Debug.LogError((object)("[CodeDmgRenderer] Error: Could not read 'gfx/toplogo.png'. " + ex.Message)); return; } Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false, false); ((Texture)val).filterMode = (FilterMode)0; ((Texture)val).wrapMode = (TextureWrapMode)1; if (!ImageConversion.LoadImage(val, array, false)) { Debug.LogError((object)"[CodeDmgRenderer] Error: ImageConversion.LoadImage failed for 'gfx/toplogo.png'."); return; } float num = screenWidth * ((float)((Texture)val).height / (float)((Texture)val).width); float num2 = 15f; float num3 = screenHeight * 0.5f + num2 + num * 0.5f; GameObject val2 = new GameObject("SkateMatrixLogo", new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(RawImage) }); RectTransform component = val2.GetComponent(); ((Transform)component).SetParent((Transform)(object)_rootRect, false); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); component.anchoredPosition = new Vector2(0f, num3); component.sizeDelta = new Vector2(screenWidth, num); RawImage component2 = val2.GetComponent(); component2.texture = (Texture)(object)val; component2.uvRect = new Rect(0f, 0f, 1f, 1f); ((Graphic)component2).color = Color.white; ((Graphic)component2).raycastTarget = false; } private void CreateLinkCableUI(float screenWidth, float screenHeight) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Expected O, but got Unknown //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Expected O, but got Unknown //IL_0292: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_038c: Unknown result type (might be due to invalid IL or missing references) //IL_0393: Expected O, but got Unknown //IL_03ad: Unknown result type (might be due to invalid IL or missing references) //IL_03ba: Unknown result type (might be due to invalid IL or missing references) //IL_03d1: Unknown result type (might be due to invalid IL or missing references) //IL_03e5: Unknown result type (might be due to invalid IL or missing references) //IL_03fe: Unknown result type (might be due to invalid IL or missing references) //IL_042d: Unknown result type (might be due to invalid IL or missing references) //IL_04a7: Unknown result type (might be due to invalid IL or missing references) //IL_04ae: Expected O, but got Unknown //IL_04c8: Unknown result type (might be due to invalid IL or missing references) //IL_04d5: Unknown result type (might be due to invalid IL or missing references) //IL_04ec: Unknown result type (might be due to invalid IL or missing references) //IL_0500: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Unknown result type (might be due to invalid IL or missing references) //IL_0548: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_logoImage == (Object)null)) { float y = ((Graphic)_logoImage).rectTransform.sizeDelta.y; float num = -15f - y - 4f; GameObject val = new GameObject("HoldHint", new Type[1] { typeof(RectTransform) }); RectTransform component = val.GetComponent(); ((Transform)component).SetParent((Transform)(object)_rootRect, false); component.anchorMin = Vector2.zero; component.anchorMax = Vector2.zero; component.pivot = new Vector2(0f, 1f); component.anchoredPosition = new Vector2(5f, num); component.sizeDelta = new Vector2(screenWidth * 0.95f, 34f); _holdHintText = val.AddComponent(); ((TMP_Text)_holdHintText).fontSize = 24f; ((Graphic)_holdHintText).color = HexColor(8553090); ((TMP_Text)_holdHintText).alignment = (TextAlignmentOptions)257; ((TMP_Text)_holdHintText).text = "HOLD START & SELECT FOR MENU"; ((Graphic)_holdHintText).raycastTarget = false; ApplyPressStartFont(_holdHintText); ((Component)_holdHintText).gameObject.SetActive(true); float num2 = num - 32f; GameObject val2 = new GameObject("LinkCableStatus", new Type[1] { typeof(RectTransform) }); RectTransform component2 = val2.GetComponent(); ((Transform)component2).SetParent((Transform)(object)_rootRect, false); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.zero; component2.pivot = new Vector2(0f, 1f); component2.anchoredPosition = new Vector2(5f, num2); component2.sizeDelta = new Vector2(screenWidth * 0.95f, 34f); _linkStatusText = val2.AddComponent(); ((TMP_Text)_linkStatusText).fontSize = 24f; ((Graphic)_linkStatusText).color = Color.red; ((TMP_Text)_linkStatusText).alignment = (TextAlignmentOptions)257; ((TMP_Text)_linkStatusText).text = "LINK: DISCONNECTED"; ((Graphic)_linkStatusText).raycastTarget = false; ApplyPressStartFont(_linkStatusText); float num3 = num2 - 36f; GameObject val3 = new GameObject("LinkCableDelay", new Type[1] { typeof(RectTransform) }); RectTransform component3 = val3.GetComponent(); ((Transform)component3).SetParent((Transform)(object)_rootRect, false); component3.anchorMin = Vector2.zero; component3.anchorMax = Vector2.zero; component3.pivot = new Vector2(0f, 1f); component3.anchoredPosition = new Vector2(5f, num3); component3.sizeDelta = new Vector2(screenWidth * 0.95f, 32f); _linkDelayText = val3.AddComponent(); ((TMP_Text)_linkDelayText).fontSize = 24f; ((Graphic)_linkDelayText).color = Color.yellow; ((TMP_Text)_linkDelayText).alignment = (TextAlignmentOptions)257; ((TMP_Text)_linkDelayText).text = string.Empty; ((Graphic)_linkDelayText).raycastTarget = false; ApplyPressStartFont(_linkDelayText); ((Component)_linkDelayText).gameObject.SetActive(false); float num4 = num2 - 34f; GameObject val4 = new GameObject("RomMismatch", new Type[1] { typeof(RectTransform) }); RectTransform component4 = val4.GetComponent(); ((Transform)component4).SetParent((Transform)(object)_rootRect, false); component4.anchorMin = Vector2.zero; component4.anchorMax = Vector2.zero; component4.pivot = new Vector2(0f, 1f); component4.anchoredPosition = new Vector2(5f, num4); component4.sizeDelta = new Vector2(screenWidth * 0.95f, 56f); _romMismatchText = val4.AddComponent(); ((TMP_Text)_romMismatchText).fontSize = 24f; ((Graphic)_romMismatchText).color = Color.yellow; ((TMP_Text)_romMismatchText).alignment = (TextAlignmentOptions)257; ((TMP_Text)_romMismatchText).text = string.Empty; ((Graphic)_romMismatchText).raycastTarget = false; ApplyPressStartFont(_romMismatchText); ((Component)_romMismatchText).gameObject.SetActive(false); float num5 = num4 - 56f; GameObject val5 = new GameObject("DesyncWarning", new Type[1] { typeof(RectTransform) }); RectTransform component5 = val5.GetComponent(); ((Transform)component5).SetParent((Transform)(object)_rootRect, false); component5.anchorMin = Vector2.zero; component5.anchorMax = Vector2.zero; component5.pivot = new Vector2(0f, 1f); component5.anchoredPosition = new Vector2(5f, num5); component5.sizeDelta = new Vector2(screenWidth * 0.95f, 34f); _desyncWarningText = val5.AddComponent(); ((TMP_Text)_desyncWarningText).fontSize = 24f; ((Graphic)_desyncWarningText).color = Color.yellow; ((TMP_Text)_desyncWarningText).alignment = (TextAlignmentOptions)257; ((TMP_Text)_desyncWarningText).text = string.Empty; ((Graphic)_desyncWarningText).raycastTarget = false; ApplyPressStartFont(_desyncWarningText); ((Component)_desyncWarningText).gameObject.SetActive(false); } } private void UpdateLinkPendingAnimation(bool active) { if (!active) { _linkPendingAnimTimer = 0f; _linkPendingAnimStep = 0; return; } _linkPendingAnimTimer += Time.unscaledDeltaTime; if (_linkPendingAnimTimer >= 0.35f) { _linkPendingAnimTimer = 0f; _linkPendingAnimStep = (_linkPendingAnimStep + 1) % 4; } } private string GetLinkPendingDots() { return _linkPendingAnimStep switch { 1 => ".", 2 => "..", 3 => "...", _ => string.Empty, }; } public void RenderLinkCableStatus(LinkCableManager lc) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_020d: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_linkStatusText == (Object)null) { return; } if ((Object)(object)_holdHintText != (Object)null) { ((Component)_holdHintText).gameObject.SetActive(true); } if (lc == null || !CodeDmgPlugin.ConfigSettings.LinkCableEnabled.Value) { ((Component)_linkStatusText).gameObject.SetActive(false); if ((Object)(object)_linkDelayText != (Object)null) { ((Component)_linkDelayText).gameObject.SetActive(false); } return; } if (lc.HasFailureStatus) { UpdateLinkPendingAnimation(active: false); ((TMP_Text)_linkStatusText).text = lc.FailureStatusText; ((Graphic)_linkStatusText).color = Color.red; ((Component)_linkStatusText).gameObject.SetActive(true); if ((Object)(object)_linkDelayText != (Object)null) { ((Component)_linkDelayText).gameObject.SetActive(false); } } else { bool active = lc.State == LinkCableState.Linking || lc.State == LinkCableState.WaitingAccept; UpdateLinkPendingAnimation(active); switch (lc.State) { case LinkCableState.Disconnected: ((TMP_Text)_linkStatusText).text = "LINK: DISCONNECTED"; ((Graphic)_linkStatusText).color = Color.red; ((Component)_linkStatusText).gameObject.SetActive(!(CodeDmgPlugin.ConfigSettings.HideLinkStatusWhenDisconnected?.Value ?? true)); if ((Object)(object)_linkDelayText != (Object)null) { ((Component)_linkDelayText).gameObject.SetActive(false); } break; case LinkCableState.Linking: case LinkCableState.WaitingAccept: { string text2 = (string.IsNullOrEmpty(lc.LinkedPlayerName) ? "PLAYER" : TruncatePlayerName(lc.LinkedPlayerName.ToUpperInvariant(), 28, appendEllipsis: false)); ((TMP_Text)_linkStatusText).text = "LINKING: " + text2 + GetLinkPendingDots(); ((Graphic)_linkStatusText).color = Color.yellow; ((Component)_linkStatusText).gameObject.SetActive(true); if ((Object)(object)_linkDelayText != (Object)null) { ((Component)_linkDelayText).gameObject.SetActive(false); } break; } case LinkCableState.Connected: { string text = (string.IsNullOrEmpty(lc.LinkedPlayerName) ? "???" : TruncatePlayerName(lc.LinkedPlayerName)); ((TMP_Text)_linkStatusText).text = "LINKED: " + text; ((Graphic)_linkStatusText).color = Color.green; ((Component)_linkStatusText).gameObject.SetActive(true); if ((Object)(object)_linkDelayText != (Object)null) { ((TMP_Text)_linkDelayText).text = "DELAY: " + lc.EffectiveLinkInputDelayFrames; ((Component)_linkDelayText).gameObject.SetActive(true); } break; } } } if ((Object)(object)_romMismatchText != (Object)null) { bool showRomMismatch = lc.ShowRomMismatch; ((Component)_romMismatchText).gameObject.SetActive(showRomMismatch); if (showRomMismatch) { ((TMP_Text)_romMismatchText).text = lc.RomMismatchMessage ?? "Different ROMs detected!"; } } if ((Object)(object)_desyncWarningText != (Object)null) { bool showDesyncWarning = lc.ShowDesyncWarning; ((Component)_desyncWarningText).gameObject.SetActive(showDesyncWarning); if (showDesyncWarning) { ((TMP_Text)_desyncWarningText).text = "Possible desync detected!!"; } } } private void CreateMissingRomText() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("MissingRomText", new Type[1] { typeof(RectTransform) }); RectTransform component = val.GetComponent(); ((Transform)component).SetParent((Transform)(object)_rootRect, false); Vector2 val2 = default(Vector2); ((Vector2)(ref val2))..ctor(0.5f, 0.5f); component.pivot = val2; Vector2 anchorMin = (component.anchorMax = val2); component.anchorMin = anchorMin; component.sizeDelta = new Vector2(220f, 50f); component.anchoredPosition = Vector2.zero; _missingRomText = val.AddComponent(); ((TMP_Text)_missingRomText).text = "Missing rom.gb"; ((TMP_Text)_missingRomText).alignment = (TextAlignmentOptions)514; ((TMP_Text)_missingRomText).fontSize = 10f; ((Graphic)_missingRomText).color = Color.white; ((Behaviour)_missingRomText).enabled = false; } } public static class CodeDmgState { public static bool AppActive; } internal sealed class GBPaletteMenu : MonoBehaviour { private static GBPaletteMenu _instance; private static TMP_FontAsset _gameFont; private const float PanelW = 686f; private const float RowH = 56f; private const float RowSpacing = 6f; private const float TitleAreaH = 88f; private const float HintH = 44f; private const float PaddingV = 18f; private const int MaxVisible = 7; private static readonly string[] Labels = new string[12] { "DMG", "Cyber", "Emu", "Autumn", "Paris", "Grayscale", "Early", "Crow", "Coffee", "Winter", "Bomb Rush Orange", "Bomb Rush Blue" }; private GameObject _panelGo; private RectTransform _listViewport; private RectTransform _listContent; private List _rows = new List(); private int _selectedIndex; private int _windowStart; private bool _fontApplied; private bool _inputGrace; private bool _upHeld; private bool _downHeld; private bool _cancelWasDown; private bool _confirmWasDown; private float _repeatTimer; private const float RepeatDelay = 0.34f; private const float RepeatRate = 0.075f; public static bool IsVisible => (Object)(object)_instance != (Object)null && ((Component)_instance).gameObject.activeSelf; public static void Show() { EnsureInstance(); string paletteName = Helper.paletteName; _instance._selectedIndex = 0; for (int i = 0; i < Helper.PaletteNames.Length; i++) { if (Helper.PaletteNames[i] == paletteName) { _instance._selectedIndex = i; break; } } _instance._windowStart = 0; _instance._inputGrace = true; _instance._cancelWasDown = true; _instance._confirmWasDown = true; _instance._upHeld = false; _instance._downHeld = false; _instance._repeatTimer = 0f; _instance._fontApplied = false; _instance.BuildRows(); _instance.ScrollToSelected(); _instance.UpdateHighlights(); ((Component)_instance).gameObject.SetActive(true); } public static void Hide() { if (!((Object)(object)_instance == (Object)null)) { ((Component)_instance).gameObject.SetActive(false); if ((Object)(object)EventSystem.current != (Object)null) { EventSystem.current.SetSelectedGameObject((GameObject)null); } } } public static bool TryHandlePhoneUp() { if (!IsVisible) { return false; } _instance.Move(-1); return true; } public static bool TryHandlePhoneDown() { if (!IsVisible) { return false; } _instance.Move(1); return true; } public static bool TryHandlePhoneRight() { if (!IsVisible) { return false; } _instance.ConfirmCurrent(); return true; } public static bool TryHandlePhoneBack() { if (!IsVisible) { return false; } _instance.BackOut(); return true; } private static void EnsureInstance() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("CodeDmgGBPaletteMenu"); val.transform.SetParent(((Component)Core.Instance.UIManager).transform, false); _instance = val.AddComponent(); _instance.Build(); } } private void Build() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Expected O, but got Unknown //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Expected O, but got Unknown //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0348: Unknown result type (might be due to invalid IL or missing references) //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_0386: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Expected O, but got Unknown //IL_03c5: Unknown result type (might be due to invalid IL or missing references) //IL_03e5: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Unknown result type (might be due to invalid IL or missing references) //IL_0413: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Unknown result type (might be due to invalid IL or missing references) //IL_0441: Unknown result type (might be due to invalid IL or missing references) //IL_0451: Unknown result type (might be due to invalid IL or missing references) //IL_0458: Expected O, but got Unknown //IL_0496: Unknown result type (might be due to invalid IL or missing references) //IL_04b1: Unknown result type (might be due to invalid IL or missing references) //IL_04cc: Unknown result type (might be due to invalid IL or missing references) //IL_04e7: Unknown result type (might be due to invalid IL or missing references) //IL_0502: Unknown result type (might be due to invalid IL or missing references) //IL_0512: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Expected O, but got Unknown //IL_054b: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_0581: Unknown result type (might be due to invalid IL or missing references) //IL_0592: Unknown result type (might be due to invalid IL or missing references) //IL_05a3: Unknown result type (might be due to invalid IL or missing references) //IL_05d9: Unknown result type (might be due to invalid IL or missing references) //IL_05e9: Unknown result type (might be due to invalid IL or missing references) //IL_05f0: Expected O, but got Unknown //IL_0642: Unknown result type (might be due to invalid IL or missing references) //IL_066f: Unknown result type (might be due to invalid IL or missing references) //IL_0686: Unknown result type (might be due to invalid IL or missing references) //IL_069d: Unknown result type (might be due to invalid IL or missing references) //IL_06b4: Unknown result type (might be due to invalid IL or missing references) //IL_06cb: Unknown result type (might be due to invalid IL or missing references) Canvas val = ((Component)this).gameObject.AddComponent(); val.renderMode = (RenderMode)0; val.sortingOrder = 504; ((Component)this).gameObject.AddComponent(); _panelGo = new GameObject("Panel"); _panelGo.transform.SetParent(((Component)this).transform, false); Image val2 = _panelGo.AddComponent(); ((Graphic)val2).color = new Color(0.08f, 0.08f, 0.08f, 0.97f); RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0.5f, 0.5f); rectTransform.anchorMax = new Vector2(0.5f, 0.5f); rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.anchoredPosition = Vector2.zero; GameObject val3 = new GameObject("Accent"); val3.transform.SetParent(_panelGo.transform, false); Image val4 = val3.AddComponent(); ((Graphic)val4).color = new Color(10f / 51f, 0.34901962f, 0.6509804f, 1f); RectTransform rectTransform2 = ((Graphic)val4).rectTransform; rectTransform2.anchorMin = new Vector2(0f, 1f); rectTransform2.anchorMax = new Vector2(1f, 1f); rectTransform2.pivot = new Vector2(0.5f, 1f); rectTransform2.anchoredPosition = Vector2.zero; rectTransform2.sizeDelta = new Vector2(0f, 5f); GameObject val5 = new GameObject("Title1"); val5.transform.SetParent(_panelGo.transform, false); TextMeshProUGUI val6 = val5.AddComponent(); ((TMP_Text)val6).text = "GB-EMU MENU"; ((TMP_Text)val6).fontSize = 30f; ((TMP_Text)val6).fontStyle = (FontStyles)1; ((Graphic)val6).color = new Color(0.35686275f, 0.56078434f, 0.8745098f, 1f); ((TMP_Text)val6).alignment = (TextAlignmentOptions)514; RectTransform rectTransform3 = ((TMP_Text)val6).rectTransform; rectTransform3.anchorMin = new Vector2(0f, 1f); rectTransform3.anchorMax = new Vector2(1f, 1f); rectTransform3.pivot = new Vector2(0.5f, 1f); rectTransform3.anchoredPosition = new Vector2(0f, -18f); rectTransform3.sizeDelta = new Vector2(0f, 40f); GameObject val7 = new GameObject("Title2"); val7.transform.SetParent(_panelGo.transform, false); TextMeshProUGUI val8 = val7.AddComponent(); ((TMP_Text)val8).text = "CHANGE GAMEBOY PALETTE"; ((TMP_Text)val8).fontSize = 22f; ((Graphic)val8).color = new Color(0.75f, 0.75f, 0.75f, 1f); ((TMP_Text)val8).alignment = (TextAlignmentOptions)514; RectTransform rectTransform4 = ((TMP_Text)val8).rectTransform; rectTransform4.anchorMin = new Vector2(0f, 1f); rectTransform4.anchorMax = new Vector2(1f, 1f); rectTransform4.pivot = new Vector2(0.5f, 1f); rectTransform4.anchoredPosition = new Vector2(0f, -60f); rectTransform4.sizeDelta = new Vector2(0f, 30f); GameObject val9 = new GameObject("Sep"); val9.transform.SetParent(_panelGo.transform, false); Image val10 = val9.AddComponent(); ((Graphic)val10).color = new Color(1f, 1f, 1f, 0.12f); RectTransform rectTransform5 = ((Graphic)val10).rectTransform; rectTransform5.anchorMin = new Vector2(0f, 1f); rectTransform5.anchorMax = new Vector2(1f, 1f); rectTransform5.pivot = new Vector2(0.5f, 1f); rectTransform5.anchoredPosition = new Vector2(0f, -100f); rectTransform5.sizeDelta = new Vector2(-24f, 1f); GameObject val11 = new GameObject("Viewport"); val11.transform.SetParent(_panelGo.transform, false); val11.AddComponent(); _listViewport = val11.GetComponent(); _listViewport.anchorMin = new Vector2(0f, 1f); _listViewport.anchorMax = new Vector2(1f, 1f); _listViewport.pivot = new Vector2(0.5f, 1f); _listViewport.anchoredPosition = new Vector2(0f, -110f); _listViewport.sizeDelta = new Vector2(-24f, 434f); GameObject val12 = new GameObject("Content"); val12.transform.SetParent(val11.transform, false); _listContent = val12.AddComponent(); _listContent.anchorMin = new Vector2(0f, 1f); _listContent.anchorMax = new Vector2(1f, 1f); _listContent.pivot = new Vector2(0.5f, 1f); _listContent.anchoredPosition = Vector2.zero; _listContent.sizeDelta = Vector2.zero; float num = 434f; float num2 = 110f + num + 14f + 44f + 18f; rectTransform.sizeDelta = new Vector2(686f, num2); GameObject val13 = new GameObject("Hint"); val13.transform.SetParent(_panelGo.transform, false); TextMeshProUGUI val14 = val13.AddComponent(); ((TMP_Text)val14).text = "Up/Down: Nav A/Right: Select B/Left: Exit"; ((TMP_Text)val14).fontSize = 19f; ((Graphic)val14).color = new Color(0.6f, 0.6f, 0.6f, 1f); ((TMP_Text)val14).alignment = (TextAlignmentOptions)514; RectTransform rectTransform6 = ((TMP_Text)val14).rectTransform; rectTransform6.anchorMin = new Vector2(0f, 0f); rectTransform6.anchorMax = new Vector2(1f, 0f); rectTransform6.pivot = new Vector2(0.5f, 0f); rectTransform6.anchoredPosition = new Vector2(0f, 18f); rectTransform6.sizeDelta = new Vector2(0f, 44f); ((Component)this).gameObject.SetActive(false); } private void BuildRows() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Expected O, but got Unknown //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) foreach (GameObject row in _rows) { if (Object.op_Implicit((Object)(object)row)) { Object.Destroy((Object)(object)row); } } _rows.Clear(); int num = Mathf.Min(Labels.Length, 7); float num2 = 62f; for (int i = 0; i < num; i++) { GameObject val = new GameObject("Row_" + i); val.transform.SetParent((Transform)(object)_listContent, false); Image val2 = val.AddComponent(); ((Graphic)val2).color = Color.clear; RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(1f, 1f); rectTransform.pivot = new Vector2(0.5f, 1f); rectTransform.anchoredPosition = new Vector2(0f, (float)(-i) * num2); rectTransform.sizeDelta = new Vector2(0f, 56f); GameObject val3 = new GameObject("Label"); val3.transform.SetParent(val.transform, false); TextMeshProUGUI val4 = val3.AddComponent(); ((TMP_Text)val4).fontSize = 24f; ((TMP_Text)val4).alignment = (TextAlignmentOptions)514; RectTransform rectTransform2 = ((TMP_Text)val4).rectTransform; rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; Vector2 offsetMin = (rectTransform2.offsetMax = Vector2.zero); rectTransform2.offsetMin = offsetMin; _rows.Add(val); } } private void Update() { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) if (!((Component)this).gameObject.activeSelf) { return; } if (!_fontApplied) { ApplyGameFont(); } if (_inputGrace) { _inputGrace = false; return; } if (PopupState.ShouldSuppressMenuInputForChat()) { _upHeld = false; _downHeld = false; _repeatTimer = 0f; _confirmWasDown = true; _cancelWasDown = true; return; } CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; bool flag = Input.GetKey(configSettings.Up.Value) || Input.GetKey((KeyCode)273) || Input.GetAxisRaw("Vertical") > 0.5f; bool flag2 = Input.GetKey(configSettings.Down.Value) || Input.GetKey((KeyCode)274) || Input.GetAxisRaw("Vertical") < -0.5f; if (PhoneMenuInputRouter.ConsumedUpThisFrame() || PhoneMenuInputRouter.ConsumedDownThisFrame()) { _upHeld = flag; _downHeld = flag2; _repeatTimer = 0f; return; } if (flag && !_upHeld) { _upHeld = true; _repeatTimer = 0f; Move(-1); } else if (!flag) { _upHeld = false; } if (flag2 && !_downHeld) { _downHeld = true; _repeatTimer = 0f; Move(1); } else if (!flag2) { _downHeld = false; } if (_upHeld || _downHeld) { _repeatTimer += Time.unscaledDeltaTime; if (_repeatTimer >= 0.34f) { _repeatTimer -= 0.075f; if (_upHeld) { Move(-1); } if (_downHeld) { Move(1); } } } bool flag3 = Input.GetKey(configSettings.A.Value) || Input.GetKey(configSettings.Right.Value) || Input.GetKey((KeyCode)13) || Input.GetKey((KeyCode)271) || Input.GetKey((KeyCode)275) || Input.GetKey((KeyCode)330) || Input.GetAxisRaw("Horizontal") > 0.5f; bool flag4 = flag3 && !_confirmWasDown; _confirmWasDown = flag3; bool flag5 = Input.GetKey(configSettings.B.Value) || Input.GetKey(configSettings.Left.Value) || Input.GetKey((KeyCode)27) || Input.GetKey((KeyCode)276) || Input.GetKey((KeyCode)331) || Input.GetAxisRaw("Horizontal") < -0.5f; bool flag6 = flag5 && !_cancelWasDown; _cancelWasDown = flag5; if (flag4) { ConfirmCurrent(); } else if (flag6) { BackOut(); } } private void Move(int dir) { _selectedIndex = (_selectedIndex + dir + Labels.Length) % Labels.Length; ScrollToSelected(); UpdateHighlights(); PlaySelectSfx(); } private void ConfirmCurrent() { string value = (Helper.paletteName = Helper.PaletteNames[_selectedIndex]); if (CodeDmgPlugin.ConfigSettings?.Palette != null) { CodeDmgPlugin.ConfigSettings.Palette.Value = value; } PlayConfirmSfx(); PopupState.SuppressPhoneNavFor(); Hide(); } private void BackOut() { PlayBackSfx(); PopupState.SuppressPhoneNavFor(); Hide(); } private static void PlaySelectSfx() { try { AppCodeDmg.PlayMenuSelectSFX(); } catch { } } private static void PlayConfirmSfx() { try { AppCodeDmg.PlayMenuConfirmSFX(); } catch { } } private static void PlayBackSfx() { try { AppCodeDmg.PlayMenuBackSFX(); } catch { } } private void ScrollToSelected() { int num = Mathf.Min(Labels.Length, 7); if (_selectedIndex < _windowStart) { _windowStart = _selectedIndex; } else if (_selectedIndex >= _windowStart + num) { _windowStart = _selectedIndex - num + 1; } _windowStart = Mathf.Clamp(_windowStart, 0, Mathf.Max(0, Labels.Length - num)); UpdateVisibleRows(); } private void UpdateVisibleRows() { for (int i = 0; i < _rows.Count; i++) { if ((Object)(object)_rows[i] == (Object)null) { continue; } int num = _windowStart + i; bool flag = num < Labels.Length; _rows[i].SetActive(flag); if (flag) { Transform obj = _rows[i].transform.Find("Label"); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)val != (Object)null) { ((TMP_Text)val).text = Labels[num]; } } } } private void UpdateHighlights() { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < _rows.Count; i++) { if (!((Object)(object)_rows[i] == (Object)null)) { bool flag = _windowStart + i == _selectedIndex; Image component = _rows[i].GetComponent(); Transform obj = _rows[i].transform.Find("Label"); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = (Color)(flag ? new Color(0.21960784f, 0.3019608f, 0.61960787f, 0.35f) : Color.clear); } if ((Object)(object)val != (Object)null) { ((Graphic)val).color = (Color)(flag ? Color.white : new Color(0.65f, 0.65f, 0.65f, 1f)); } } } } private void ApplyGameFont() { if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array = Resources.FindObjectsOfTypeAll(); TextMeshProUGUI[] array2 = array; foreach (TextMeshProUGUI val in array2) { if ((Object)(object)val != (Object)null && (Object)(object)((TMP_Text)val).font != (Object)null && ((Object)val).name == "HeaderLabel") { _gameFont = ((TMP_Text)val).font; break; } } if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array3 = array; foreach (TextMeshProUGUI val2 in array3) { if ((Object)(object)val2 != (Object)null && (Object)(object)((TMP_Text)val2).font != (Object)null) { _gameFont = ((TMP_Text)val2).font; break; } } } } if ((Object)(object)_gameFont == (Object)null) { return; } TMP_Text[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); foreach (TMP_Text val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && (Object)(object)val3.font != (Object)(object)_gameFont) { val3.font = _gameFont; } } _fontApplied = true; } } internal sealed class MasterMenu : MonoBehaviour { private static MasterMenu _instance; private static TMP_FontAsset _gameFont; private const float PanelW = 686f; private const float RowH = 56f; private const float RowGap = 6f; private const float TitleAreaH = 88f; private const float HintH = 44f; private const float PaddingV = 18f; private static readonly string[] OptionLabels = new string[5] { "Change Game", "Link Cable", "Volume", "GB Palette", "Reboot Game" }; private static readonly int[] OptionActions = new int[5] { 0, 1, 2, 3, 4 }; private GameObject _panelGo; private RectTransform _panelRect; private GameObject[] _rows; private TextMeshProUGUI _hintText; private int[] _visibleActions; private int _visibleCount; private float _listTop; private int _selected; private bool _fontApplied; private bool _inputGrace; private bool _cancelWasDown; private bool _confirmWasDown; private Action _onChangeGame; private Action _onLinkCable; private Action _onVolume; private Action _onGBPalette; private Action _onReboot; private bool _upHeld; private bool _downHeld; private float _repeatTimer; private const float RepeatDelay = 0.34f; private const float RepeatRate = 0.075f; public static bool IsVisible => (Object)(object)_instance != (Object)null && ((Component)_instance).gameObject.activeSelf; public static void Show(Action onChangeGame, Action onLinkCable, Action onVolume, Action onGBPalette, Action onReboot) { EnsureInstance(); _instance._onChangeGame = onChangeGame; _instance._onLinkCable = onLinkCable; _instance._onVolume = onVolume; _instance._onGBPalette = onGBPalette; _instance._onReboot = onReboot; _instance.RefreshOptions(); _instance._selected = 0; _instance._inputGrace = true; _instance._cancelWasDown = true; _instance._confirmWasDown = true; _instance._upHeld = false; _instance._downHeld = false; _instance._repeatTimer = 0f; _instance._fontApplied = false; _instance.UpdateHighlights(); ((Component)_instance).gameObject.SetActive(true); } public static void Hide() { if (!((Object)(object)_instance == (Object)null)) { ((Component)_instance).gameObject.SetActive(false); if ((Object)(object)EventSystem.current != (Object)null) { EventSystem.current.SetSelectedGameObject((GameObject)null); } } } public static bool TryHandlePhoneUp() { if (!IsVisible) { return false; } _instance.MoveSelection(-1); return true; } public static bool TryHandlePhoneDown() { if (!IsVisible) { return false; } _instance.MoveSelection(1); return true; } public static bool TryHandlePhoneRight() { if (!IsVisible) { return false; } _instance.SelectCurrent(); return true; } public static bool TryHandlePhoneBack() { if (!IsVisible) { return false; } _instance.BackOut(); return true; } private static void EnsureInstance() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("CodeDmgMasterMenu"); val.transform.SetParent(((Component)Core.Instance.UIManager).transform, false); _instance = val.AddComponent(); _instance.Build(); } } private void Build() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Expected O, but got Unknown //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Expected O, but got Unknown //IL_02f4: Unknown result type (might be due to invalid IL or missing references) //IL_0321: Unknown result type (might be due to invalid IL or missing references) //IL_0338: Unknown result type (might be due to invalid IL or missing references) //IL_034f: Unknown result type (might be due to invalid IL or missing references) //IL_0366: Unknown result type (might be due to invalid IL or missing references) //IL_037d: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Unknown result type (might be due to invalid IL or missing references) //IL_0394: Expected O, but got Unknown //IL_03cc: Unknown result type (might be due to invalid IL or missing references) //IL_03ec: Unknown result type (might be due to invalid IL or missing references) //IL_0403: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_0431: Unknown result type (might be due to invalid IL or missing references) //IL_0448: Unknown result type (might be due to invalid IL or missing references) //IL_04e6: Unknown result type (might be due to invalid IL or missing references) //IL_04f6: Unknown result type (might be due to invalid IL or missing references) //IL_04fd: Expected O, but got Unknown //IL_055f: Unknown result type (might be due to invalid IL or missing references) //IL_0594: Unknown result type (might be due to invalid IL or missing references) //IL_05ab: Unknown result type (might be due to invalid IL or missing references) //IL_05c2: Unknown result type (might be due to invalid IL or missing references) //IL_05d9: Unknown result type (might be due to invalid IL or missing references) //IL_05f0: Unknown result type (might be due to invalid IL or missing references) Canvas val = ((Component)this).gameObject.AddComponent(); val.renderMode = (RenderMode)0; val.sortingOrder = 502; ((Component)this).gameObject.AddComponent(); _panelGo = new GameObject("Panel"); _panelGo.transform.SetParent(((Component)this).transform, false); Image val2 = _panelGo.AddComponent(); ((Graphic)val2).color = new Color(0.08f, 0.08f, 0.08f, 0.97f); RectTransform val3 = (_panelRect = ((Graphic)val2).rectTransform); val3.anchorMin = new Vector2(0.5f, 0.5f); val3.anchorMax = new Vector2(0.5f, 0.5f); val3.pivot = new Vector2(0.5f, 0.5f); val3.anchoredPosition = Vector2.zero; GameObject val4 = new GameObject("Accent"); val4.transform.SetParent(_panelGo.transform, false); Image val5 = val4.AddComponent(); ((Graphic)val5).color = new Color(10f / 51f, 0.34901962f, 0.6509804f, 1f); RectTransform rectTransform = ((Graphic)val5).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(1f, 1f); rectTransform.pivot = new Vector2(0.5f, 1f); rectTransform.anchoredPosition = Vector2.zero; rectTransform.sizeDelta = new Vector2(0f, 5f); GameObject val6 = new GameObject("Title1"); val6.transform.SetParent(_panelGo.transform, false); TextMeshProUGUI val7 = val6.AddComponent(); ((TMP_Text)val7).text = "GB-EMU MENU"; ((TMP_Text)val7).fontSize = 30f; ((TMP_Text)val7).fontStyle = (FontStyles)1; ((Graphic)val7).color = new Color(0.35686275f, 0.56078434f, 0.8745098f, 1f); ((TMP_Text)val7).alignment = (TextAlignmentOptions)514; RectTransform rectTransform2 = ((TMP_Text)val7).rectTransform; rectTransform2.anchorMin = new Vector2(0f, 1f); rectTransform2.anchorMax = new Vector2(1f, 1f); rectTransform2.pivot = new Vector2(0.5f, 1f); rectTransform2.anchoredPosition = new Vector2(0f, -18f); rectTransform2.sizeDelta = new Vector2(0f, 40f); GameObject val8 = new GameObject("Title2"); val8.transform.SetParent(_panelGo.transform, false); TextMeshProUGUI val9 = val8.AddComponent(); ((TMP_Text)val9).text = "SELECT OPTION"; ((TMP_Text)val9).fontSize = 22f; ((Graphic)val9).color = new Color(0.75f, 0.75f, 0.75f, 1f); ((TMP_Text)val9).alignment = (TextAlignmentOptions)514; RectTransform rectTransform3 = ((TMP_Text)val9).rectTransform; rectTransform3.anchorMin = new Vector2(0f, 1f); rectTransform3.anchorMax = new Vector2(1f, 1f); rectTransform3.pivot = new Vector2(0.5f, 1f); rectTransform3.anchoredPosition = new Vector2(0f, -60f); rectTransform3.sizeDelta = new Vector2(0f, 30f); GameObject val10 = new GameObject("Sep"); val10.transform.SetParent(_panelGo.transform, false); Image val11 = val10.AddComponent(); ((Graphic)val11).color = new Color(1f, 1f, 1f, 0.12f); RectTransform rectTransform4 = ((Graphic)val11).rectTransform; rectTransform4.anchorMin = new Vector2(0f, 1f); rectTransform4.anchorMax = new Vector2(1f, 1f); rectTransform4.pivot = new Vector2(0.5f, 1f); rectTransform4.anchoredPosition = new Vector2(0f, -100f); rectTransform4.sizeDelta = new Vector2(-24f, 1f); _listTop = 110f; _rows = (GameObject[])(object)new GameObject[OptionLabels.Length]; for (int i = 0; i < OptionLabels.Length; i++) { _rows[i] = BuildRow(OptionLabels[i], i, _listTop + (float)i * 62f); } float num = _listTop + (float)OptionLabels.Length * 62f + 14f + 44f + 18f; val3.sizeDelta = new Vector2(686f, num); GameObject val12 = new GameObject("Hint"); val12.transform.SetParent(_panelGo.transform, false); _hintText = val12.AddComponent(); ((TMP_Text)_hintText).text = "Up/Down: Nav A/Right: Select B/Left: Exit"; ((TMP_Text)_hintText).fontSize = 19f; ((Graphic)_hintText).color = new Color(0.6f, 0.6f, 0.6f, 1f); ((TMP_Text)_hintText).alignment = (TextAlignmentOptions)514; RectTransform rectTransform5 = ((TMP_Text)_hintText).rectTransform; rectTransform5.anchorMin = new Vector2(0f, 0f); rectTransform5.anchorMax = new Vector2(1f, 0f); rectTransform5.pivot = new Vector2(0.5f, 0f); rectTransform5.anchoredPosition = new Vector2(0f, 18f); rectTransform5.sizeDelta = new Vector2(0f, 44f); ((Component)this).gameObject.SetActive(false); } private void RefreshOptions() { //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; bool flag = configSettings != null && configSettings.LinkCableEnabled.Value && CodeDmgPlugin.LinkCable != null && CodeDmgPlugin.IsBombRushMPReady(); if (_visibleActions == null || _visibleActions.Length != OptionActions.Length) { _visibleActions = new int[OptionActions.Length]; } _visibleCount = 0; for (int i = 0; i < OptionLabels.Length; i++) { if (OptionActions[i] == 1 && !flag) { if (_rows != null && i < _rows.Length && (Object)(object)_rows[i] != (Object)null) { _rows[i].SetActive(false); } continue; } _visibleActions[_visibleCount] = OptionActions[i]; if (_rows != null && i < _rows.Length && (Object)(object)_rows[i] != (Object)null) { _rows[i].SetActive(true); RectTransform component = _rows[i].GetComponent(); if ((Object)(object)component != (Object)null) { component.anchoredPosition = new Vector2(0f, 0f - (_listTop + (float)_visibleCount * 62f)); } Transform obj = _rows[i].transform.Find("Label"); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)val != (Object)null) { ((TMP_Text)val).text = GetOptionLabel(i); } } _visibleCount++; } if ((Object)(object)_panelRect != (Object)null) { float num = _listTop + (float)_visibleCount * 62f + 14f + 44f + 18f; _panelRect.sizeDelta = new Vector2(686f, num); } if (_visibleCount > 0 && _selected >= _visibleCount) { _selected = _visibleCount - 1; } } private static string GetOptionLabel(int index) { if (index == 1 && CodeDmgPlugin.LinkCable != null && CodeDmgPlugin.LinkCable.State == LinkCableState.Connected) { return "Disconnect Link Cable"; } return OptionLabels[index]; } private GameObject BuildRow(string label, int index, float yOffset) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Row_" + index); val.transform.SetParent(_panelGo.transform, false); Image val2 = val.AddComponent(); ((Graphic)val2).color = Color.clear; RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0.03f, 1f); rectTransform.anchorMax = new Vector2(0.97f, 1f); rectTransform.pivot = new Vector2(0.5f, 1f); rectTransform.anchoredPosition = new Vector2(0f, 0f - yOffset); rectTransform.sizeDelta = new Vector2(0f, 56f); GameObject val3 = new GameObject("Label"); val3.transform.SetParent(val.transform, false); TextMeshProUGUI val4 = val3.AddComponent(); ((TMP_Text)val4).text = label; ((TMP_Text)val4).fontSize = 24f; ((Graphic)val4).color = Color.white; ((TMP_Text)val4).alignment = (TextAlignmentOptions)514; RectTransform rectTransform2 = ((TMP_Text)val4).rectTransform; rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; Vector2 offsetMin = (rectTransform2.offsetMax = Vector2.zero); rectTransform2.offsetMin = offsetMin; return val; } private void Update() { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) if (!((Component)this).gameObject.activeSelf) { return; } if (!_fontApplied) { ApplyGameFont(); } if (_inputGrace) { _inputGrace = false; return; } if (PopupState.ShouldSuppressMenuInputForChat()) { _upHeld = false; _downHeld = false; _repeatTimer = 0f; _confirmWasDown = true; _cancelWasDown = true; return; } CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; bool flag = Input.GetKey(configSettings.Up.Value) || Input.GetKey((KeyCode)273) || Input.GetAxisRaw("Vertical") > 0.5f; bool flag2 = Input.GetKey(configSettings.Down.Value) || Input.GetKey((KeyCode)274) || Input.GetAxisRaw("Vertical") < -0.5f; if (PhoneMenuInputRouter.ConsumedUpThisFrame() || PhoneMenuInputRouter.ConsumedDownThisFrame()) { _upHeld = flag; _downHeld = flag2; _repeatTimer = 0f; } else { if (flag && !_upHeld) { _upHeld = true; _repeatTimer = 0f; MoveSelection(-1); } else if (!flag) { _upHeld = false; } if (flag2 && !_downHeld) { _downHeld = true; _repeatTimer = 0f; MoveSelection(1); } else if (!flag2) { _downHeld = false; } if (_upHeld || _downHeld) { _repeatTimer += Time.unscaledDeltaTime; if (_repeatTimer >= 0.34f) { _repeatTimer -= 0.075f; if (_upHeld) { MoveSelection(-1); } if (_downHeld) { MoveSelection(1); } } } } bool flag3 = Input.GetKey(configSettings.A.Value) || Input.GetKey(configSettings.Right.Value) || Input.GetKey((KeyCode)13) || Input.GetKey((KeyCode)271) || Input.GetKey((KeyCode)275) || Input.GetKey((KeyCode)330) || Input.GetAxisRaw("Horizontal") > 0.5f; bool flag4 = flag3 && !_confirmWasDown; _confirmWasDown = flag3; bool flag5 = Input.GetKey(configSettings.B.Value) || Input.GetKey(configSettings.Left.Value) || Input.GetKey((KeyCode)27) || Input.GetKey((KeyCode)276) || Input.GetKey((KeyCode)331) || Input.GetAxisRaw("Horizontal") < -0.5f; bool flag6 = flag5 && !_cancelWasDown; _cancelWasDown = flag5; if (flag4) { SelectCurrent(); } else if (flag6) { BackOut(); } } private void MoveSelection(int delta) { if (_visibleCount > 0) { _selected = (_selected + delta + _visibleCount) % _visibleCount; UpdateHighlights(); PlaySelectSfx(); } } private void SelectCurrent() { PlayConfirmSfx(); PopupState.SuppressPhoneNavFor(); Hide(); switch ((_visibleActions != null && _selected >= 0 && _selected < _visibleCount) ? _visibleActions[_selected] : (-1)) { case 0: _onChangeGame?.Invoke(); break; case 1: _onLinkCable?.Invoke(); break; case 2: _onVolume?.Invoke(); break; case 3: _onGBPalette?.Invoke(); break; case 4: _onReboot?.Invoke(); break; } } private void BackOut() { PlayBackSfx(); PopupState.SuppressPhoneNavFor(); Hide(); } private static void PlaySelectSfx() { try { AppCodeDmg.PlayMenuSelectSFX(); } catch { } } private static void PlayConfirmSfx() { try { AppCodeDmg.PlayMenuConfirmSFX(); } catch { } } private static void PlayBackSfx() { try { AppCodeDmg.PlayMenuBackSFX(); } catch { } } private void UpdateHighlights() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) int num = 0; for (int i = 0; i < _rows.Length; i++) { if ((Object)(object)_rows[i] == (Object)null) { continue; } if (!_rows[i].activeSelf) { Image component = _rows[i].GetComponent(); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = Color.clear; } continue; } bool flag = num == _selected; Image component2 = _rows[i].GetComponent(); Transform obj = _rows[i].transform.Find("Label"); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)component2 != (Object)null) { ((Graphic)component2).color = (Color)(flag ? new Color(0.21960784f, 0.3019608f, 0.61960787f, 0.35f) : Color.clear); } if ((Object)(object)val != (Object)null) { ((Graphic)val).color = (Color)(flag ? Color.white : new Color(0.65f, 0.65f, 0.65f, 1f)); } num++; } } private void ApplyGameFont() { if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array = Resources.FindObjectsOfTypeAll(); TextMeshProUGUI[] array2 = array; foreach (TextMeshProUGUI val in array2) { if ((Object)(object)val != (Object)null && (Object)(object)((TMP_Text)val).font != (Object)null && ((Object)val).name == "HeaderLabel") { _gameFont = ((TMP_Text)val).font; break; } } if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array3 = array; foreach (TextMeshProUGUI val2 in array3) { if ((Object)(object)val2 != (Object)null && (Object)(object)((TMP_Text)val2).font != (Object)null) { _gameFont = ((TMP_Text)val2).font; break; } } } } if ((Object)(object)_gameFont == (Object)null) { return; } TMP_Text[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); foreach (TMP_Text val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && (Object)(object)val3.font != (Object)(object)_gameFont) { val3.font = _gameFont; } } _fontApplied = true; } } internal static class PopupState { private static float _suppressUntil = -1f; private static float _chatInputSuppressUntil = -1f; public static bool AnyVisible => MasterMenu.IsVisible || RomSelectMenu.IsVisible || LinkCablePlayerList.IsVisible || LinkCableConnectDialog.IsVisible || VolumeMenu.IsVisible || GBPaletteMenu.IsVisible; public static bool IsSuppressed => Time.unscaledTime <= _suppressUntil; public static void SuppressPhoneNavFor(float seconds = 0.2f) { _suppressUntil = Time.unscaledTime + seconds; } public static bool ShouldSuppressMenuInputForChat() { float unscaledTime = Time.unscaledTime; if (AppCodeDmg.IsChatInputActive()) { _chatInputSuppressUntil = unscaledTime + 0.35f; } return unscaledTime <= _chatInputSuppressUntil; } } internal sealed class RomSelectMenu : MonoBehaviour { private static RomSelectMenu _instance; private static TMP_FontAsset _gameFont; private const float PanelW = 686f; private const float RowH = 56f; private const float RowSpacing = 6f; private const float TitleAreaH = 88f; private const float HintH = 44f; private const float PaddingV = 18f; private const int MaxVisible = 7; private GameObject _panelGo; private TextMeshProUGUI _hintText; private RectTransform _listViewport; private RectTransform _listContent; private TextMeshProUGUI _loadingText; private List _romPaths = new List(); private List _rows = new List(); private int _selectedIndex; private int _windowStart; private bool _fontApplied; private bool _inputGrace; private bool _cancelWasDown; private bool _confirmWasDown; private bool _loading; private float _repeatTimer; private const float RepeatDelay = 0.34f; private const float RepeatRate = 0.075f; private bool _upHeld; private bool _downHeld; private Action _onPick; private static List _cachedRoms; private static string _cachedDir; private static int _cachedCount; private static long _cachedTick; public static bool IsVisible => (Object)(object)_instance != (Object)null && ((Component)_instance).gameObject.activeSelf; public static void Show(Action onPick) { EnsureInstance(); _instance._onPick = onPick; _instance._inputGrace = true; _instance._cancelWasDown = true; _instance._confirmWasDown = true; _instance._fontApplied = false; _instance._selectedIndex = 0; _instance._repeatTimer = 0f; _instance._upHeld = (_instance._downHeld = false); ((Component)_instance).gameObject.SetActive(true); ((MonoBehaviour)_instance).StartCoroutine(_instance.LoadRoms()); } public static void Hide() { if (!((Object)(object)_instance == (Object)null)) { ((Component)_instance).gameObject.SetActive(false); if ((Object)(object)EventSystem.current != (Object)null) { EventSystem.current.SetSelectedGameObject((GameObject)null); } } } public static bool TryHandlePhoneUp() { if (!IsVisible) { return false; } if (!_instance._loading) { _instance.Move(-1); } return true; } public static bool TryHandlePhoneDown() { if (!IsVisible) { return false; } if (!_instance._loading) { _instance.Move(1); } return true; } public static bool TryHandlePhoneRight() { if (!IsVisible) { return false; } if (!_instance._loading) { _instance.ConfirmCurrent(); } return true; } public static bool TryHandlePhoneBack() { if (!IsVisible) { return false; } _instance.BackOut(); return true; } public static void InvalidateCache() { _cachedRoms = null; } private static void EnsureInstance() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("CodeDmgRomSelect"); val.transform.SetParent(((Component)Core.Instance.UIManager).transform, false); _instance = val.AddComponent(); _instance.Build(); } } private void Build() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Expected O, but got Unknown //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Expected O, but got Unknown //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0348: Unknown result type (might be due to invalid IL or missing references) //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_0386: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Expected O, but got Unknown //IL_03c5: Unknown result type (might be due to invalid IL or missing references) //IL_03e5: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Unknown result type (might be due to invalid IL or missing references) //IL_0413: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Unknown result type (might be due to invalid IL or missing references) //IL_0441: Unknown result type (might be due to invalid IL or missing references) //IL_0451: Unknown result type (might be due to invalid IL or missing references) //IL_0458: Expected O, but got Unknown //IL_0496: Unknown result type (might be due to invalid IL or missing references) //IL_04b1: Unknown result type (might be due to invalid IL or missing references) //IL_04cc: Unknown result type (might be due to invalid IL or missing references) //IL_04e7: Unknown result type (might be due to invalid IL or missing references) //IL_0502: Unknown result type (might be due to invalid IL or missing references) //IL_0512: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Expected O, but got Unknown //IL_054b: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_0581: Unknown result type (might be due to invalid IL or missing references) //IL_0592: Unknown result type (might be due to invalid IL or missing references) //IL_05a3: Unknown result type (might be due to invalid IL or missing references) //IL_05b3: Unknown result type (might be due to invalid IL or missing references) //IL_05ba: Expected O, but got Unknown //IL_061c: Unknown result type (might be due to invalid IL or missing references) //IL_0651: Unknown result type (might be due to invalid IL or missing references) //IL_0668: Unknown result type (might be due to invalid IL or missing references) //IL_067f: Unknown result type (might be due to invalid IL or missing references) //IL_0696: Unknown result type (might be due to invalid IL or missing references) //IL_06ad: Unknown result type (might be due to invalid IL or missing references) //IL_06f5: Unknown result type (might be due to invalid IL or missing references) //IL_0705: Unknown result type (might be due to invalid IL or missing references) //IL_070c: Expected O, but got Unknown //IL_076e: Unknown result type (might be due to invalid IL or missing references) //IL_07a3: Unknown result type (might be due to invalid IL or missing references) //IL_07ba: Unknown result type (might be due to invalid IL or missing references) //IL_07d1: Unknown result type (might be due to invalid IL or missing references) //IL_07e8: Unknown result type (might be due to invalid IL or missing references) //IL_07ff: Unknown result type (might be due to invalid IL or missing references) Canvas val = ((Component)this).gameObject.AddComponent(); val.renderMode = (RenderMode)0; val.sortingOrder = 503; ((Component)this).gameObject.AddComponent(); _panelGo = new GameObject("Panel"); _panelGo.transform.SetParent(((Component)this).transform, false); Image val2 = _panelGo.AddComponent(); ((Graphic)val2).color = new Color(0.08f, 0.08f, 0.08f, 0.97f); RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0.5f, 0.5f); rectTransform.anchorMax = new Vector2(0.5f, 0.5f); rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.anchoredPosition = Vector2.zero; GameObject val3 = new GameObject("Accent"); val3.transform.SetParent(_panelGo.transform, false); Image val4 = val3.AddComponent(); ((Graphic)val4).color = new Color(10f / 51f, 0.34901962f, 0.6509804f, 1f); RectTransform rectTransform2 = ((Graphic)val4).rectTransform; rectTransform2.anchorMin = new Vector2(0f, 1f); rectTransform2.anchorMax = new Vector2(1f, 1f); rectTransform2.pivot = new Vector2(0.5f, 1f); rectTransform2.anchoredPosition = Vector2.zero; rectTransform2.sizeDelta = new Vector2(0f, 5f); GameObject val5 = new GameObject("Title1"); val5.transform.SetParent(_panelGo.transform, false); TextMeshProUGUI val6 = val5.AddComponent(); ((TMP_Text)val6).text = "GB-EMU MENU"; ((TMP_Text)val6).fontSize = 30f; ((TMP_Text)val6).fontStyle = (FontStyles)1; ((Graphic)val6).color = new Color(0.35686275f, 0.56078434f, 0.8745098f, 1f); ((TMP_Text)val6).alignment = (TextAlignmentOptions)514; RectTransform rectTransform3 = ((TMP_Text)val6).rectTransform; rectTransform3.anchorMin = new Vector2(0f, 1f); rectTransform3.anchorMax = new Vector2(1f, 1f); rectTransform3.pivot = new Vector2(0.5f, 1f); rectTransform3.anchoredPosition = new Vector2(0f, -18f); rectTransform3.sizeDelta = new Vector2(0f, 40f); GameObject val7 = new GameObject("Title2"); val7.transform.SetParent(_panelGo.transform, false); TextMeshProUGUI val8 = val7.AddComponent(); ((TMP_Text)val8).text = "CHANGE GAME"; ((TMP_Text)val8).fontSize = 22f; ((Graphic)val8).color = new Color(0.75f, 0.75f, 0.75f, 1f); ((TMP_Text)val8).alignment = (TextAlignmentOptions)514; RectTransform rectTransform4 = ((TMP_Text)val8).rectTransform; rectTransform4.anchorMin = new Vector2(0f, 1f); rectTransform4.anchorMax = new Vector2(1f, 1f); rectTransform4.pivot = new Vector2(0.5f, 1f); rectTransform4.anchoredPosition = new Vector2(0f, -60f); rectTransform4.sizeDelta = new Vector2(0f, 30f); GameObject val9 = new GameObject("Sep"); val9.transform.SetParent(_panelGo.transform, false); Image val10 = val9.AddComponent(); ((Graphic)val10).color = new Color(1f, 1f, 1f, 0.12f); RectTransform rectTransform5 = ((Graphic)val10).rectTransform; rectTransform5.anchorMin = new Vector2(0f, 1f); rectTransform5.anchorMax = new Vector2(1f, 1f); rectTransform5.pivot = new Vector2(0.5f, 1f); rectTransform5.anchoredPosition = new Vector2(0f, -100f); rectTransform5.sizeDelta = new Vector2(-24f, 1f); GameObject val11 = new GameObject("Viewport"); val11.transform.SetParent(_panelGo.transform, false); val11.AddComponent(); _listViewport = val11.GetComponent(); _listViewport.anchorMin = new Vector2(0f, 1f); _listViewport.anchorMax = new Vector2(1f, 1f); _listViewport.pivot = new Vector2(0.5f, 1f); _listViewport.anchoredPosition = new Vector2(0f, -110f); _listViewport.sizeDelta = new Vector2(-24f, 434f); GameObject val12 = new GameObject("Content"); val12.transform.SetParent(val11.transform, false); _listContent = val12.AddComponent(); _listContent.anchorMin = new Vector2(0f, 1f); _listContent.anchorMax = new Vector2(1f, 1f); _listContent.pivot = new Vector2(0.5f, 1f); _listContent.anchoredPosition = Vector2.zero; _listContent.sizeDelta = Vector2.zero; GameObject val13 = new GameObject("Loading"); val13.transform.SetParent(_panelGo.transform, false); _loadingText = val13.AddComponent(); ((TMP_Text)_loadingText).text = "Scanning ROMs..."; ((TMP_Text)_loadingText).fontSize = 22f; ((Graphic)_loadingText).color = new Color(0.6f, 0.6f, 0.6f, 1f); ((TMP_Text)_loadingText).alignment = (TextAlignmentOptions)514; RectTransform rectTransform6 = ((TMP_Text)_loadingText).rectTransform; rectTransform6.anchorMin = new Vector2(0f, 1f); rectTransform6.anchorMax = new Vector2(1f, 1f); rectTransform6.pivot = new Vector2(0.5f, 1f); rectTransform6.anchoredPosition = new Vector2(0f, -138f); rectTransform6.sizeDelta = new Vector2(0f, 56f); ((Component)_loadingText).gameObject.SetActive(false); float num = 434f; float num2 = 110f + num + 14f + 44f + 18f; rectTransform.sizeDelta = new Vector2(686f, num2); GameObject val14 = new GameObject("Hint"); val14.transform.SetParent(_panelGo.transform, false); _hintText = val14.AddComponent(); ((TMP_Text)_hintText).text = "Up/Down: Nav A/Right: Load B/Left: Exit"; ((TMP_Text)_hintText).fontSize = 19f; ((Graphic)_hintText).color = new Color(0.6f, 0.6f, 0.6f, 1f); ((TMP_Text)_hintText).alignment = (TextAlignmentOptions)514; RectTransform rectTransform7 = ((TMP_Text)_hintText).rectTransform; rectTransform7.anchorMin = new Vector2(0f, 0f); rectTransform7.anchorMax = new Vector2(1f, 0f); rectTransform7.pivot = new Vector2(0.5f, 0f); rectTransform7.anchoredPosition = new Vector2(0f, 18f); rectTransform7.sizeDelta = new Vector2(0f, 44f); ((Component)this).gameObject.SetActive(false); } private IEnumerator LoadRoms() { ClearRows(); _loading = true; ((Component)_loadingText).gameObject.SetActive(true); string dir = GetRomDirectory(); if (_cachedRoms == null || _cachedDir != dir || DirectoryChanged(dir)) { yield return null; List found = new List(); try { if (Directory.Exists(dir)) { List files = Directory.GetFiles(dir, "*.*", SearchOption.TopDirectoryOnly).Where(delegate(string f) { string text = Path.GetExtension(f).ToLowerInvariant(); return text == ".gb" || text == ".gbc"; }).OrderBy((string f) => Path.GetFileNameWithoutExtension(f), StringComparer.OrdinalIgnoreCase) .ToList(); found.AddRange(files); } } catch (Exception ex) { Exception ex2 = ex; Debug.LogWarning((object)("[CODE-DMG] ROM scan failed: " + ex2.Message)); } _cachedRoms = found; _cachedDir = dir; _cachedCount = found.Count; _cachedTick = GetDirectoryTick(dir); } _romPaths = new List(_cachedRoms ?? new List()); string tobuPath = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "rom.gb"); if (File.Exists(tobuPath) && !_romPaths.Contains(tobuPath)) { int insertAt = _romPaths.Count; for (int i = 0; i < _romPaths.Count; i++) { string label = Path.GetFileNameWithoutExtension(_romPaths[i]); if (StringComparer.OrdinalIgnoreCase.Compare("Tobu Tobu Girl DX", label) <= 0) { insertAt = i; break; } } _romPaths.Insert(insertAt, tobuPath); } _loading = false; ((Component)_loadingText).gameObject.SetActive(false); BuildRows(); ApplyGameFont(); ScrollToSelected(); UpdateHighlights(); } private static bool DirectoryChanged(string dir) { if (!Directory.Exists(dir)) { return false; } long directoryTick = GetDirectoryTick(dir); if (directoryTick != _cachedTick) { return true; } try { int num = Directory.GetFiles(dir, "*.*", SearchOption.TopDirectoryOnly).Count(delegate(string f) { string text = Path.GetExtension(f).ToLowerInvariant(); return text == ".gb" || text == ".gbc"; }); return num != _cachedCount; } catch { return false; } } private static long GetDirectoryTick(string dir) { try { long num = 0L; string[] files = Directory.GetFiles(dir, "*.*", SearchOption.TopDirectoryOnly); foreach (string path in files) { string text = Path.GetExtension(path).ToLowerInvariant(); if (!(text != ".gb") || !(text != ".gbc")) { long ticks = File.GetLastWriteTimeUtc(path).Ticks; if (ticks > num) { num = ticks; } } } return num; } catch { return 0L; } } private static string GetRomDirectory() { string text = CodeDmgPlugin.ConfigSettings?.RomDirectory?.Value; if (!string.IsNullOrWhiteSpace(text)) { try { Directory.CreateDirectory(text); } catch { } return text; } string text2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Bomb Rush Cyberfunk Modding", "BRCGameBoyEmu", "Roms"); Directory.CreateDirectory(text2); return text2; } private void BuildRows() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) ClearRows(); _windowStart = 0; if (_romPaths.Count == 0) { GameObject val = new GameObject("Empty"); val.transform.SetParent((Transform)(object)_listContent, false); TextMeshProUGUI val2 = val.AddComponent(); ((TMP_Text)val2).text = "No ROMs found."; ((TMP_Text)val2).fontSize = 22f; ((Graphic)val2).color = new Color(0.55f, 0.55f, 0.55f, 1f); ((TMP_Text)val2).alignment = (TextAlignmentOptions)514; RectTransform rectTransform = ((TMP_Text)val2).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(1f, 1f); rectTransform.pivot = new Vector2(0.5f, 1f); rectTransform.anchoredPosition = new Vector2(0f, -12f); rectTransform.sizeDelta = new Vector2(0f, 56f); _rows.Add(val); _listContent.sizeDelta = new Vector2(0f, 80f); } else { int num = Mathf.Min(_romPaths.Count, 7); float num2 = 62f; for (int i = 0; i < num; i++) { _rows.Add(BuildRow(i, (float)i * num2)); } _listContent.anchoredPosition = Vector2.zero; _listContent.sizeDelta = new Vector2(0f, (float)num * num2); UpdateVisibleRows(); } } private GameObject BuildRow(int slot, float yOffset) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Row_" + slot); val.transform.SetParent((Transform)(object)_listContent, false); Image val2 = val.AddComponent(); ((Graphic)val2).color = Color.clear; RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(1f, 1f); rectTransform.pivot = new Vector2(0.5f, 1f); rectTransform.anchoredPosition = new Vector2(0f, 0f - yOffset); rectTransform.sizeDelta = new Vector2(0f, 56f); GameObject val3 = new GameObject("Label"); val3.transform.SetParent(val.transform, false); TextMeshProUGUI val4 = val3.AddComponent(); ((TMP_Text)val4).text = string.Empty; ((TMP_Text)val4).fontSize = 21f; ((Graphic)val4).color = Color.white; ((TMP_Text)val4).alignment = (TextAlignmentOptions)4097; RectTransform rectTransform2 = ((TMP_Text)val4).rectTransform; rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; rectTransform2.offsetMin = new Vector2(18f, 0f); rectTransform2.offsetMax = new Vector2(-8f, 0f); return val; } private void UpdateVisibleRows() { string b = Path.Combine(CodeDmgPlugin.Instance.PluginDirectory, "rom.gb"); for (int i = 0; i < _rows.Count; i++) { GameObject val = _rows[i]; if ((Object)(object)val == (Object)null) { continue; } int num = _windowStart + i; bool flag = num >= 0 && num < _romPaths.Count; val.SetActive(flag); if (flag) { string text = _romPaths[num]; string text2 = (string.Equals(text, b, StringComparison.OrdinalIgnoreCase) ? "Tobu Tobu Girl DX" : Path.GetFileNameWithoutExtension(text)); Transform obj = val.transform.Find("Label"); TextMeshProUGUI val2 = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)val2 != (Object)null) { ((TMP_Text)val2).text = text2; } } } } private void ClearRows() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) foreach (GameObject row in _rows) { if (Object.op_Implicit((Object)(object)row)) { Object.Destroy((Object)(object)row); } } _rows.Clear(); _listContent.anchoredPosition = Vector2.zero; } private void Update() { if (!((Component)this).gameObject.activeSelf) { return; } if (!_fontApplied) { ApplyGameFont(); } if (_inputGrace) { _inputGrace = false; } else if (!_loading) { if (PopupState.ShouldSuppressMenuInputForChat()) { _upHeld = false; _downHeld = false; _repeatTimer = 0f; _confirmWasDown = true; _cancelWasDown = true; } else { HandleNavigation(); HandleConfirmCancel(); } } } private void HandleNavigation() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) if (_romPaths.Count == 0) { return; } CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; bool flag = Input.GetKey(configSettings.Up.Value) || Input.GetKey((KeyCode)273) || Input.GetAxisRaw("Vertical") > 0.5f; bool flag2 = Input.GetKey(configSettings.Down.Value) || Input.GetKey((KeyCode)274) || Input.GetAxisRaw("Vertical") < -0.5f; if (PhoneMenuInputRouter.ConsumedUpThisFrame() || PhoneMenuInputRouter.ConsumedDownThisFrame()) { _upHeld = flag; _downHeld = flag2; _repeatTimer = 0f; return; } if (flag && !_upHeld) { _upHeld = true; _repeatTimer = 0f; Move(-1); } else if (!flag) { _upHeld = false; } if (flag2 && !_downHeld) { _downHeld = true; _repeatTimer = 0f; Move(1); } else if (!flag2) { _downHeld = false; } if (!_upHeld && !_downHeld) { return; } _repeatTimer += Time.unscaledDeltaTime; if (_repeatTimer >= 0.34f) { _repeatTimer -= 0.075f; if (_upHeld) { Move(-1); } if (_downHeld) { Move(1); } } } private void Move(int dir) { if (_romPaths.Count != 0) { int selectedIndex = _selectedIndex; _selectedIndex = (_selectedIndex + dir + _romPaths.Count) % _romPaths.Count; bool wrapped = (dir < 0 && _selectedIndex > selectedIndex) || (dir > 0 && _selectedIndex < selectedIndex); ScrollToSelected(wrapped); UpdateHighlights(); PlaySelectSfx(); } } private void HandleConfirmCancel() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; bool flag = Input.GetKey(configSettings.A.Value) || Input.GetKey(configSettings.Right.Value) || Input.GetKey((KeyCode)13) || Input.GetKey((KeyCode)271) || Input.GetKey((KeyCode)275) || Input.GetKey((KeyCode)330) || Input.GetAxisRaw("Horizontal") > 0.5f; bool flag2 = flag && !_confirmWasDown; _confirmWasDown = flag; bool flag3 = Input.GetKey(configSettings.B.Value) || Input.GetKey(configSettings.Left.Value) || Input.GetKey((KeyCode)27) || Input.GetKey((KeyCode)276) || Input.GetKey((KeyCode)331) || Input.GetAxisRaw("Horizontal") < -0.5f; bool flag4 = flag3 && !_cancelWasDown; _cancelWasDown = flag3; if (flag2) { ConfirmCurrent(); } else if (flag4) { BackOut(); } } private void ConfirmCurrent() { if (_romPaths.Count != 0) { string obj = _romPaths[_selectedIndex]; PlayConfirmSfx(); PopupState.SuppressPhoneNavFor(); Hide(); _onPick?.Invoke(obj); } } private void BackOut() { PlayBackSfx(); PopupState.SuppressPhoneNavFor(); Hide(); } private static void PlaySelectSfx() { try { AppCodeDmg.PlayMenuSelectSFX(); } catch { } } private static void PlayConfirmSfx() { try { AppCodeDmg.PlayMenuConfirmSFX(); } catch { } } private static void PlayBackSfx() { try { AppCodeDmg.PlayMenuBackSFX(); } catch { } } private void UpdateHighlights() { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) if (_romPaths.Count == 0) { return; } UpdateVisibleRows(); for (int i = 0; i < _rows.Count; i++) { if (!((Object)(object)_rows[i] == (Object)null)) { int num = _windowStart + i; bool flag = num == _selectedIndex; Image component = _rows[i].GetComponent(); Transform obj = _rows[i].transform.Find("Label"); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = (Color)(flag ? new Color(0.21960784f, 0.3019608f, 0.61960787f, 0.35f) : Color.clear); } if ((Object)(object)val != (Object)null) { ((Graphic)val).color = (Color)(flag ? Color.white : new Color(0.65f, 0.65f, 0.65f, 1f)); } } } } private void ScrollToSelected(bool wrapped = false) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) if (_romPaths.Count != 0) { int num = Mathf.Min(_romPaths.Count, 7); if (_romPaths.Count <= num) { _windowStart = 0; } else if (_selectedIndex < _windowStart || (wrapped && _selectedIndex == 0)) { _windowStart = _selectedIndex; } else if (_selectedIndex >= _windowStart + num || wrapped) { _windowStart = _selectedIndex - num + 1; } _windowStart = Mathf.Clamp(_windowStart, 0, Mathf.Max(0, _romPaths.Count - num)); _listContent.anchoredPosition = Vector2.zero; UpdateVisibleRows(); } } private void ApplyGameFont() { if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array = Resources.FindObjectsOfTypeAll(); TextMeshProUGUI[] array2 = array; foreach (TextMeshProUGUI val in array2) { if ((Object)(object)val != (Object)null && (Object)(object)((TMP_Text)val).font != (Object)null && ((Object)val).name == "HeaderLabel") { _gameFont = ((TMP_Text)val).font; break; } } if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array3 = array; foreach (TextMeshProUGUI val2 in array3) { if ((Object)(object)val2 != (Object)null && (Object)(object)((TMP_Text)val2).font != (Object)null) { _gameFont = ((TMP_Text)val2).font; break; } } } } if ((Object)(object)_gameFont == (Object)null) { return; } TMP_Text[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); foreach (TMP_Text val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && (Object)(object)val3.font != (Object)(object)_gameFont) { val3.font = _gameFont; } } _fontApplied = true; } } internal sealed class VolumeMenu : MonoBehaviour { private static VolumeMenu _instance; private static TMP_FontAsset _gameFont; private const float PanelW = 686f; private const float RowH = 56f; private const float RowSpacing = 6f; private const float TitleAreaH = 88f; private const float HintH = 44f; private const float PaddingV = 18f; private const int MaxVisible = 7; private static readonly string[] Options = new string[11] { "100%", "90%", "80%", "70%", "60%", "50%", "40%", "30%", "20%", "10%", "0%" }; private static readonly int[] Values = new int[11] { 100, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0 }; private GameObject _panelGo; private RectTransform _listViewport; private RectTransform _listContent; private List _rows = new List(); private int _selectedIndex; private int _windowStart; private bool _fontApplied; private bool _inputGrace; private bool _upHeld; private bool _downHeld; private bool _cancelWasDown; private bool _confirmWasDown; private float _repeatTimer; private const float RepeatDelay = 0.34f; private const float RepeatRate = 0.075f; public static bool IsVisible => (Object)(object)_instance != (Object)null && ((Component)_instance).gameObject.activeSelf; public static void Show() { EnsureInstance(); int num = CodeDmgPlugin.ConfigSettings?.Volume?.Value ?? 80; _instance._selectedIndex = 0; for (int i = 0; i < Values.Length; i++) { if (Values[i] == num) { _instance._selectedIndex = i; break; } } _instance._windowStart = 0; _instance._inputGrace = true; _instance._cancelWasDown = true; _instance._confirmWasDown = true; _instance._upHeld = false; _instance._downHeld = false; _instance._repeatTimer = 0f; _instance._fontApplied = false; _instance.BuildRows(); _instance.ScrollToSelected(); _instance.UpdateHighlights(); ((Component)_instance).gameObject.SetActive(true); } public static void Hide() { if (!((Object)(object)_instance == (Object)null)) { ((Component)_instance).gameObject.SetActive(false); if ((Object)(object)EventSystem.current != (Object)null) { EventSystem.current.SetSelectedGameObject((GameObject)null); } } } public static bool TryHandlePhoneUp() { if (!IsVisible) { return false; } _instance.Move(-1); return true; } public static bool TryHandlePhoneDown() { if (!IsVisible) { return false; } _instance.Move(1); return true; } public static bool TryHandlePhoneRight() { if (!IsVisible) { return false; } _instance.ConfirmCurrent(); return true; } public static bool TryHandlePhoneBack() { if (!IsVisible) { return false; } _instance.BackOut(); return true; } private static void EnsureInstance() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("CodeDmgVolumeMenu"); val.transform.SetParent(((Component)Core.Instance.UIManager).transform, false); _instance = val.AddComponent(); _instance.Build(); } } private void Build() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Expected O, but got Unknown //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Expected O, but got Unknown //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0348: Unknown result type (might be due to invalid IL or missing references) //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_0386: Unknown result type (might be due to invalid IL or missing references) //IL_038d: Expected O, but got Unknown //IL_03c5: Unknown result type (might be due to invalid IL or missing references) //IL_03e5: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Unknown result type (might be due to invalid IL or missing references) //IL_0413: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Unknown result type (might be due to invalid IL or missing references) //IL_0441: Unknown result type (might be due to invalid IL or missing references) //IL_0451: Unknown result type (might be due to invalid IL or missing references) //IL_0458: Expected O, but got Unknown //IL_0496: Unknown result type (might be due to invalid IL or missing references) //IL_04b1: Unknown result type (might be due to invalid IL or missing references) //IL_04cc: Unknown result type (might be due to invalid IL or missing references) //IL_04e7: Unknown result type (might be due to invalid IL or missing references) //IL_0502: Unknown result type (might be due to invalid IL or missing references) //IL_0512: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Expected O, but got Unknown //IL_054b: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_0581: Unknown result type (might be due to invalid IL or missing references) //IL_0592: Unknown result type (might be due to invalid IL or missing references) //IL_05a3: Unknown result type (might be due to invalid IL or missing references) //IL_05d9: Unknown result type (might be due to invalid IL or missing references) //IL_05e9: Unknown result type (might be due to invalid IL or missing references) //IL_05f0: Expected O, but got Unknown //IL_0642: Unknown result type (might be due to invalid IL or missing references) //IL_066f: Unknown result type (might be due to invalid IL or missing references) //IL_0686: Unknown result type (might be due to invalid IL or missing references) //IL_069d: Unknown result type (might be due to invalid IL or missing references) //IL_06b4: Unknown result type (might be due to invalid IL or missing references) //IL_06cb: Unknown result type (might be due to invalid IL or missing references) Canvas val = ((Component)this).gameObject.AddComponent(); val.renderMode = (RenderMode)0; val.sortingOrder = 504; ((Component)this).gameObject.AddComponent(); _panelGo = new GameObject("Panel"); _panelGo.transform.SetParent(((Component)this).transform, false); Image val2 = _panelGo.AddComponent(); ((Graphic)val2).color = new Color(0.08f, 0.08f, 0.08f, 0.97f); RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0.5f, 0.5f); rectTransform.anchorMax = new Vector2(0.5f, 0.5f); rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.anchoredPosition = Vector2.zero; GameObject val3 = new GameObject("Accent"); val3.transform.SetParent(_panelGo.transform, false); Image val4 = val3.AddComponent(); ((Graphic)val4).color = new Color(10f / 51f, 0.34901962f, 0.6509804f, 1f); RectTransform rectTransform2 = ((Graphic)val4).rectTransform; rectTransform2.anchorMin = new Vector2(0f, 1f); rectTransform2.anchorMax = new Vector2(1f, 1f); rectTransform2.pivot = new Vector2(0.5f, 1f); rectTransform2.anchoredPosition = Vector2.zero; rectTransform2.sizeDelta = new Vector2(0f, 5f); GameObject val5 = new GameObject("Title1"); val5.transform.SetParent(_panelGo.transform, false); TextMeshProUGUI val6 = val5.AddComponent(); ((TMP_Text)val6).text = "GB-EMU MENU"; ((TMP_Text)val6).fontSize = 30f; ((TMP_Text)val6).fontStyle = (FontStyles)1; ((Graphic)val6).color = new Color(0.35686275f, 0.56078434f, 0.8745098f, 1f); ((TMP_Text)val6).alignment = (TextAlignmentOptions)514; RectTransform rectTransform3 = ((TMP_Text)val6).rectTransform; rectTransform3.anchorMin = new Vector2(0f, 1f); rectTransform3.anchorMax = new Vector2(1f, 1f); rectTransform3.pivot = new Vector2(0.5f, 1f); rectTransform3.anchoredPosition = new Vector2(0f, -18f); rectTransform3.sizeDelta = new Vector2(0f, 40f); GameObject val7 = new GameObject("Title2"); val7.transform.SetParent(_panelGo.transform, false); TextMeshProUGUI val8 = val7.AddComponent(); ((TMP_Text)val8).text = "ADJUST VOLUME"; ((TMP_Text)val8).fontSize = 22f; ((Graphic)val8).color = new Color(0.75f, 0.75f, 0.75f, 1f); ((TMP_Text)val8).alignment = (TextAlignmentOptions)514; RectTransform rectTransform4 = ((TMP_Text)val8).rectTransform; rectTransform4.anchorMin = new Vector2(0f, 1f); rectTransform4.anchorMax = new Vector2(1f, 1f); rectTransform4.pivot = new Vector2(0.5f, 1f); rectTransform4.anchoredPosition = new Vector2(0f, -60f); rectTransform4.sizeDelta = new Vector2(0f, 30f); GameObject val9 = new GameObject("Sep"); val9.transform.SetParent(_panelGo.transform, false); Image val10 = val9.AddComponent(); ((Graphic)val10).color = new Color(1f, 1f, 1f, 0.12f); RectTransform rectTransform5 = ((Graphic)val10).rectTransform; rectTransform5.anchorMin = new Vector2(0f, 1f); rectTransform5.anchorMax = new Vector2(1f, 1f); rectTransform5.pivot = new Vector2(0.5f, 1f); rectTransform5.anchoredPosition = new Vector2(0f, -100f); rectTransform5.sizeDelta = new Vector2(-24f, 1f); GameObject val11 = new GameObject("Viewport"); val11.transform.SetParent(_panelGo.transform, false); val11.AddComponent(); _listViewport = val11.GetComponent(); _listViewport.anchorMin = new Vector2(0f, 1f); _listViewport.anchorMax = new Vector2(1f, 1f); _listViewport.pivot = new Vector2(0.5f, 1f); _listViewport.anchoredPosition = new Vector2(0f, -110f); _listViewport.sizeDelta = new Vector2(-24f, 434f); GameObject val12 = new GameObject("Content"); val12.transform.SetParent(val11.transform, false); _listContent = val12.AddComponent(); _listContent.anchorMin = new Vector2(0f, 1f); _listContent.anchorMax = new Vector2(1f, 1f); _listContent.pivot = new Vector2(0.5f, 1f); _listContent.anchoredPosition = Vector2.zero; _listContent.sizeDelta = Vector2.zero; float num = 434f; float num2 = 110f + num + 14f + 44f + 18f; rectTransform.sizeDelta = new Vector2(686f, num2); GameObject val13 = new GameObject("Hint"); val13.transform.SetParent(_panelGo.transform, false); TextMeshProUGUI val14 = val13.AddComponent(); ((TMP_Text)val14).text = "Up/Down: Nav A/Right: Select B/Left: Exit"; ((TMP_Text)val14).fontSize = 19f; ((Graphic)val14).color = new Color(0.6f, 0.6f, 0.6f, 1f); ((TMP_Text)val14).alignment = (TextAlignmentOptions)514; RectTransform rectTransform6 = ((TMP_Text)val14).rectTransform; rectTransform6.anchorMin = new Vector2(0f, 0f); rectTransform6.anchorMax = new Vector2(1f, 0f); rectTransform6.pivot = new Vector2(0.5f, 0f); rectTransform6.anchoredPosition = new Vector2(0f, 18f); rectTransform6.sizeDelta = new Vector2(0f, 44f); ((Component)this).gameObject.SetActive(false); } private void BuildRows() { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Expected O, but got Unknown //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) foreach (GameObject row in _rows) { if (Object.op_Implicit((Object)(object)row)) { Object.Destroy((Object)(object)row); } } _rows.Clear(); int num = Mathf.Min(Options.Length, 7); float num2 = 62f; for (int i = 0; i < num; i++) { GameObject val = new GameObject("Row_" + i); val.transform.SetParent((Transform)(object)_listContent, false); Image val2 = val.AddComponent(); ((Graphic)val2).color = Color.clear; RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(1f, 1f); rectTransform.pivot = new Vector2(0.5f, 1f); rectTransform.anchoredPosition = new Vector2(0f, (float)(-i) * num2); rectTransform.sizeDelta = new Vector2(0f, 56f); GameObject val3 = new GameObject("Label"); val3.transform.SetParent(val.transform, false); TextMeshProUGUI val4 = val3.AddComponent(); ((TMP_Text)val4).fontSize = 24f; ((TMP_Text)val4).alignment = (TextAlignmentOptions)514; RectTransform rectTransform2 = ((TMP_Text)val4).rectTransform; rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; Vector2 offsetMin = (rectTransform2.offsetMax = Vector2.zero); rectTransform2.offsetMin = offsetMin; _rows.Add(val); } } private void Update() { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) if (!((Component)this).gameObject.activeSelf) { return; } if (!_fontApplied) { ApplyGameFont(); } if (_inputGrace) { _inputGrace = false; return; } if (PopupState.ShouldSuppressMenuInputForChat()) { _upHeld = false; _downHeld = false; _repeatTimer = 0f; _confirmWasDown = true; _cancelWasDown = true; return; } CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; bool flag = Input.GetKey(configSettings.Up.Value) || Input.GetKey((KeyCode)273) || Input.GetAxisRaw("Vertical") > 0.5f; bool flag2 = Input.GetKey(configSettings.Down.Value) || Input.GetKey((KeyCode)274) || Input.GetAxisRaw("Vertical") < -0.5f; if (PhoneMenuInputRouter.ConsumedUpThisFrame() || PhoneMenuInputRouter.ConsumedDownThisFrame()) { _upHeld = flag; _downHeld = flag2; _repeatTimer = 0f; return; } if (flag && !_upHeld) { _upHeld = true; _repeatTimer = 0f; Move(-1); } else if (!flag) { _upHeld = false; } if (flag2 && !_downHeld) { _downHeld = true; _repeatTimer = 0f; Move(1); } else if (!flag2) { _downHeld = false; } if (_upHeld || _downHeld) { _repeatTimer += Time.unscaledDeltaTime; if (_repeatTimer >= 0.34f) { _repeatTimer -= 0.075f; if (_upHeld) { Move(-1); } if (_downHeld) { Move(1); } } } bool flag3 = Input.GetKey(configSettings.A.Value) || Input.GetKey(configSettings.Right.Value) || Input.GetKey((KeyCode)13) || Input.GetKey((KeyCode)271) || Input.GetKey((KeyCode)275) || Input.GetKey((KeyCode)330) || Input.GetAxisRaw("Horizontal") > 0.5f; bool flag4 = flag3 && !_confirmWasDown; _confirmWasDown = flag3; bool flag5 = Input.GetKey(configSettings.B.Value) || Input.GetKey(configSettings.Left.Value) || Input.GetKey((KeyCode)27) || Input.GetKey((KeyCode)276) || Input.GetKey((KeyCode)331) || Input.GetAxisRaw("Horizontal") < -0.5f; bool flag6 = flag5 && !_cancelWasDown; _cancelWasDown = flag5; if (flag4) { ConfirmCurrent(); } else if (flag6) { BackOut(); } } private void Move(int dir) { _selectedIndex = (_selectedIndex + dir + Options.Length) % Options.Length; ScrollToSelected(); UpdateHighlights(); PlaySelectSfx(); } private void ConfirmCurrent() { if (CodeDmgPlugin.ConfigSettings?.Volume != null) { CodeDmgPlugin.ConfigSettings.Volume.Value = Values[_selectedIndex]; } PlayConfirmSfx(); PopupState.SuppressPhoneNavFor(); Hide(); } private void BackOut() { PlayBackSfx(); PopupState.SuppressPhoneNavFor(); Hide(); } private static void PlaySelectSfx() { try { AppCodeDmg.PlayMenuSelectSFX(); } catch { } } private static void PlayConfirmSfx() { try { AppCodeDmg.PlayMenuConfirmSFX(); } catch { } } private static void PlayBackSfx() { try { AppCodeDmg.PlayMenuBackSFX(); } catch { } } private void ScrollToSelected() { int num = Mathf.Min(Options.Length, 7); if (_selectedIndex < _windowStart) { _windowStart = _selectedIndex; } else if (_selectedIndex >= _windowStart + num) { _windowStart = _selectedIndex - num + 1; } _windowStart = Mathf.Clamp(_windowStart, 0, Mathf.Max(0, Options.Length - num)); UpdateVisibleRows(); } private void UpdateVisibleRows() { for (int i = 0; i < _rows.Count; i++) { if ((Object)(object)_rows[i] == (Object)null) { continue; } int num = _windowStart + i; bool flag = num < Options.Length; _rows[i].SetActive(flag); if (flag) { Transform obj = _rows[i].transform.Find("Label"); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)val != (Object)null) { ((TMP_Text)val).text = Options[num]; } } } } private void UpdateHighlights() { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < _rows.Count; i++) { if (!((Object)(object)_rows[i] == (Object)null)) { bool flag = _windowStart + i == _selectedIndex; Image component = _rows[i].GetComponent(); Transform obj = _rows[i].transform.Find("Label"); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = (Color)(flag ? new Color(0.21960784f, 0.3019608f, 0.61960787f, 0.35f) : Color.clear); } if ((Object)(object)val != (Object)null) { ((Graphic)val).color = (Color)(flag ? Color.white : new Color(0.65f, 0.65f, 0.65f, 1f)); } } } } private void ApplyGameFont() { if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array = Resources.FindObjectsOfTypeAll(); TextMeshProUGUI[] array2 = array; foreach (TextMeshProUGUI val in array2) { if ((Object)(object)val != (Object)null && (Object)(object)((TMP_Text)val).font != (Object)null && ((Object)val).name == "HeaderLabel") { _gameFont = ((TMP_Text)val).font; break; } } if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array3 = array; foreach (TextMeshProUGUI val2 in array3) { if ((Object)(object)val2 != (Object)null && (Object)(object)((TMP_Text)val2).font != (Object)null) { _gameFont = ((TMP_Text)val2).font; break; } } } } if ((Object)(object)_gameFont == (Object)null) { return; } TMP_Text[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); foreach (TMP_Text val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && (Object)(object)val3.font != (Object)(object)_gameFont) { val3.font = _gameFont; } } _fontApplied = true; } } public static class Helper { public const string DefaultPaletteName = "dmg"; public const string DefaultPeerPaletteName = "bombrush-blue"; public static int scale = 1; private static string _paletteName = "dmg"; public static readonly string[] PaletteNames = new string[12] { "dmg", "cyber", "emu", "autumn", "paris", "grayscale", "early", "crow", "coffee", "winter", "bombrush-orange", "bombrush-blue" }; public static readonly Dictionary palettes = new Dictionary { { "dmg", (Color32[])(object)new Color32[5] { new Color32((byte)155, (byte)188, (byte)15, byte.MaxValue), new Color32((byte)139, (byte)172, (byte)15, byte.MaxValue), new Color32((byte)48, (byte)98, (byte)48, byte.MaxValue), new Color32((byte)15, (byte)56, (byte)15, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "cyber", (Color32[])(object)new Color32[5] { new Color32((byte)50, (byte)153, (byte)180, byte.MaxValue), new Color32((byte)46, (byte)116, (byte)134, byte.MaxValue), new Color32((byte)2, (byte)70, (byte)88, byte.MaxValue), new Color32((byte)2, (byte)49, (byte)61, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "emu", (Color32[])(object)new Color32[5] { new Color32((byte)224, (byte)248, (byte)208, byte.MaxValue), new Color32((byte)136, (byte)192, (byte)112, byte.MaxValue), new Color32((byte)52, (byte)104, (byte)86, byte.MaxValue), new Color32((byte)8, (byte)24, (byte)32, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "autumn", (Color32[])(object)new Color32[5] { new Color32(byte.MaxValue, (byte)246, (byte)211, byte.MaxValue), new Color32((byte)249, (byte)168, (byte)117, byte.MaxValue), new Color32((byte)235, (byte)107, (byte)111, byte.MaxValue), new Color32((byte)124, (byte)63, (byte)88, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "paris", (Color32[])(object)new Color32[5] { new Color32((byte)218, (byte)112, (byte)214, byte.MaxValue), new Color32((byte)186, (byte)85, (byte)211, byte.MaxValue), new Color32((byte)153, (byte)50, (byte)204, byte.MaxValue), new Color32((byte)75, (byte)0, (byte)130, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "grayscale", (Color32[])(object)new Color32[5] { new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue), new Color32((byte)170, (byte)170, (byte)170, byte.MaxValue), new Color32((byte)85, (byte)85, (byte)85, byte.MaxValue), new Color32((byte)0, (byte)0, (byte)0, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "early", (Color32[])(object)new Color32[5] { new Color32((byte)0, (byte)0, (byte)0, byte.MaxValue), new Color32((byte)85, (byte)85, (byte)85, byte.MaxValue), new Color32((byte)170, (byte)170, (byte)170, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "crow", (Color32[])(object)new Color32[5] { new Color32((byte)204, (byte)61, (byte)80, byte.MaxValue), new Color32((byte)153, (byte)31, (byte)39, byte.MaxValue), new Color32((byte)89, (byte)22, (byte)22, byte.MaxValue), new Color32((byte)38, (byte)15, (byte)13, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "coffee", (Color32[])(object)new Color32[5] { new Color32((byte)204, (byte)158, (byte)122, byte.MaxValue), new Color32((byte)153, (byte)116, (byte)92, byte.MaxValue), new Color32((byte)115, (byte)77, (byte)69, byte.MaxValue), new Color32((byte)77, (byte)48, (byte)46, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "winter", (Color32[])(object)new Color32[5] { new Color32((byte)159, (byte)244, (byte)229, byte.MaxValue), new Color32((byte)0, (byte)185, (byte)190, byte.MaxValue), new Color32((byte)0, (byte)95, (byte)140, byte.MaxValue), new Color32((byte)0, (byte)43, (byte)89, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "bombrush-orange", (Color32[])(object)new Color32[5] { new Color32(byte.MaxValue, (byte)218, (byte)170, byte.MaxValue), new Color32((byte)220, (byte)101, (byte)57, byte.MaxValue), new Color32((byte)143, (byte)55, (byte)42, byte.MaxValue), new Color32((byte)59, (byte)25, (byte)31, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } }, { "bombrush-blue", (Color32[])(object)new Color32[5] { new Color32((byte)216, (byte)228, byte.MaxValue, byte.MaxValue), new Color32((byte)63, (byte)111, (byte)194, byte.MaxValue), new Color32((byte)38, (byte)60, (byte)136, byte.MaxValue), new Color32((byte)13, (byte)13, (byte)63, byte.MaxValue), new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) } } }; public static string paletteName { get { return _paletteName; } set { _paletteName = NormalizePaletteName(value); } } public static string NormalizePaletteName(string paletteName) { return NormalizePaletteName(paletteName, "dmg"); } public static string NormalizePaletteName(string paletteName, string fallbackPaletteName) { string text = (string.IsNullOrWhiteSpace(fallbackPaletteName) ? "dmg" : fallbackPaletteName.Trim().ToLowerInvariant()); if (!palettes.ContainsKey(text)) { text = "dmg"; } if (string.IsNullOrWhiteSpace(paletteName)) { return text; } string text2 = paletteName.Trim().ToLowerInvariant(); return palettes.ContainsKey(text2) ? text2 : text; } } public enum GameBoyButton : byte { A = 1, B = 2, Select = 4, Start = 8, Right = 0x10, Left = 0x20, Up = 0x40, Down = 0x80 } public class Joypad { private readonly MMU mmu; public Joypad(MMU mmu) { this.mmu = mmu; } internal void SetStateRaw(byte state) { mmu.joypadState = state; } internal byte GetState() { return mmu.joypadState; } public void SetButton(GameBoyButton button, bool pressed) { byte b = (byte)button; if (pressed) { mmu.joypadState &= (byte)(~b); } else { mmu.joypadState |= b; } } public void SaveState(BinaryWriter writer) { } public void LoadState(BinaryReader reader) { } } internal sealed class LinkCableConnectDialog : MonoBehaviour { private static LinkCableConnectDialog _instance; private static TMP_FontAsset _gameFont; private const float PanelW = 686f; private const float PaddingV = 18f; private const float TitleAreaH = 88f; private const float HintH = 44f; private TextMeshProUGUI _titleLine1; private TextMeshProUGUI _titleLine2; private TextMeshProUGUI _hintText; private GameObject _yesRow; private GameObject _noRow; private bool _fontApplied; private Action _onYes; private Action _onNo; private int _selected; private bool _inputGrace; private bool _cancelWasDown; private bool _confirmWasDown; private bool _upHeld; private bool _downHeld; private float _repeatTimer; private const float RepeatDelay = 0.34f; private const float RepeatRate = 0.075f; public static bool IsVisible => (Object)(object)_instance != (Object)null && ((Component)_instance).gameObject.activeSelf; public static void Show(string hostName, Action onYes, Action onNo) { EnsureInstance(); _instance._onYes = onYes; _instance._onNo = onNo; _instance._selected = 0; _instance._inputGrace = true; _instance._cancelWasDown = true; _instance._confirmWasDown = true; _instance._upHeld = false; _instance._downHeld = false; _instance._repeatTimer = 0f; _instance._fontApplied = false; ((TMP_Text)_instance._titleLine2).text = "Connect to " + CodeDmgRenderer.TruncatePlayerName(hostName) + "?"; _instance.UpdateHighlights(); ((Component)_instance).gameObject.SetActive(true); } public static void Hide() { if (!((Object)(object)_instance == (Object)null)) { ((Component)_instance).gameObject.SetActive(false); if ((Object)(object)EventSystem.current != (Object)null) { EventSystem.current.SetSelectedGameObject((GameObject)null); } } } public static bool TryHandlePhoneUp() { if (!IsVisible) { return false; } _instance.MoveSelection(); return true; } public static bool TryHandlePhoneDown() { if (!IsVisible) { return false; } _instance.MoveSelection(); return true; } public static bool TryHandlePhoneRight() { if (!IsVisible) { return false; } _instance.ConfirmCurrent(); return true; } public static bool TryHandlePhoneBack() { if (!IsVisible) { return false; } _instance.BackOut(); return true; } private static void EnsureInstance() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("CodeDmgLinkCableDialog"); val.transform.SetParent(((Component)Core.Instance.UIManager).transform, false); _instance = val.AddComponent(); _instance.Build(); } } private void Build() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected O, but got Unknown //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Expected O, but got Unknown //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0245: Unknown result type (might be due to invalid IL or missing references) //IL_025c: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Expected O, but got Unknown //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Unknown result type (might be due to invalid IL or missing references) //IL_039c: Unknown result type (might be due to invalid IL or missing references) //IL_03ac: Unknown result type (might be due to invalid IL or missing references) //IL_03b3: Expected O, but got Unknown //IL_03e6: Unknown result type (might be due to invalid IL or missing references) //IL_0406: Unknown result type (might be due to invalid IL or missing references) //IL_041d: Unknown result type (might be due to invalid IL or missing references) //IL_0434: Unknown result type (might be due to invalid IL or missing references) //IL_044b: Unknown result type (might be due to invalid IL or missing references) //IL_0462: Unknown result type (might be due to invalid IL or missing references) //IL_04f0: Unknown result type (might be due to invalid IL or missing references) //IL_0500: Unknown result type (might be due to invalid IL or missing references) //IL_0507: Expected O, but got Unknown //IL_0564: Unknown result type (might be due to invalid IL or missing references) //IL_0599: Unknown result type (might be due to invalid IL or missing references) //IL_05b0: Unknown result type (might be due to invalid IL or missing references) //IL_05c7: Unknown result type (might be due to invalid IL or missing references) //IL_05de: Unknown result type (might be due to invalid IL or missing references) //IL_05f5: Unknown result type (might be due to invalid IL or missing references) Canvas val = ((Component)this).gameObject.AddComponent(); val.renderMode = (RenderMode)0; val.sortingOrder = 501; ((Component)this).gameObject.AddComponent(); GameObject val2 = new GameObject("Panel"); val2.transform.SetParent(((Component)this).transform, false); Image val3 = val2.AddComponent(); ((Graphic)val3).color = new Color(0.08f, 0.08f, 0.08f, 0.97f); RectTransform rectTransform = ((Graphic)val3).rectTransform; rectTransform.anchorMin = new Vector2(0.5f, 0.5f); rectTransform.anchorMax = new Vector2(0.5f, 0.5f); rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.anchoredPosition = Vector2.zero; GameObject val4 = new GameObject("Accent"); val4.transform.SetParent(val2.transform, false); Image val5 = val4.AddComponent(); ((Graphic)val5).color = new Color(10f / 51f, 0.34901962f, 0.6509804f, 1f); RectTransform rectTransform2 = ((Graphic)val5).rectTransform; rectTransform2.anchorMin = new Vector2(0f, 1f); rectTransform2.anchorMax = new Vector2(1f, 1f); rectTransform2.pivot = new Vector2(0.5f, 1f); rectTransform2.anchoredPosition = Vector2.zero; rectTransform2.sizeDelta = new Vector2(0f, 5f); GameObject val6 = new GameObject("TitleLine1"); val6.transform.SetParent(val2.transform, false); _titleLine1 = val6.AddComponent(); ((TMP_Text)_titleLine1).text = "GB-EMU LINK"; ((TMP_Text)_titleLine1).fontSize = 30f; ((TMP_Text)_titleLine1).fontStyle = (FontStyles)1; ((Graphic)_titleLine1).color = new Color(0.35686275f, 0.56078434f, 0.8745098f, 1f); ((TMP_Text)_titleLine1).alignment = (TextAlignmentOptions)514; RectTransform rectTransform3 = ((TMP_Text)_titleLine1).rectTransform; rectTransform3.anchorMin = new Vector2(0f, 1f); rectTransform3.anchorMax = new Vector2(1f, 1f); rectTransform3.pivot = new Vector2(0.5f, 1f); rectTransform3.anchoredPosition = new Vector2(0f, -18f); rectTransform3.sizeDelta = new Vector2(0f, 40f); GameObject val7 = new GameObject("TitleLine2"); val7.transform.SetParent(val2.transform, false); _titleLine2 = val7.AddComponent(); ((TMP_Text)_titleLine2).text = "Connect to ...?"; ((TMP_Text)_titleLine2).fontSize = 22f; ((Graphic)_titleLine2).color = new Color(0.75f, 0.75f, 0.75f, 1f); ((TMP_Text)_titleLine2).alignment = (TextAlignmentOptions)514; ((TMP_Text)_titleLine2).enableWordWrapping = false; RectTransform rectTransform4 = ((TMP_Text)_titleLine2).rectTransform; rectTransform4.anchorMin = new Vector2(0f, 1f); rectTransform4.anchorMax = new Vector2(1f, 1f); rectTransform4.pivot = new Vector2(0.5f, 1f); rectTransform4.anchoredPosition = new Vector2(0f, -60f); rectTransform4.sizeDelta = new Vector2(0f, 30f); GameObject val8 = new GameObject("Sep"); val8.transform.SetParent(val2.transform, false); Image val9 = val8.AddComponent(); ((Graphic)val9).color = new Color(1f, 1f, 1f, 0.12f); RectTransform rectTransform5 = ((Graphic)val9).rectTransform; rectTransform5.anchorMin = new Vector2(0f, 1f); rectTransform5.anchorMax = new Vector2(1f, 1f); rectTransform5.pivot = new Vector2(0.5f, 1f); rectTransform5.anchoredPosition = new Vector2(0f, -100f); rectTransform5.sizeDelta = new Vector2(-24f, 1f); float num = 56f; float num2 = 6f; float num3 = 110f; float num4 = num * 2f + num2; _yesRow = BuildOptionRow(val2.transform, "Yes!", 0f, num, num3); _noRow = BuildOptionRow(val2.transform, "Nope!", num + num2, num, num3); float num5 = num3 + num4 + 14f + 44f + 18f; rectTransform.sizeDelta = new Vector2(686f, num5); GameObject val10 = new GameObject("Hint"); val10.transform.SetParent(val2.transform, false); _hintText = val10.AddComponent(); ((TMP_Text)_hintText).text = "Up/Down: Nav A/Right: Link B/Left: Exit"; ((TMP_Text)_hintText).fontSize = 19f; ((Graphic)_hintText).color = new Color(0.6f, 0.6f, 0.6f, 1f); ((TMP_Text)_hintText).alignment = (TextAlignmentOptions)514; RectTransform rectTransform6 = ((TMP_Text)_hintText).rectTransform; rectTransform6.anchorMin = new Vector2(0f, 0f); rectTransform6.anchorMax = new Vector2(1f, 0f); rectTransform6.pivot = new Vector2(0.5f, 0f); rectTransform6.anchoredPosition = new Vector2(0f, 18f); rectTransform6.sizeDelta = new Vector2(0f, 44f); ((Component)this).gameObject.SetActive(false); } private GameObject BuildOptionRow(Transform parent, string label, float yOffset, float rowH, float listTop) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown //IL_0028: 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) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Row_" + label); val.transform.SetParent(parent, false); Image val2 = val.AddComponent(); ((Graphic)val2).color = Color.clear; RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0.03f, 1f); rectTransform.anchorMax = new Vector2(0.97f, 1f); rectTransform.pivot = new Vector2(0.5f, 1f); rectTransform.anchoredPosition = new Vector2(0f, 0f - (listTop + yOffset)); rectTransform.sizeDelta = new Vector2(0f, rowH); GameObject val3 = new GameObject("Label"); val3.transform.SetParent(val.transform, false); TextMeshProUGUI val4 = val3.AddComponent(); ((TMP_Text)val4).text = label; ((TMP_Text)val4).fontSize = 24f; ((Graphic)val4).color = Color.white; ((TMP_Text)val4).alignment = (TextAlignmentOptions)514; RectTransform rectTransform2 = ((TMP_Text)val4).rectTransform; rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; Vector2 offsetMin = (rectTransform2.offsetMax = Vector2.zero); rectTransform2.offsetMin = offsetMin; return val; } private void Update() { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) if (!((Component)this).gameObject.activeSelf) { return; } if (!_fontApplied) { ApplyGameFont(); } if (_inputGrace) { _inputGrace = false; return; } if (PopupState.ShouldSuppressMenuInputForChat()) { _upHeld = false; _downHeld = false; _repeatTimer = 0f; _confirmWasDown = true; _cancelWasDown = true; return; } CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; bool flag = Input.GetKey(configSettings.Up.Value) || Input.GetKey((KeyCode)273) || Input.GetAxisRaw("Vertical") > 0.5f; bool flag2 = Input.GetKey(configSettings.Down.Value) || Input.GetKey((KeyCode)274) || Input.GetAxisRaw("Vertical") < -0.5f; if (PhoneMenuInputRouter.ConsumedUpThisFrame() || PhoneMenuInputRouter.ConsumedDownThisFrame()) { _upHeld = flag; _downHeld = flag2; _repeatTimer = 0f; } else { if (flag && !_upHeld) { _upHeld = true; _repeatTimer = 0f; MoveSelection(); } else if (!flag) { _upHeld = false; } if (flag2 && !_downHeld) { _downHeld = true; _repeatTimer = 0f; MoveSelection(); } else if (!flag2) { _downHeld = false; } if (_upHeld || _downHeld) { _repeatTimer += Time.unscaledDeltaTime; if (_repeatTimer >= 0.34f) { _repeatTimer -= 0.075f; MoveSelection(); } } } bool flag3 = Input.GetKey(configSettings.A.Value) || Input.GetKey(configSettings.Right.Value) || Input.GetKey((KeyCode)13) || Input.GetKey((KeyCode)271) || Input.GetKey((KeyCode)275) || Input.GetKey((KeyCode)330) || Input.GetAxisRaw("Horizontal") > 0.5f; bool flag4 = flag3 && !_confirmWasDown; _confirmWasDown = flag3; bool flag5 = Input.GetKey(configSettings.B.Value) || Input.GetKey(configSettings.Left.Value) || Input.GetKey((KeyCode)27) || Input.GetKey((KeyCode)276) || Input.GetKey((KeyCode)331) || Input.GetAxisRaw("Horizontal") < -0.5f; bool flag6 = flag5 && !_cancelWasDown; _cancelWasDown = flag5; if (flag4) { ConfirmCurrent(); } else if (flag6) { BackOut(); } } private void MoveSelection() { _selected = ((_selected == 0) ? 1 : 0); UpdateHighlights(); PlaySelectSfx(); } private void ConfirmCurrent() { PlayConfirmSfx(); PopupState.SuppressPhoneNavFor(); if (_selected == 0) { Hide(); _onYes?.Invoke(); } else { Hide(); _onNo?.Invoke(); } } private void BackOut() { PlayBackSfx(); PopupState.SuppressPhoneNavFor(); Hide(); _onNo?.Invoke(); } private static void PlaySelectSfx() { try { AppCodeDmg.PlayMenuSelectSFX(); } catch { } } private static void PlayConfirmSfx() { try { AppCodeDmg.PlayMenuConfirmSFX(); } catch { } } private static void PlayBackSfx() { try { AppCodeDmg.PlayMenuBackSFX(); } catch { } } private void UpdateHighlights() { SetRowHighlight(_yesRow, _selected == 0); SetRowHighlight(_noRow, _selected == 1); } private static void SetRowHighlight(GameObject row, bool selected) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)row == (Object)null)) { Image component = row.GetComponent(); Transform obj = row.transform.Find("Label"); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = (Color)(selected ? new Color(0.21960784f, 0.3019608f, 0.61960787f, 0.35f) : Color.clear); } if ((Object)(object)val != (Object)null) { ((Graphic)val).color = (Color)(selected ? Color.white : new Color(0.65f, 0.65f, 0.65f, 1f)); } } } private void ApplyGameFont() { if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array = Resources.FindObjectsOfTypeAll(); TextMeshProUGUI[] array2 = array; foreach (TextMeshProUGUI val in array2) { if ((Object)(object)val != (Object)null && (Object)(object)((TMP_Text)val).font != (Object)null && ((Object)val).name == "HeaderLabel") { _gameFont = ((TMP_Text)val).font; break; } } if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array3 = array; foreach (TextMeshProUGUI val2 in array3) { if ((Object)(object)val2 != (Object)null && (Object)(object)((TMP_Text)val2).font != (Object)null) { _gameFont = ((TMP_Text)val2).font; break; } } } } if ((Object)(object)_gameFont == (Object)null) { return; } TMP_Text[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); foreach (TMP_Text val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && (Object)(object)val3.font != (Object)(object)_gameFont) { val3.font = _gameFont; } } _fontApplied = true; } } public enum LinkCableState { Disconnected, Linking, WaitingAccept, Connected } public sealed class LinkCableManager : IDisposable { private static readonly Regex _emojiRx = new Regex("[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\u2600-\\u27BF]|]*>|<[^>]+>", RegexOptions.Compiled); private const float InviteTimeoutSec = 30f; private const float FailureStatusSeconds = 5f; private const float RomErrorStatusSeconds = 5f; private const byte DisconnectReasonMissingRom = 1; private float _romMismatchTimer; private float _desyncWarningTimer; private string _localRomHash; private string _remoteRomHash; private string _peerRomPath; private float _inviteTimeoutTimer; private float _pendingInviteTimer; private float _failureStatusTimer; private readonly LinkCableTransport _transport; private AppCodeDmg _app; private ushort _pendingInviterPlayerId; private byte[] _checkpointState; private int? _remoteLinkInputDelayFrames; private bool _saveStateSent; private bool _waitingForSync; public LinkCableState State { get; private set; } = LinkCableState.Disconnected; public ushort LinkedPlayerId { get; private set; } public string LinkedPlayerName { get; private set; } public bool IsHost { get; private set; } public string FailureStatusText { get; private set; } public bool HasFailureStatus => _failureStatusTimer > 0f && !string.IsNullOrEmpty(FailureStatusText); public int EffectiveLinkInputDelayFrames => GetEffectiveLinkInputDelayFrames(); public bool ShowRomMismatch { get; private set; } public string RomMismatchMessage { get; private set; } public bool ShowDesyncWarning { get; private set; } public bool HasPendingInvite { get; private set; } public string PendingInviterName { get; private set; } public bool WaitingForSync => _waitingForSync; public event Action StateChanged; public LinkCableManager(LinkCableTransport transport) { _transport = transport; _transport.InviteReceived += OnInviteReceived; _transport.AcceptReceived += OnAcceptReceived; _transport.DeclineReceived += OnDeclineReceived; _transport.DisconnectReceived += OnDisconnectReceived; _transport.RomHashReceived += OnRomHashReceived; _transport.SaveStateReceived += OnSaveStateReceived; _transport.LinkSettingsReceived += OnLinkSettingsReceived; _transport.JoypadReceived += OnJoypadReceived; ClientController.ServerDisconnect = (Action)Delegate.Combine(ClientController.ServerDisconnect, new Action(OnServerDisconnected)); ClientController.PlayerDisconnected = (Action)Delegate.Combine(ClientController.PlayerDisconnected, new Action(OnPlayerDisconnected)); } public void Dispose() { _transport.InviteReceived -= OnInviteReceived; _transport.AcceptReceived -= OnAcceptReceived; _transport.DeclineReceived -= OnDeclineReceived; _transport.DisconnectReceived -= OnDisconnectReceived; _transport.RomHashReceived -= OnRomHashReceived; _transport.SaveStateReceived -= OnSaveStateReceived; _transport.LinkSettingsReceived -= OnLinkSettingsReceived; _transport.JoypadReceived -= OnJoypadReceived; ClientController.ServerDisconnect = (Action)Delegate.Remove(ClientController.ServerDisconnect, new Action(OnServerDisconnected)); ClientController.PlayerDisconnected = (Action)Delegate.Remove(ClientController.PlayerDisconnected, new Action(OnPlayerDisconnected)); } public void SetApp(AppCodeDmg app) { _app = app; } public void SetLocalRomHash(string h) { _localRomHash = h; } public void Tick(float dt) { if (_failureStatusTimer > 0f) { _failureStatusTimer -= dt; if (_failureStatusTimer <= 0f) { _failureStatusTimer = 0f; FailureStatusText = null; this.StateChanged?.Invoke(); } } if (State == LinkCableState.WaitingAccept) { if (LinkedPlayerId != 0 && !PlayerExists(LinkedPlayerId)) { FailPendingLink("LINK FAILED: PLAYER GONE"); return; } _inviteTimeoutTimer += dt; if (_inviteTimeoutTimer >= 30f) { FailPendingLink("LINK FAILED: TIMED OUT"); return; } } if (HasPendingInvite) { _pendingInviteTimer += dt; if (_pendingInviteTimer >= 30f) { ClearPendingInvite(); ShowFailureStatus("LINK FAILED: YOU'RE TOO SLOW"); return; } } if (ShowRomMismatch) { _romMismatchTimer -= dt; if (_romMismatchTimer <= 0f) { ShowRomMismatch = false; this.StateChanged?.Invoke(); } } if (ShowDesyncWarning) { _desyncWarningTimer -= dt; if (_desyncWarningTimer <= 0f) { ShowDesyncWarning = false; this.StateChanged?.Invoke(); } } if (State == LinkCableState.Connected) { CodeDmgEmulator codeDmgEmulator = _app?.GetEmulator(); if (codeDmgEmulator != null && codeDmgEmulator.DesyncDetected) { codeDmgEmulator.DesyncDetected = false; ShowDesyncWarning = true; _desyncWarningTimer = 5f; this.StateChanged?.Invoke(); } } if (State != LinkCableState.Connected || LinkedPlayerId == 0 || _waitingForSync) { return; } CodeDmgEmulator codeDmgEmulator2 = _app?.GetEmulator(); if (codeDmgEmulator2 != null) { uint frame; byte state; while (codeDmgEmulator2.TryDequeueJoypadPacket(out frame, out state)) { _transport.SendJoypad(LinkedPlayerId, frame, state); } } } public void HostSendInvite(ushort targetPlayerId, string hostDisplayName, string targetDisplayName = null) { if (State == LinkCableState.Disconnected && _transport.Connected) { ClearFailureStatus(); if (targetPlayerId == 0 || !PlayerExists(targetPlayerId)) { ShowFailureStatus("LINK FAILED: PLAYER GONE"); return; } LinkedPlayerId = targetPlayerId; LinkedPlayerName = (string.IsNullOrEmpty(targetDisplayName) ? string.Empty : SanitizeName(targetDisplayName)); IsHost = true; State = LinkCableState.WaitingAccept; _inviteTimeoutTimer = 0f; _transport.SendInvite(targetPlayerId, SanitizeName(hostDisplayName)); this.StateChanged?.Invoke(); } } public void ClientAcceptInvite() { if (!HasPendingInvite) { return; } if (_pendingInviterPlayerId != 0 && !PlayerExists(_pendingInviterPlayerId)) { ClearPendingInvite(); ShowFailureStatus("LINK FAILED: PLAYER GONE"); return; } HasPendingInvite = false; _pendingInviteTimer = 0f; LinkedPlayerId = _pendingInviterPlayerId; LinkedPlayerName = SanitizeName(PendingInviterName); IsHost = false; State = LinkCableState.Connected; _transport.SendAccept(_pendingInviterPlayerId, SanitizeName(GetLocalPlayerName())); if (!string.IsNullOrEmpty(_localRomHash)) { _transport.SendRomHash(LinkedPlayerId, _localRomHash); } SendOurSaveState(); PostConnectionNotifications(LinkedPlayerName); this.StateChanged?.Invoke(); } public void ClientDeclineInvite() { if (HasPendingInvite) { ushort pendingInviterPlayerId = _pendingInviterPlayerId; HasPendingInvite = false; _pendingInviterPlayerId = 0; PendingInviterName = null; _pendingInviteTimer = 0f; _transport.SendDecline(pendingInviterPlayerId); this.StateChanged?.Invoke(); } } public void Drop() { if (State != LinkCableState.Disconnected || HasPendingInvite) { bool flag = State != LinkCableState.Disconnected || HasPendingInvite; if (LinkedPlayerId != 0 && State != LinkCableState.Disconnected) { _transport.SendDisconnect(LinkedPlayerId, 0); } TearDownLinkedEmulator(); ClearLinkState(); if (flag) { ShowFailureStatus("LINK DISCONNECTED"); } else { this.StateChanged?.Invoke(); } } } private void DropSilent(byte reason = 0) { if (LinkedPlayerId != 0 && State != LinkCableState.Disconnected) { _transport.SendDisconnect(LinkedPlayerId, reason); } TearDownLinkedEmulator(); ClearLinkState(); this.StateChanged?.Invoke(); } private void SendOurSaveState() { if (_saveStateSent) { return; } CodeDmgEmulator codeDmgEmulator = _app?.GetEmulator(); if (codeDmgEmulator == null || LinkedPlayerId == 0) { return; } _saveStateSent = true; _waitingForSync = true; try { _transport.SendLinkSettings(LinkedPlayerId, GetLocalConfiguredLinkInputDelayFrames()); ApplyEffectiveLinkInputDelayToEmulator(); _checkpointState = codeDmgEmulator.SerializeState(); codeDmgEmulator.DeserializeState(_checkpointState); Debug.Log((object)$"[CodeDMG] SaveState SEND to {LinkedPlayerId}: {_checkpointState.Length} bytes"); _transport.SendSaveState(LinkedPlayerId, _checkpointState); } catch (Exception ex) { Debug.LogError((object)("[CodeDMG] SaveState serialize/rewind error: " + ex.Message)); } } private void TearDownLinkedEmulator() { (_app?.GetEmulator())?.DetachLinkedEmulator(); } private void OnInviteReceived(ushort fromId, string senderName) { if (State == LinkCableState.Disconnected) { ClearFailureStatus(); HasPendingInvite = true; _pendingInviterPlayerId = fromId; _pendingInviteTimer = 0f; PendingInviterName = SanitizeName(senderName); this.StateChanged?.Invoke(); } } private void OnAcceptReceived(ushort fromId, string clientName) { if (State == LinkCableState.WaitingAccept && fromId == LinkedPlayerId) { LinkedPlayerName = SanitizeName(clientName); State = LinkCableState.Connected; _inviteTimeoutTimer = 0f; ClearFailureStatus(); if (!string.IsNullOrEmpty(_localRomHash)) { _transport.SendRomHash(LinkedPlayerId, _localRomHash); } SendOurSaveState(); PostConnectionNotifications(LinkedPlayerName); this.StateChanged?.Invoke(); } } private void OnDeclineReceived(ushort fromId) { if (fromId == LinkedPlayerId && State == LinkCableState.WaitingAccept) { FailPendingLink("LINK FAILED: DECLINED", sendDisc: false); } } private void OnServerDisconnected() { if (State != LinkCableState.Disconnected || HasPendingInvite) { TearDownLinkedEmulator(); ClearLinkState(); ShowFailureStatus("LINK DISCONNECTED"); } } private void OnPlayerDisconnected(ushort playerId) { if ((playerId == LinkedPlayerId || playerId == _pendingInviterPlayerId) && (State != LinkCableState.Disconnected || HasPendingInvite)) { TearDownLinkedEmulator(); ClearLinkState(); ShowFailureStatus("LINK DISCONNECTED"); } } private void OnDisconnectReceived(ushort fromId, byte reason) { if (fromId == LinkedPlayerId || fromId == _pendingInviterPlayerId) { bool flag = HasPendingInvite && fromId == _pendingInviterPlayerId; TearDownLinkedEmulator(); ClearLinkState(); if (reason == 1) { ShowRomError("LINK DISCONNECTED!", "Peer is missing your ROM\nin their RomDirectory!"); } else { ShowFailureStatus(flag ? "LINK FAILED: YOU'RE TOO SLOW" : "LINK DISCONNECTED"); } } } private void OnRomHashReceived(ushort fromId, string hash) { if (fromId == LinkedPlayerId) { _remoteRomHash = hash; CheckRomCompatibility(); } } private void OnSaveStateReceived(ushort fromId, byte[] data) { if (fromId == LinkedPlayerId && State == LinkCableState.Connected) { CodeDmgEmulator codeDmgEmulator = _app?.GetEmulator(); if (codeDmgEmulator != null) { Debug.Log((object)$"[CodeDMG] SaveState RECEIVE from {fromId}: {data.Length} bytes"); codeDmgEmulator.SetLinkInputDelayFrames(GetEffectiveLinkInputDelayFrames()); string romPath = ((!string.IsNullOrEmpty(_peerRomPath)) ? _peerRomPath : codeDmgEmulator.RomPath); codeDmgEmulator.AttachLinkedEmulator(romPath, codeDmgEmulator.BootRomPath, string.Empty); codeDmgEmulator.SetLinkPlayerIds(_transport.LocalPlayerId, LinkedPlayerId); codeDmgEmulator.DeserializeLinkedState(data); _waitingForSync = false; Debug.Log((object)"[CodeDMG] Sync complete — linked emulator loaded from peer checkpoint"); } } } private void OnLinkSettingsReceived(ushort fromId, int inputDelayFrames) { if (fromId == LinkedPlayerId && State == LinkCableState.Connected) { _remoteLinkInputDelayFrames = CodeDmgEmulator.NormalizeLinkInputDelayFrames(inputDelayFrames); ApplyEffectiveLinkInputDelayToEmulator(); } } private void OnJoypadReceived(ushort fromId, uint frame, byte state) { if (fromId == LinkedPlayerId && State == LinkCableState.Connected) { _app?.GetEmulator()?.QueuePeerJoypad(frame, state); } } private void CheckRomCompatibility() { if (string.IsNullOrEmpty(_localRomHash) || string.IsNullOrEmpty(_remoteRomHash) || string.Equals(_localRomHash, _remoteRomHash, StringComparison.OrdinalIgnoreCase)) { return; } string romTitle = GetRomTitle(_localRomHash); string romTitle2 = GetRomTitle(_remoteRomHash); if (IsPokemonCompatible(romTitle, romTitle2)) { string romCrc = GetRomCrc(_remoteRomHash); if (!string.IsNullOrEmpty(romCrc)) { _peerRomPath = FindRomByCrc(romCrc); if (!string.IsNullOrEmpty(_peerRomPath)) { return; } } string romTitle3 = GetRomTitle(_remoteRomHash); DropSilent(1); ShowRomError("LINK DISCONNECTED!", "Missing peer ROM in RomDirectory:\n" + romTitle3 + ".gb/.gbc"); } else { DropSilent(0); ShowRomError("LINK DISCONNECTED!", "Different ROMs detected!"); } } private static string GetRomCrc(string hash) { if (string.IsNullOrEmpty(hash)) { return string.Empty; } int num = hash.IndexOf(':'); return (num >= 0) ? hash.Substring(num + 1) : string.Empty; } private string FindRomByCrc(string crc) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); try { hashSet.Add(CodeDmgPlugin.Instance.PluginDirectory); } catch { } try { string text = _app?.GetEmulator()?.RomPath; if (!string.IsNullOrEmpty(text)) { hashSet.Add(Path.GetDirectoryName(text)); } } catch { } foreach (string item in hashSet) { if (string.IsNullOrEmpty(item) || !Directory.Exists(item)) { continue; } try { string[] files = Directory.GetFiles(item, "*.*"); foreach (string text2 in files) { string text3 = Path.GetExtension(text2).ToLowerInvariant(); if (!(text3 != ".gb") || !(text3 != ".gbc")) { byte[] data = File.ReadAllBytes(text2); if (CodeDmgEmulator.ComputeCrc32Public(data).ToString("X8").Equals(crc, StringComparison.OrdinalIgnoreCase)) { return text2; } } } } catch { } } return string.Empty; } private static bool IsPokemonCompatible(string a, string b) { return IsPokemonTitle(a) && IsPokemonTitle(b); } private static bool IsPokemonTitle(string title) { if (string.IsNullOrEmpty(title)) { return false; } string text = title.ToUpperInvariant(); return text.StartsWith("POKEMON") || text.StartsWith("PM_CRYSTAL") || text.StartsWith("POKECARD"); } private static string GetRomTitle(string hash) { if (string.IsNullOrEmpty(hash)) { return string.Empty; } int num = hash.IndexOf(':'); return (num > 0) ? hash.Substring(0, num) : string.Empty; } internal void FlushJoypadPacket(uint frame, byte state) { if (State == LinkCableState.Connected && LinkedPlayerId != 0 && !_waitingForSync) { _transport.SendJoypad(LinkedPlayerId, frame, state); } } private int GetLocalConfiguredLinkInputDelayFrames() { int frames = CodeDmgPlugin.ConfigSettings?.LinkInputDelayFrames?.Value ?? 16; return CodeDmgEmulator.NormalizeLinkInputDelayFrames(frames); } private int GetEffectiveLinkInputDelayFrames() { int localConfiguredLinkInputDelayFrames = GetLocalConfiguredLinkInputDelayFrames(); ushort lobbyHostId = GetLobbyHostId(); if (lobbyHostId == _transport.LocalPlayerId) { return localConfiguredLinkInputDelayFrames; } if (lobbyHostId == LinkedPlayerId && _remoteLinkInputDelayFrames.HasValue) { return _remoteLinkInputDelayFrames.Value; } if (lobbyHostId == 0 && !IsHost && _remoteLinkInputDelayFrames.HasValue) { return _remoteLinkInputDelayFrames.Value; } return localConfiguredLinkInputDelayFrames; } private void ApplyEffectiveLinkInputDelayToEmulator() { _app?.GetEmulator()?.SetLinkInputDelayFrames(GetEffectiveLinkInputDelayFrames()); } private static ushort GetLobbyHostId() { try { ClientController instance = ClientController.Instance; object obj; if (instance == null) { obj = null; } else { ClientLobbyManager clientLobbyManager = instance.ClientLobbyManager; obj = ((clientLobbyManager != null) ? clientLobbyManager.CurrentLobby : null); } Lobby val = (Lobby)obj; if (val?.LobbyState == null) { return 0; } return val.LobbyState.HostId; } catch { return 0; } } private void FailPendingLink(string message, bool sendDisc = true) { if (sendDisc && LinkedPlayerId != 0 && State != LinkCableState.Disconnected && PlayerExists(LinkedPlayerId)) { _transport.SendDisconnect(LinkedPlayerId, 0); } TearDownLinkedEmulator(); ClearLinkState(); ShowFailureStatus(message); } private void ClearLinkState() { HasPendingInvite = false; _pendingInviterPlayerId = 0; PendingInviterName = null; _pendingInviteTimer = 0f; LinkedPlayerId = 0; LinkedPlayerName = null; IsHost = false; State = LinkCableState.Disconnected; _remoteRomHash = null; _peerRomPath = null; ShowRomMismatch = false; RomMismatchMessage = null; ShowDesyncWarning = false; _saveStateSent = false; _waitingForSync = false; _checkpointState = null; _remoteLinkInputDelayFrames = null; LinkCableConnectDialog.Hide(); } private void ClearPendingInvite() { HasPendingInvite = false; _pendingInviterPlayerId = 0; PendingInviterName = null; _pendingInviteTimer = 0f; LinkCableConnectDialog.Hide(); this.StateChanged?.Invoke(); } private void ShowFailureStatus(string message, float duration = 5f) { FailureStatusText = message; _failureStatusTimer = duration; this.StateChanged?.Invoke(); } private void ShowRomError(string statusMsg, string detailMsg, float duration = 5f) { ShowFailureStatus(statusMsg); RomMismatchMessage = detailMsg; ShowRomMismatch = true; _romMismatchTimer = duration; this.StateChanged?.Invoke(); } private void ClearFailureStatus() { FailureStatusText = null; _failureStatusTimer = 0f; } private void PostConnectionNotifications(string otherName) { ShowChatNotification(IsHost ? (otherName + " connected!") : ("Connected to " + otherName + "!")); } private static void ShowChatNotification(string message) { try { ClientController instance = ClientController.Instance; if ((Object)(object)instance != (Object)null) { MethodInfo method = ((object)instance).GetType().GetMethod("ShowLocalNotification", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { method.Invoke(instance, new object[1] { message }); return; } } } catch { } Debug.Log((object)("[CodeDMG] " + message)); } public static string SanitizeName(string raw) { if (string.IsNullOrWhiteSpace(raw)) { return "Player"; } string text = _emojiRx.Replace(raw, string.Empty).Trim(); return string.IsNullOrWhiteSpace(text) ? "Player" : text; } private static bool PlayerExists(ushort id) { try { ClientController instance = ClientController.Instance; return instance != null && instance.Players?.ContainsKey(id) == true; } catch { return false; } } private static string GetLocalPlayerName() { try { ClientController instance = ClientController.Instance; if ((Object)(object)instance == (Object)null) { return string.Empty; } if (!instance.Players.TryGetValue(instance.LocalID, out var value) || value == null) { return string.Empty; } return MPUtility.GetPlayerDisplayName(value.ClientState) ?? string.Empty; } catch { return string.Empty; } } } internal sealed class LinkCablePlayerList : MonoBehaviour { private static LinkCablePlayerList _instance; private static TMP_FontAsset _gameFont; private const float PanelW = 686f; private const float RowH = 56f; private const float RowSpacing = 6f; private const float TitleAreaH = 88f; private const float HintH = 44f; private const float PaddingV = 18f; private const int MaxVisible = 7; private GameObject _panelGo; private TextMeshProUGUI _titleLine1; private TextMeshProUGUI _titleLine2; private TextMeshProUGUI _hintText; private RectTransform _listViewport; private RectTransform _listContent; private Action _onPick; private bool _fontApplied; private List> _players = new List>(); private List _rows = new List(); private int _selectedIndex; private int _windowStart; private float _repeatTimer; private const float RepeatDelay = 0.34f; private const float RepeatRate = 0.075f; private bool _upHeld; private bool _downHeld; private bool _inputGrace; private bool _cancelWasDown; private bool _confirmWasDown; public static bool IsVisible => (Object)(object)_instance != (Object)null && ((Component)_instance).gameObject.activeSelf; public static bool TryHandlePhoneUp() { if (!IsVisible) { return false; } _instance.Move(-1); return true; } public static bool TryHandlePhoneDown() { if (!IsVisible) { return false; } _instance.Move(1); return true; } public static bool TryHandlePhoneRight() { if (!IsVisible) { return false; } _instance.ConfirmCurrent(); return true; } public static bool TryHandlePhoneBack() { if (!IsVisible) { return false; } _instance.BackOut(); return true; } public static void Show(Action onPick) { EnsureInstance(); _instance._onPick = onPick; _instance._inputGrace = true; _instance._cancelWasDown = true; _instance._confirmWasDown = true; ((Component)_instance).gameObject.SetActive(true); _instance._fontApplied = false; _instance.Refresh(); } public static void Hide() { if (!((Object)(object)_instance == (Object)null)) { _instance.Close(); } } private void Close() { ((Component)this).gameObject.SetActive(false); if ((Object)(object)EventSystem.current != (Object)null) { EventSystem.current.SetSelectedGameObject((GameObject)null); } } private static void EnsureInstance() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("CodeDmgLinkCableList"); val.transform.SetParent(((Component)Core.Instance.UIManager).transform, false); _instance = val.AddComponent(); _instance.Build(); } } private void Build() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Expected O, but got Unknown //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Expected O, but got Unknown //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Expected O, but got Unknown //IL_032f: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_037b: Unknown result type (might be due to invalid IL or missing references) //IL_0392: Unknown result type (might be due to invalid IL or missing references) //IL_03a9: Unknown result type (might be due to invalid IL or missing references) //IL_03c0: Unknown result type (might be due to invalid IL or missing references) //IL_03d0: Unknown result type (might be due to invalid IL or missing references) //IL_03d7: Expected O, but got Unknown //IL_040f: Unknown result type (might be due to invalid IL or missing references) //IL_042f: Unknown result type (might be due to invalid IL or missing references) //IL_0446: Unknown result type (might be due to invalid IL or missing references) //IL_045d: Unknown result type (might be due to invalid IL or missing references) //IL_0474: Unknown result type (might be due to invalid IL or missing references) //IL_048b: Unknown result type (might be due to invalid IL or missing references) //IL_049b: Unknown result type (might be due to invalid IL or missing references) //IL_04a2: Expected O, but got Unknown //IL_04e0: Unknown result type (might be due to invalid IL or missing references) //IL_04fb: Unknown result type (might be due to invalid IL or missing references) //IL_0516: Unknown result type (might be due to invalid IL or missing references) //IL_0531: Unknown result type (might be due to invalid IL or missing references) //IL_054c: Unknown result type (might be due to invalid IL or missing references) //IL_055c: Unknown result type (might be due to invalid IL or missing references) //IL_0563: Expected O, but got Unknown //IL_0595: Unknown result type (might be due to invalid IL or missing references) //IL_05b0: Unknown result type (might be due to invalid IL or missing references) //IL_05cb: Unknown result type (might be due to invalid IL or missing references) //IL_05dc: Unknown result type (might be due to invalid IL or missing references) //IL_05ed: Unknown result type (might be due to invalid IL or missing references) //IL_05fd: Unknown result type (might be due to invalid IL or missing references) //IL_0604: Expected O, but got Unknown //IL_0666: Unknown result type (might be due to invalid IL or missing references) //IL_069b: Unknown result type (might be due to invalid IL or missing references) //IL_06b2: Unknown result type (might be due to invalid IL or missing references) //IL_06c9: Unknown result type (might be due to invalid IL or missing references) //IL_06e0: Unknown result type (might be due to invalid IL or missing references) //IL_06f7: Unknown result type (might be due to invalid IL or missing references) Canvas val = ((Component)this).gameObject.AddComponent(); val.renderMode = (RenderMode)0; val.sortingOrder = 500; ((Component)this).gameObject.AddComponent(); _panelGo = new GameObject("Panel"); _panelGo.transform.SetParent(((Component)this).transform, false); Image val2 = _panelGo.AddComponent(); ((Graphic)val2).color = new Color(0.08f, 0.08f, 0.08f, 0.97f); RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0.5f, 0.5f); rectTransform.anchorMax = new Vector2(0.5f, 0.5f); rectTransform.pivot = new Vector2(0.5f, 0.5f); rectTransform.anchoredPosition = Vector2.zero; rectTransform.sizeDelta = new Vector2(686f, 400f); GameObject val3 = new GameObject("Accent"); val3.transform.SetParent(_panelGo.transform, false); Image val4 = val3.AddComponent(); ((Graphic)val4).color = new Color(10f / 51f, 0.34901962f, 0.6509804f, 1f); RectTransform rectTransform2 = ((Graphic)val4).rectTransform; rectTransform2.anchorMin = new Vector2(0f, 1f); rectTransform2.anchorMax = new Vector2(1f, 1f); rectTransform2.pivot = new Vector2(0.5f, 1f); rectTransform2.anchoredPosition = Vector2.zero; rectTransform2.sizeDelta = new Vector2(0f, 5f); GameObject val5 = new GameObject("TitleLine1"); val5.transform.SetParent(_panelGo.transform, false); _titleLine1 = val5.AddComponent(); ((TMP_Text)_titleLine1).text = "GB-EMU MENU"; ((TMP_Text)_titleLine1).fontSize = 30f; ((TMP_Text)_titleLine1).fontStyle = (FontStyles)1; ((Graphic)_titleLine1).color = new Color(0.35686275f, 0.56078434f, 0.8745098f, 1f); ((TMP_Text)_titleLine1).alignment = (TextAlignmentOptions)514; RectTransform rectTransform3 = ((TMP_Text)_titleLine1).rectTransform; rectTransform3.anchorMin = new Vector2(0f, 1f); rectTransform3.anchorMax = new Vector2(1f, 1f); rectTransform3.pivot = new Vector2(0.5f, 1f); rectTransform3.anchoredPosition = new Vector2(0f, -18f); rectTransform3.sizeDelta = new Vector2(0f, 40f); GameObject val6 = new GameObject("TitleLine2"); val6.transform.SetParent(_panelGo.transform, false); _titleLine2 = val6.AddComponent(); ((TMP_Text)_titleLine2).text = "LINK WITH ANOTHER PLAYER"; ((TMP_Text)_titleLine2).fontSize = 22f; ((Graphic)_titleLine2).color = new Color(0.75f, 0.75f, 0.75f, 1f); ((TMP_Text)_titleLine2).alignment = (TextAlignmentOptions)514; RectTransform rectTransform4 = ((TMP_Text)_titleLine2).rectTransform; rectTransform4.anchorMin = new Vector2(0f, 1f); rectTransform4.anchorMax = new Vector2(1f, 1f); rectTransform4.pivot = new Vector2(0.5f, 1f); rectTransform4.anchoredPosition = new Vector2(0f, -60f); rectTransform4.sizeDelta = new Vector2(0f, 30f); GameObject val7 = new GameObject("Sep"); val7.transform.SetParent(_panelGo.transform, false); Image val8 = val7.AddComponent(); ((Graphic)val8).color = new Color(1f, 1f, 1f, 0.12f); RectTransform rectTransform5 = ((Graphic)val8).rectTransform; rectTransform5.anchorMin = new Vector2(0f, 1f); rectTransform5.anchorMax = new Vector2(1f, 1f); rectTransform5.pivot = new Vector2(0.5f, 1f); rectTransform5.anchoredPosition = new Vector2(0f, -100f); rectTransform5.sizeDelta = new Vector2(-24f, 1f); GameObject val9 = new GameObject("Viewport"); val9.transform.SetParent(_panelGo.transform, false); val9.AddComponent(); _listViewport = val9.GetComponent(); _listViewport.anchorMin = new Vector2(0f, 1f); _listViewport.anchorMax = new Vector2(1f, 1f); _listViewport.pivot = new Vector2(0.5f, 1f); _listViewport.anchoredPosition = new Vector2(0f, -110f); _listViewport.sizeDelta = new Vector2(-24f, 434f); GameObject val10 = new GameObject("Content"); val10.transform.SetParent(val9.transform, false); _listContent = val10.AddComponent(); _listContent.anchorMin = new Vector2(0f, 1f); _listContent.anchorMax = new Vector2(1f, 1f); _listContent.pivot = new Vector2(0.5f, 1f); _listContent.anchoredPosition = Vector2.zero; _listContent.sizeDelta = Vector2.zero; GameObject val11 = new GameObject("Hint"); val11.transform.SetParent(_panelGo.transform, false); _hintText = val11.AddComponent(); ((TMP_Text)_hintText).text = "Up/Down: Nav A/Enter: Link B/Left: Quit"; ((TMP_Text)_hintText).fontSize = 19f; ((Graphic)_hintText).color = new Color(0.6f, 0.6f, 0.6f, 1f); ((TMP_Text)_hintText).alignment = (TextAlignmentOptions)514; RectTransform rectTransform6 = ((TMP_Text)_hintText).rectTransform; rectTransform6.anchorMin = new Vector2(0f, 0f); rectTransform6.anchorMax = new Vector2(1f, 0f); rectTransform6.pivot = new Vector2(0.5f, 0f); rectTransform6.anchoredPosition = new Vector2(0f, 18f); rectTransform6.sizeDelta = new Vector2(0f, 44f); ((Component)this).gameObject.SetActive(false); } private void Refresh() { //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Expected O, but got Unknown //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) foreach (GameObject row in _rows) { if ((Object)(object)row != (Object)null) { Object.Destroy((Object)(object)row); } } _rows.Clear(); _players = GetAvailablePlayers(); _selectedIndex = 0; _windowStart = 0; _repeatTimer = 0f; _upHeld = (_downHeld = false); int num = Mathf.Min((_players.Count == 0) ? 1 : _players.Count, 7); float num2 = (float)num * 62f; if (_players.Count == 0) { GameObject val = new GameObject("Empty"); val.transform.SetParent((Transform)(object)_listContent, false); TextMeshProUGUI val2 = val.AddComponent(); ((TMP_Text)val2).text = "No players nearby."; ((TMP_Text)val2).fontSize = 24f; ((Graphic)val2).color = new Color(0.55f, 0.55f, 0.55f, 1f); ((TMP_Text)val2).alignment = (TextAlignmentOptions)514; RectTransform rectTransform = ((TMP_Text)val2).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(1f, 1f); rectTransform.pivot = new Vector2(0.5f, 1f); rectTransform.anchoredPosition = new Vector2(0f, -12f); rectTransform.sizeDelta = new Vector2(0f, 56f); _rows.Add(val); _listContent.sizeDelta = new Vector2(0f, 80f); } else { float num3 = 62f; for (int i = 0; i < num; i++) { _rows.Add(BuildRow(i, (float)i * num3)); } _listContent.sizeDelta = new Vector2(0f, num2); UpdateVisibleRows(); } _listContent.anchoredPosition = Vector2.zero; _listViewport.sizeDelta = new Vector2(-24f, num2); float num4 = 110f + num2 + 14f + 44f + 18f; _panelGo.GetComponent().sizeDelta = new Vector2(686f, num4); ScrollToSelected(); UpdateHighlights(); } private GameObject BuildRow(int slot, float yOffset) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Row_" + slot); val.transform.SetParent((Transform)(object)_listContent, false); Image val2 = val.AddComponent(); ((Graphic)val2).color = Color.clear; RectTransform rectTransform = ((Graphic)val2).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(1f, 1f); rectTransform.pivot = new Vector2(0.5f, 1f); rectTransform.anchoredPosition = new Vector2(0f, 0f - yOffset); rectTransform.sizeDelta = new Vector2(0f, 56f); GameObject val3 = new GameObject("Label"); val3.transform.SetParent(val.transform, false); TextMeshProUGUI val4 = val3.AddComponent(); ((TMP_Text)val4).text = string.Empty; ((TMP_Text)val4).fontSize = 21f; ((Graphic)val4).color = Color.white; ((TMP_Text)val4).alignment = (TextAlignmentOptions)4097; RectTransform rectTransform2 = ((TMP_Text)val4).rectTransform; rectTransform2.anchorMin = Vector2.zero; rectTransform2.anchorMax = Vector2.one; rectTransform2.offsetMin = new Vector2(18f, 0f); rectTransform2.offsetMax = new Vector2(-8f, 0f); return val; } private void UpdateVisibleRows() { for (int i = 0; i < _rows.Count; i++) { GameObject val = _rows[i]; if ((Object)(object)val == (Object)null) { continue; } int num = _windowStart + i; bool flag = num >= 0 && num < _players.Count; val.SetActive(flag); if (flag) { Transform obj = val.transform.Find("Label"); TextMeshProUGUI val2 = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)val2 != (Object)null) { ((TMP_Text)val2).text = _players[num].Value; } } } } private void Update() { if (((Component)this).gameObject.activeSelf) { if (!_fontApplied) { ApplyGameFont(); } if (_inputGrace) { _inputGrace = false; } else if (PopupState.ShouldSuppressMenuInputForChat()) { _upHeld = false; _downHeld = false; _repeatTimer = 0f; _confirmWasDown = true; _cancelWasDown = true; } else { HandleNavigation(); HandleConfirmCancel(); } } } private void HandleNavigation() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) if (_players.Count == 0) { return; } CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; bool flag = Input.GetKey(configSettings.Up.Value) || Input.GetKey((KeyCode)273) || Input.GetAxisRaw("Vertical") > 0.5f; bool flag2 = Input.GetKey(configSettings.Down.Value) || Input.GetKey((KeyCode)274) || Input.GetAxisRaw("Vertical") < -0.5f; if (PhoneMenuInputRouter.ConsumedUpThisFrame() || PhoneMenuInputRouter.ConsumedDownThisFrame()) { _upHeld = flag; _downHeld = flag2; _repeatTimer = 0f; return; } if (flag && !_upHeld) { _upHeld = true; _repeatTimer = 0f; Move(-1); } else if (!flag) { _upHeld = false; } if (flag2 && !_downHeld) { _downHeld = true; _repeatTimer = 0f; Move(1); } else if (!flag2) { _downHeld = false; } if (!_upHeld && !_downHeld) { return; } _repeatTimer += Time.unscaledDeltaTime; if (_repeatTimer >= 0.34f) { _repeatTimer -= 0.075f; if (_upHeld) { Move(-1); } if (_downHeld) { Move(1); } } } private void Move(int dir) { if (_players.Count != 0) { int selectedIndex = _selectedIndex; _selectedIndex = (_selectedIndex + dir + _players.Count) % _players.Count; bool wrapped = (dir < 0 && _selectedIndex > selectedIndex) || (dir > 0 && _selectedIndex < selectedIndex); ScrollToSelected(wrapped); UpdateHighlights(); PlaySelectSfx(); } } private void HandleConfirmCancel() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) CodeDmgConfig configSettings = CodeDmgPlugin.ConfigSettings; bool flag = Input.GetKey(configSettings.A.Value) || Input.GetKey(configSettings.Right.Value) || Input.GetKey((KeyCode)13) || Input.GetKey((KeyCode)271) || Input.GetKey((KeyCode)275) || Input.GetKey((KeyCode)330) || Input.GetAxisRaw("Horizontal") > 0.5f; bool flag2 = flag && !_confirmWasDown; _confirmWasDown = flag; bool flag3 = Input.GetKey(configSettings.B.Value) || Input.GetKey(configSettings.Left.Value) || Input.GetKey((KeyCode)27) || Input.GetKey((KeyCode)276) || Input.GetKey((KeyCode)331) || Input.GetAxisRaw("Horizontal") < -0.5f; bool flag4 = flag3 && !_cancelWasDown; _cancelWasDown = flag3; if (flag2) { ConfirmCurrent(); } else if (flag4) { BackOut(); } } private void ConfirmCurrent() { if (_players.Count != 0) { KeyValuePair keyValuePair = _players[_selectedIndex]; PlayConfirmSfx(); PopupState.SuppressPhoneNavFor(); Close(); _onPick?.Invoke(keyValuePair.Key, keyValuePair.Value); } } private void BackOut() { PlayBackSfx(); PopupState.SuppressPhoneNavFor(); Close(); } private static void PlaySelectSfx() { try { AppCodeDmg.PlayMenuSelectSFX(); } catch { } } private static void PlayConfirmSfx() { try { AppCodeDmg.PlayMenuConfirmSFX(); } catch { } } private static void PlayBackSfx() { try { AppCodeDmg.PlayMenuBackSFX(); } catch { } } private void UpdateHighlights() { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) if (_players.Count == 0) { return; } UpdateVisibleRows(); for (int i = 0; i < _rows.Count; i++) { if (!((Object)(object)_rows[i] == (Object)null)) { int num = _windowStart + i; bool flag = num == _selectedIndex; Image component = _rows[i].GetComponent(); Transform obj = _rows[i].transform.Find("Label"); TextMeshProUGUI val = ((obj != null) ? ((Component)obj).GetComponent() : null); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = (Color)(flag ? new Color(0.21960784f, 0.3019608f, 0.61960787f, 0.35f) : Color.clear); } if ((Object)(object)val != (Object)null) { ((Graphic)val).color = (Color)(flag ? Color.white : new Color(0.65f, 0.65f, 0.65f, 1f)); } } } } private void ScrollToSelected(bool wrapped = false) { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) if (_players.Count != 0) { int num = Mathf.Min(_players.Count, 7); if (_players.Count <= num) { _windowStart = 0; } else if (_selectedIndex < _windowStart || (wrapped && _selectedIndex == 0)) { _windowStart = _selectedIndex; } else if (_selectedIndex >= _windowStart + num || wrapped) { _windowStart = _selectedIndex - num + 1; } _windowStart = Mathf.Clamp(_windowStart, 0, Mathf.Max(0, _players.Count - num)); _listContent.anchoredPosition = Vector2.zero; UpdateVisibleRows(); } } private void ApplyGameFont() { if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array = Resources.FindObjectsOfTypeAll(); TextMeshProUGUI[] array2 = array; foreach (TextMeshProUGUI val in array2) { if ((Object)(object)val != (Object)null && (Object)(object)((TMP_Text)val).font != (Object)null && ((Object)val).name == "HeaderLabel") { _gameFont = ((TMP_Text)val).font; break; } } if ((Object)(object)_gameFont == (Object)null) { TextMeshProUGUI[] array3 = array; foreach (TextMeshProUGUI val2 in array3) { if ((Object)(object)val2 != (Object)null && (Object)(object)((TMP_Text)val2).font != (Object)null) { _gameFont = ((TMP_Text)val2).font; break; } } } } if ((Object)(object)_gameFont == (Object)null) { return; } TMP_Text[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); foreach (TMP_Text val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null && (Object)(object)val3.font != (Object)(object)_gameFont) { val3.font = _gameFont; } } _fontApplied = true; } private static List> GetAvailablePlayers() { List> list = new List>(); try { ClientController instance = ClientController.Instance; if ((Object)(object)instance == (Object)null || instance.Players == null) { return list; } ushort localID = instance.LocalID; foreach (KeyValuePair player in instance.Players) { if (player.Key == localID) { continue; } MPPlayer value = player.Value; if (value != null) { string empty = string.Empty; try { empty = MPUtility.GetPlayerDisplayName(value.ClientState); } catch { empty = string.Empty; } string value2 = LinkCableManager.SanitizeName(empty); if (!string.IsNullOrWhiteSpace(value2)) { list.Add(new KeyValuePair(player.Key, value2)); } } } } catch { } return list; } } public sealed class Mbc3Rtc { private byte rtcS; private byte rtcM; private byte rtcH; private byte rtcDL; private byte rtcDH; private byte latchedS; private byte latchedM; private byte latchedH; private byte latchedDL; private byte latchedDH; private bool isLatched; private byte selectedRegister = byte.MaxValue; private byte lastLatchWrite; private long lastHostUnixTime; public Mbc3Rtc() { SeedFromHostClock(); } public void SeedFromHostClock() { DateTimeOffset now = DateTimeOffset.Now; DateTimeOffset utcNow = DateTimeOffset.UtcNow; rtcS = (byte)now.Second; rtcM = (byte)now.Minute; rtcH = (byte)now.Hour; int num = (int)((utcNow.ToUnixTimeSeconds() / 86400) & 0x1FF); rtcDL = (byte)(num & 0xFF); rtcDH = (byte)((num >> 8) & 1); latchedS = rtcS; latchedM = rtcM; latchedH = rtcH; latchedDL = rtcDL; latchedDH = rtcDH; isLatched = false; lastLatchWrite = 0; lastHostUnixTime = GetHostUnixTime(); } public void SelectRegister(byte value) { selectedRegister = value; } public void Latch(byte value) { if (lastLatchWrite == 0 && value == 1) { SyncToHostClock(); latchedS = rtcS; latchedM = rtcM; latchedH = rtcH; latchedDL = rtcDL; latchedDH = rtcDH; isLatched = true; } lastLatchWrite = value; } public byte ReadSelected() { SyncToHostClock(); return selectedRegister switch { 8 => isLatched ? latchedS : rtcS, 9 => isLatched ? latchedM : rtcM, 10 => isLatched ? latchedH : rtcH, 11 => isLatched ? latchedDL : rtcDL, 12 => isLatched ? latchedDH : rtcDH, _ => byte.MaxValue, }; } public void WriteSelected(byte value) { SyncToHostClock(); switch (selectedRegister) { case 8: rtcS = (byte)(value % 60); break; case 9: rtcM = (byte)(value % 60); break; case 10: rtcH = (byte)(value % 24); break; case 11: rtcDL = value; break; case 12: { bool flag = IsHalted(); rtcDH = (byte)(value & 0xC1); bool flag2 = IsHalted(); if (flag && !flag2) { lastHostUnixTime = GetHostUnixTime(); } break; } } } public void SyncToHostClock() { long hostUnixTime = GetHostUnixTime(); if (lastHostUnixTime == 0) { lastHostUnixTime = hostUnixTime; return; } if (IsHalted()) { lastHostUnixTime = hostUnixTime; return; } long num = hostUnixTime - lastHostUnixTime; if (num > 0) { AddSeconds(num); lastHostUnixTime = hostUnixTime; } } private void AddSeconds(long deltaSeconds) { long num = GetDayCounter(); long num2 = rtcS + (long)rtcM * 60L + (long)rtcH * 3600L + num * 86400; num2 += deltaSeconds; long num3 = num2 / 86400; long num4 = num2 % 86400; if (num3 > 511) { rtcDH |= 128; num3 &= 0x1FF; } rtcH = (byte)(num4 / 3600); num4 %= 3600; rtcM = (byte)(num4 / 60); rtcS = (byte)(num4 % 60); SetDayCounter((int)num3); } private int GetDayCounter() { return rtcDL | ((rtcDH & 1) << 8); } private void SetDayCounter(int days) { days &= 0x1FF; rtcDL = (byte)(days & 0xFF); rtcDH = (byte)((rtcDH & 0xC0) | ((days >> 8) & 1)); } private bool IsHalted() { return (rtcDH & 0x40) != 0; } private static long GetHostUnixTime() { return DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } public void Save(BinaryWriter writer) { SyncToHostClock(); writer.Write(rtcS); writer.Write(rtcM); writer.Write(rtcH); writer.Write(rtcDL); writer.Write(rtcDH); writer.Write(latchedS); writer.Write(latchedM); writer.Write(latchedH); writer.Write(latchedDL); writer.Write(latchedDH); writer.Write(isLatched); writer.Write(selectedRegister); writer.Write(lastLatchWrite); writer.Write(lastHostUnixTime); } public void Load(BinaryReader reader) { rtcS = reader.ReadByte(); rtcM = reader.ReadByte(); rtcH = reader.ReadByte(); rtcDL = reader.ReadByte(); rtcDH = reader.ReadByte(); latchedS = reader.ReadByte(); latchedM = reader.ReadByte(); latchedH = reader.ReadByte(); latchedDL = reader.ReadByte(); latchedDH = reader.ReadByte(); isLatched = reader.ReadBoolean(); selectedRegister = reader.ReadByte(); lastLatchWrite = reader.ReadByte(); lastHostUnixTime = reader.ReadInt64(); SyncToHostClock(); } } public static class GBEmuCurrentState { public static bool AppActive; } [HarmonyPatch(typeof(Player), "SetInputs")] public static class Player_SetInputs_GBEmuPatch { private static bool Prefix(Player __instance) { if (!GBEmuCurrentState.AppActive) { return true; } __instance.FlushInput(); return false; } } internal static class PluginInfo { public const string PLUGIN_GUID = "transrights.codedmg"; public const string PLUGIN_NAME = "CodeDMG"; public const string PLUGIN_VERSION = "1.6.1"; } public class PPU { private const int HBLANK = 0; private const int VBLANK = 1; private const int OAM = 2; private const int VRAM_MODE = 3; private const int SCANLINE_CYCLES = 456; private const int OAM_CYCLES = 80; private const int VRAM_CYCLES = 172; private const int HBLANK_CYCLES = 204; private const int ScreenWidth = 160; private const int ScreenHeight = 144; private readonly MMU mmu; private int mode; private int cycles; private bool vblankTriggered; private bool lcdPreviouslyOff; private bool lcdBlankFramePending; private bool lcdOffFramePushed; private bool windowWyTriggeredThisFrame; private int windowLineCounter; private readonly Color32[] frameBuffer; private readonly Color32[] scanlineBuffer = (Color32[])(object)new Color32[160]; private readonly int[] bgColorIndexBuffer = new int[160]; private readonly bool[] bgAttrPriorityBuffer = new bool[160]; internal string PaletteOverride; public bool FrameDirty { get; private set; } public void SaveStateNoBuffers(BinaryWriter writer) { writer.Write(mode); writer.Write(cycles); writer.Write(vblankTriggered); writer.Write(windowLineCounter); writer.Write(windowWyTriggeredThisFrame); writer.Write(lcdPreviouslyOff); writer.Write(lcdBlankFramePending); writer.Write(lcdOffFramePushed); } public void LoadStateNoBuffers(BinaryReader reader) { mode = reader.ReadInt32(); cycles = reader.ReadInt32(); vblankTriggered = reader.ReadBoolean(); windowLineCounter = reader.ReadInt32(); windowWyTriggeredThisFrame = reader.ReadBoolean(); lcdPreviouslyOff = reader.ReadBoolean(); lcdBlankFramePending = reader.ReadBoolean(); lcdOffFramePushed = reader.ReadBoolean(); } public PPU(MMU mmu) { this.mmu = mmu; mode = 2; cycles = 0; frameBuffer = (Color32[])(object)new Color32[23040]; ClearToPalette0(); } public Color32[] GetUnityFrame() { return frameBuffer; } public void ClearDirtyFlag() { FrameDirty = false; } public void Step(int elapsedCycles) { cycles += elapsedCycles; bool flag = (mmu.LCDC & 0x80) != 0; if (flag && lcdPreviouslyOff) { lcdPreviouslyOff = false; cycles = 0; mode = 2; mmu.LY = 0; windowWyTriggeredThisFrame = false; windowLineCounter = 0; vblankTriggered = false; lcdBlankFramePending = true; lcdOffFramePushed = false; mmu.STAT = (byte)((mmu.STAT & 0xFC) | mode); SetLYCFlag(); } else if (!flag) { if (!lcdPreviouslyOff) { lcdPreviouslyOff = true; mode = 0; cycles = 0; mmu.LY = 0; windowWyTriggeredThisFrame = false; windowLineCounter = 0; vblankTriggered = false; FrameDirty = true; lcdOffFramePushed = true; } return; } switch (mode) { case 2: if (cycles >= 80) { cycles -= 80; if (mmu.LY == mmu.WY && mmu.WY <= 143) { windowWyTriggeredThisFrame = true; } mode = 3; mmu.STAT = (byte)((mmu.STAT & 0xFC) | mode); } break; case 3: if (cycles < 172) { break; } cycles -= 172; if (mmu.LY < 144) { if (lcdBlankFramePending) { RenderBlankScanline(mmu.LY); } else { RenderScanline(); } } mode = 0; mmu.STAT = (byte)((mmu.STAT & 0xFC) | mode); mmu.ExecuteHBlankDMA(); if ((mmu.STAT & 8) != 0) { mmu.IF |= 2; } break; case 0: if (cycles < 204) { break; } cycles -= 204; mmu.LY++; SetLYCFlag(); if (mmu.LY == 144) { mode = 1; mmu.STAT = (byte)((mmu.STAT & 0xFC) | mode); FrameDirty = true; vblankTriggered = false; lcdBlankFramePending = false; if ((mmu.STAT & 0x10) != 0) { mmu.IF |= 2; } } else { mode = 2; mmu.STAT = (byte)((mmu.STAT & 0xFC) | mode); if ((mmu.STAT & 0x20) != 0) { mmu.IF |= 2; } } break; case 1: if (!vblankTriggered && mmu.LY == 144) { mmu.IF |= 1; vblankTriggered = true; } if (cycles < 456) { break; } cycles -= 456; mmu.LY++; SetLYCFlag(); if (mmu.LY > 153) { mmu.LY = 0; SetLYCFlag(); mode = 2; mmu.STAT = (byte)((mmu.STAT & 0xFC) | mode); vblankTriggered = false; windowWyTriggeredThisFrame = false; windowLineCounter = 0; if ((mmu.STAT & 0x20) != 0) { mmu.IF |= 2; } } break; } } private void RenderScanline() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) bool isCGBMode = mmu.IsCGBMode; int lY = mmu.LY; for (int i = 0; i < 160; i++) { bgColorIndexBuffer[i] = 0; bgAttrPriorityBuffer[i] = false; } if (!isCGBMode && (mmu.LCDC & 1) == 0) { Color32 val = ConvertDMGColor(0); for (int j = 0; j < 160; j++) { scanlineBuffer[j] = val; } } else { RenderBackgroundAndWindowScanline(lY, isCGBMode); } RenderSprites(); Array.Copy(scanlineBuffer, 0, frameBuffer, lY * 160, 160); } private void RenderBackgroundAndWindowScanline(int ly, bool cgb) { int num = mmu.SCX & 7; bool flag = (mmu.LCDC & 0x20) != 0; bool flag2 = mmu.WX <= 166 && mmu.WY <= 143; bool flag3 = flag && flag2 && windowWyTriggeredThisFrame; int num2 = mmu.WX - 7; bool flag4 = false; int currentWindowLine = windowLineCounter; for (int i = 0; i < 160; i++) { if (flag3 && i >= num2) { flag4 = true; RenderWindowPixel(i, currentWindowLine, cgb); } else { int effectiveScx = (mmu.SCX & 0xF8) | num; RenderBackgroundPixel(i, ly, effectiveScx, cgb); } } if (flag4) { windowLineCounter++; } } private void RenderBackgroundPixel(int screenX, int ly, int effectiveScx, bool cgb) { //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) int num = (effectiveScx + screenX) & 0xFF; int num2 = (mmu.SCY + ly) & 0xFF; ushort num3 = (ushort)(((mmu.LCDC & 8) != 0) ? 39936 : 38912); int num4 = num >> 3; int num5 = num2 >> 3; int num6 = num5 * 32 + num4; int relAddr = num3 - 32768 + num6; byte tileNumber = mmu.ReadVramDirect(relAddr, 0); byte b = (byte)(cgb ? mmu.ReadVramDirect(relAddr, 1) : 0); int paletteIndex = b & 7; int num7 = (b >> 3) & 1; bool flag = (b & 0x20) != 0; bool flag2 = (b & 0x40) != 0; bool flag3 = (b & 0x80) != 0; int num8 = num2 & 7; if (flag2) { num8 = 7 - num8; } int bgWindowTileDataRelBase = GetBgWindowTileDataRelBase(tileNumber, (mmu.LCDC & 0x10) != 0); int bank = (cgb ? num7 : 0); byte b2 = mmu.ReadVramDirect(bgWindowTileDataRelBase + num8 * 2, bank); byte b3 = mmu.ReadVramDirect(bgWindowTileDataRelBase + num8 * 2 + 1, bank); int num9 = num & 7; int num10 = (flag ? num9 : (7 - num9)); int num11 = (((b3 >> num10) & 1) << 1) | ((b2 >> num10) & 1); bgColorIndexBuffer[screenX] = num11; bgAttrPriorityBuffer[screenX] = cgb && flag3 && num11 != 0; if (cgb) { scanlineBuffer[screenX] = mmu.GetCGBBgColor(paletteIndex, num11); } else { scanlineBuffer[screenX] = ConvertDMGColor((mmu.BGP >> num11 * 2) & 3); } } private void RenderWindowPixel(int screenX, int currentWindowLine, bool cgb) { //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) int num = mmu.WX - 7; int num2 = screenX - num; if (num2 >= 0) { ushort num3 = (ushort)(((mmu.LCDC & 0x40) != 0) ? 39936 : 38912); int num4 = num2 >> 3; int num5 = currentWindowLine >> 3; int num6 = num5 * 32 + num4; int relAddr = num3 - 32768 + num6; byte tileNumber = mmu.ReadVramDirect(relAddr, 0); byte b = (byte)(cgb ? mmu.ReadVramDirect(relAddr, 1) : 0); int paletteIndex = b & 7; int num7 = (b >> 3) & 1; bool flag = (b & 0x20) != 0; bool flag2 = (b & 0x40) != 0; bool flag3 = (b & 0x80) != 0; int num8 = currentWindowLine & 7; if (flag2) { num8 = 7 - num8; } int bgWindowTileDataRelBase = GetBgWindowTileDataRelBase(tileNumber, (mmu.LCDC & 0x10) != 0); int bank = (cgb ? num7 : 0); byte b2 = mmu.ReadVramDirect(bgWindowTileDataRelBase + num8 * 2, bank); byte b3 = mmu.ReadVramDirect(bgWindowTileDataRelBase + num8 * 2 + 1, bank); int num9 = num2 & 7; int num10 = (flag ? num9 : (7 - num9)); int num11 = (((b3 >> num10) & 1) << 1) | ((b2 >> num10) & 1); bgColorIndexBuffer[screenX] = num11; bgAttrPriorityBuffer[screenX] = cgb && flag3 && num11 != 0; if (cgb) { scanlineBuffer[screenX] = mmu.GetCGBBgColor(paletteIndex, num11); } else { scanlineBuffer[screenX] = ConvertDMGColor((mmu.BGP >> num11 * 2) & 3); } } } private int GetBgWindowTileDataRelBase(byte tileNumber, bool use8000Method) { if (use8000Method) { return tileNumber * 16; } short num = (sbyte)tileNumber; int num2 = 36864 + num * 16; return num2 - 32768; } private void RenderSprites() { //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_0332: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Unknown result type (might be due to invalid IL or missing references) //IL_033e: Unknown result type (might be due to invalid IL or missing references) if ((mmu.LCDC & 2) == 0) { return; } int lY = mmu.LY; bool isCGBMode = mmu.IsCGBMode; bool flag = (mmu.LCDC & 1) != 0; int num = 0; int[] array = new int[160]; for (int i = 0; i < 160; i++) { array[i] = -1; } int num2 = (((mmu.LCDC & 4) != 0) ? 16 : 8); for (int j = 0; j < 40; j++) { if (num >= 10) { break; } int num3 = 65024 + j * 4; int num4 = mmu.Read((ushort)num3) - 16; int num5 = mmu.Read((ushort)(num3 + 1)) - 8; byte b = mmu.Read((ushort)(num3 + 2)); byte b2 = mmu.Read((ushort)(num3 + 3)); if (lY < num4 || lY >= num4 + num2) { continue; } int num6 = lY - num4; if ((b2 & 0x40) != 0) { num6 = num2 - 1 - num6; } if (num2 == 16) { b &= 0xFE; if (num6 >= 8) { b++; num6 -= 8; } } int bank = ((isCGBMode && (b2 & 8) != 0) ? 1 : 0); int num7 = b * 16 + num6 * 2; byte b3 = mmu.ReadVramDirect(num7, bank); byte b4 = mmu.ReadVramDirect(num7 + 1, bank); bool flag2 = (b2 & 0x20) != 0; int paletteIndex = b2 & 7; byte b5 = (((b2 & 0x10) != 0) ? mmu.OBP1 : mmu.OBP0); for (int k = 0; k < 8; k++) { int num8 = (flag2 ? k : (7 - k)); int num9 = (((b4 >> num8) & 1) << 1) | ((b3 >> num8) & 1); if (num9 == 0) { continue; } int num10 = num5 + k; if (num10 < 0 || num10 >= 160 || !((!isCGBMode) ? (array[num10] == -1 || num5 < array[num10]) : (array[num10] == -1))) { continue; } if (isCGBMode) { if (flag) { bool flag3 = (b2 & 0x80) != 0; if ((bgAttrPriorityBuffer[num10] || flag3) && bgColorIndexBuffer[num10] != 0) { continue; } } } else if ((b2 & 0x80) != 0 && bgColorIndexBuffer[num10] != 0) { continue; } array[num10] = (isCGBMode ? j : num5); Color32 val = ((!isCGBMode) ? ConvertDMGColor((b5 >> num9 * 2) & 3) : mmu.GetCGBObjColor(paletteIndex, num9)); scanlineBuffer[num10] = val; } num++; } } private Color32 ConvertDMGColor(int paletteColor) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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) string key = PaletteOverride ?? Helper.paletteName; return Helper.palettes[key][paletteColor]; } private void SetLYCFlag() { if (mmu.LY == mmu.LYC) { mmu.STAT |= 4; if ((mmu.STAT & 0x40) != 0) { mmu.IF |= 2; } } else { mmu.STAT = (byte)(mmu.STAT & -5); } } private void ClearToPalette0() { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) Color32 val = ConvertDMGColor(0); for (int i = 0; i < frameBuffer.Length; i++) { frameBuffer[i] = val; } } private Color32 GetLcdOffBlankColor() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) return (Color32)(mmu.IsCGBMode ? new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue) : ConvertDMGColor(0)); } private void ClearToLcdOffBlank() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) Color32 lcdOffBlankColor = GetLcdOffBlankColor(); for (int i = 0; i < frameBuffer.Length; i++) { frameBuffer[i] = lcdOffBlankColor; } } private void RenderBlankScanline(int ly) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) Color32 lcdOffBlankColor = GetLcdOffBlankColor(); for (int i = 0; i < 160; i++) { scanlineBuffer[i] = lcdOffBlankColor; bgColorIndexBuffer[i] = 0; bgAttrPriorityBuffer[i] = false; } Array.Copy(scanlineBuffer, 0, frameBuffer, ly * 160, 160); } private static void WriteColor32Array(BinaryWriter writer, Color32[] data) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (data == null) { writer.Write(-1); return; } writer.Write(data.Length); foreach (Color32 val in data) { writer.Write(val.r); writer.Write(val.g); writer.Write(val.b); writer.Write(val.a); } } private static Color32[] ReadColor32Array(BinaryReader reader) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) int num = reader.ReadInt32(); if (num < 0) { return null; } Color32[] array = (Color32[])(object)new Color32[num]; for (int i = 0; i < num; i++) { byte b = reader.ReadByte(); byte b2 = reader.ReadByte(); byte b3 = reader.ReadByte(); byte b4 = reader.ReadByte(); array[i] = new Color32(b, b2, b3, b4); } return array; } public void SaveState(BinaryWriter writer) { writer.Write(mode); writer.Write(cycles); writer.Write(vblankTriggered); writer.Write(windowLineCounter); writer.Write(windowWyTriggeredThisFrame); writer.Write(lcdPreviouslyOff); writer.Write(lcdBlankFramePending); writer.Write(lcdOffFramePushed); WriteColor32Array(writer, frameBuffer); WriteColor32Array(writer, scanlineBuffer); } public void LoadState(BinaryReader reader) { mode = reader.ReadInt32(); cycles = reader.ReadInt32(); vblankTriggered = reader.ReadBoolean(); windowLineCounter = reader.ReadInt32(); windowWyTriggeredThisFrame = reader.ReadBoolean(); lcdPreviouslyOff = reader.ReadBoolean(); lcdBlankFramePending = reader.ReadBoolean(); lcdOffFramePushed = reader.ReadBoolean(); Color32[] array = ReadColor32Array(reader); Color32[] array2 = ReadColor32Array(reader); if (array == null || array.Length != frameBuffer.Length) { throw new InvalidOperationException("Invalid frameBuffer in savestate."); } if (array2 == null || array2.Length != scanlineBuffer.Length) { throw new InvalidOperationException("Invalid scanlineBuffer in savestate."); } Array.Copy(array, frameBuffer, frameBuffer.Length); Array.Copy(array2, scanlineBuffer, scanlineBuffer.Length); FrameDirty = true; } } } namespace BRCCodeDmg.Transport { public sealed class LinkCableTransport : IDisposable { private bool _disposed; private readonly uint[] _joypadSentFrames = new uint[3]; private readonly byte[] _joypadSentStates = new byte[3] { 255, 255, 255 }; private int _joypadSentIndex; public ushort LocalPlayerId => (ushort)(((Object)(object)ClientController.Instance != (Object)null) ? ClientController.Instance.LocalID : 0); public bool Connected => (Object)(object)ClientController.Instance != (Object)null && ClientController.Instance.Connected; public event Action InviteReceived; public event Action AcceptReceived; public event Action DeclineReceived; public event Action DisconnectReceived; public event Action RomHashReceived; public event Action SaveStateReceived; public event Action LinkSettingsReceived; public event Action JoypadReceived; public LinkCableTransport() { ClientController.RegisterCustomPacketHandler("codedmg.linkcable_invite", (Action)OnRawInvite); ClientController.RegisterCustomPacketHandler("codedmg.linkcable_accept", (Action)OnRawAccept); ClientController.RegisterCustomPacketHandler("codedmg.linkcable_decline", (Action)OnRawDecline); ClientController.RegisterCustomPacketHandler("codedmg.linkcable_disconnect", (Action)OnRawDisconnect); ClientController.RegisterCustomPacketHandler("codedmg.linkcable_romhash", (Action)OnRawRomHash); ClientController.RegisterCustomPacketHandler("codedmg.linkcable_savestate", (Action)OnRawSaveState); ClientController.RegisterCustomPacketHandler("codedmg.linkcable_settings", (Action)OnRawLinkSettings); ClientController.RegisterCustomPacketHandler("codedmg.linkcable_joypad", (Action)OnRawJoypad); } public void Dispose() { _disposed = true; ClientController.UnregisterCustomPacketHandler("codedmg.linkcable_invite"); ClientController.UnregisterCustomPacketHandler("codedmg.linkcable_accept"); ClientController.UnregisterCustomPacketHandler("codedmg.linkcable_decline"); ClientController.UnregisterCustomPacketHandler("codedmg.linkcable_disconnect"); ClientController.UnregisterCustomPacketHandler("codedmg.linkcable_romhash"); ClientController.UnregisterCustomPacketHandler("codedmg.linkcable_savestate"); ClientController.UnregisterCustomPacketHandler("codedmg.linkcable_settings"); ClientController.UnregisterCustomPacketHandler("codedmg.linkcable_joypad"); } public void SendInvite(ushort t, string name) { Send(Str(name), "codedmg.linkcable_invite", t, (SendModes)2); } public void SendAccept(ushort t, string name) { Send(Str(name), "codedmg.linkcable_accept", t, (SendModes)2); } public void SendDecline(ushort t) { Send(new byte[0], "codedmg.linkcable_decline", t, (SendModes)2); } public void SendDisconnect(ushort t, byte reason = 0) { Send(new byte[1] { reason }, "codedmg.linkcable_disconnect", t, (SendModes)2); } public void SendRomHash(ushort t, string hash) { Send(Str(hash), "codedmg.linkcable_romhash", t, (SendModes)2); } public void SendSaveState(ushort t, byte[] data) { if (_disposed || !Connected || data == null) { return; } Debug.Log((object)$"[CodeDMG] SaveState SEND to {t}: {data.Length} bytes"); try { ClientController.Instance.SendCustomPacketToPlayer(data, "codedmg.linkcable_savestate", t, (SendModes)1); } catch (Exception ex) { Debug.LogError((object)("[CodeDMG] SaveState send error: " + ex.Message)); } } public void SendLinkSettings(ushort t, int inputDelayFrames) { if (_disposed || !Connected) { return; } byte[] array = new byte[4] { (byte)(inputDelayFrames & 0xFF), (byte)((inputDelayFrames >> 8) & 0xFF), (byte)((inputDelayFrames >> 16) & 0xFF), (byte)((inputDelayFrames >> 24) & 0xFF) }; try { ClientController.Instance.SendCustomPacketToPlayer(array, "codedmg.linkcable_settings", t, (SendModes)1); } catch { } } public void SendJoypad(ushort t, uint frame, byte state) { if (_disposed || !Connected) { return; } int num = _joypadSentIndex % 3; _joypadSentFrames[num] = frame; _joypadSentStates[num] = state; _joypadSentIndex++; byte[] array = new byte[9] { (byte)(frame & 0xFF), (byte)((frame >> 8) & 0xFF), (byte)((frame >> 16) & 0xFF), (byte)((frame >> 24) & 0xFF), state, 0, 0, 0, 0 }; for (int i = 1; i <= 2; i++) { int num2 = ((_joypadSentIndex - 1 - i) % 3 + 3) % 3; uint num3 = _joypadSentFrames[num2]; byte b = _joypadSentStates[num2]; long num4 = (long)frame - (long)num3; array[5 + (i - 1) * 2] = (byte)((num4 > 0 && num4 <= 255) ? ((byte)num4) : 0); array[5 + (i - 1) * 2 + 1] = b; } try { ClientController.Instance.SendCustomPacketToPlayer(array, "codedmg.linkcable_joypad", t, (SendModes)0); } catch { } } private void OnRawInvite(ushort from, byte[] data) { if (!_disposed) { this.InviteReceived?.Invoke(from, ReadStr(data)); } } private void OnRawAccept(ushort from, byte[] data) { if (!_disposed) { this.AcceptReceived?.Invoke(from, ReadStr(data)); } } private void OnRawDecline(ushort from, byte[] data) { if (!_disposed) { this.DeclineReceived?.Invoke(from); } } private void OnRawDisconnect(ushort from, byte[] data) { if (!_disposed) { this.DisconnectReceived?.Invoke(from, (byte)((data != null && data.Length != 0) ? data[0] : 0)); } } private void OnRawRomHash(ushort from, byte[] data) { if (!_disposed) { this.RomHashReceived?.Invoke(from, ReadStr(data)); } } private void OnRawSaveState(ushort from, byte[] data) { if (!_disposed && data != null && data.Length != 0) { Debug.Log((object)$"[CodeDMG] SaveState RECEIVE from {from}: {data.Length} bytes"); this.SaveStateReceived?.Invoke(from, data); } } private void OnRawLinkSettings(ushort from, byte[] data) { if (!_disposed && data != null && data.Length >= 4) { int arg = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); this.LinkSettingsReceived?.Invoke(from, arg); } } private void OnRawJoypad(ushort from, byte[] data) { if (_disposed || data == null || data.Length < 5) { return; } uint num = (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); this.JoypadReceived?.Invoke(from, num, data[4]); for (int i = 0; i < 2 && 5 + i * 2 + 1 < data.Length; i++) { byte b = data[5 + i * 2]; if (b != 0) { uint arg = num - b; this.JoypadReceived?.Invoke(from, arg, data[5 + i * 2 + 1]); } } } private void Send(byte[] payload, string id, ushort t, SendModes mode = (SendModes)2) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (_disposed || !Connected) { return; } try { ClientController.Instance.SendCustomPacketToPlayer(payload, id, t, mode); } catch { } } private static byte[] Str(string s) { return Encoding.UTF8.GetBytes(s ?? string.Empty); } private static string ReadStr(byte[] d) { if (d == null || d.Length == 0) { return string.Empty; } try { return Encoding.UTF8.GetString(d); } catch { return string.Empty; } } } } namespace BRCCodeDmg.Transport.Packets { internal static class LinkCablePacketIds { public const string Invite = "codedmg.linkcable_invite"; public const string Accept = "codedmg.linkcable_accept"; public const string Decline = "codedmg.linkcable_decline"; public const string Disconnect = "codedmg.linkcable_disconnect"; public const string SerialByte = "codedmg.linkcable_serial"; public const string RomHash = "codedmg.linkcable_romhash"; public const string SaveState = "codedmg.linkcable_savestate"; public const string LinkSettings = "codedmg.linkcable_settings"; public const string Joypad = "codedmg.linkcable_joypad"; } } namespace BRCCodeDmg.Patches { [HarmonyPatch] internal static class CorePauseLinkCablePatch { [HarmonyTargetMethods] private static IEnumerable TargetMethods() { string[] names = new string[3] { "PauseFromGame", "SetupPauseCore", "TogglePauseCore" }; string[] array = names; foreach (string name in array) { MethodInfo method = AccessTools.Method(typeof(Core), name, (Type[])null, (Type[])null); if (method != null) { yield return method; } } } private static void Postfix() { AppCodeDmg.HandleGamePauseStarted(); } } [HarmonyPatch] internal static class LinkCablePhoneNavigationPatch { [HarmonyTargetMethods] private static IEnumerable TargetMethods() { Type phoneType = typeof(Phone); string[] candidates = new string[4] { "PhoneMoveLeft", "OnPressLeft", "HandlePressedBackButton", "CloseCurrentApp" }; string[] array = candidates; foreach (string name in array) { MethodInfo method = AccessTools.Method(phoneType, name, (Type[])null, (Type[])null); if (method != null) { yield return method; } } } private static bool Prefix() { if (PhoneMenuInputRouter.TryHandleBack()) { PopupState.SuppressPhoneNavFor(); return false; } if (PopupState.IsSuppressed) { return false; } return true; } } [HarmonyPatch] internal static class MasterMenuPhoneDirectionPatch { [HarmonyTargetMethods] private static IEnumerable TargetMethods() { HashSet names = new HashSet { "PhoneMoveUp", "PhoneMoveDown", "PhoneMoveRight", "OnPressUp", "OnPressDown", "OnPressRight", "OnReleaseRight", "OnHoldUp", "OnHoldDown", "OnReleaseUp", "OnReleaseDown" }; foreach (Type type in GetPhoneTypes()) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo method in methods) { if (!method.IsAbstract && !method.ContainsGenericParameters && names.Contains(method.Name)) { yield return method; } } } } private static bool Prefix(MethodBase __originalMethod) { if (__originalMethod == null) { return true; } switch (__originalMethod.Name) { case "PhoneMoveUp": case "OnPressUp": return !PhoneMenuInputRouter.TryHandleUp(held: false); case "OnHoldUp": return !PhoneMenuInputRouter.TryHandleUp(held: true); case "OnReleaseUp": PhoneMenuInputRouter.ResetUp(); return true; case "PhoneMoveDown": case "OnPressDown": return !PhoneMenuInputRouter.TryHandleDown(held: false); case "OnHoldDown": return !PhoneMenuInputRouter.TryHandleDown(held: true); case "OnReleaseDown": PhoneMenuInputRouter.ResetDown(); return true; case "PhoneMoveRight": case "OnPressRight": return !PhoneMenuInputRouter.TryHandleRight(); case "OnReleaseRight": AppCodeDmg.MarkRightMenuOpenReleased(); return true; default: return true; } } private static IEnumerable GetPhoneTypes() { Type[] types; try { types = typeof(Phone).Assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { ReflectionTypeLoadException ex2 = ex; types = ex2.Types; } if (types == null) { yield break; } Type[] array = types; foreach (Type type in array) { if (!(type == null) && type.Namespace != null && (type.Namespace == "Reptile.Phone" || type.Namespace.StartsWith("Reptile.Phone."))) { yield return type; } } } } internal static class PhoneMenuInputRouter { private const float PressCooldown = 0.11f; private const float RepeatDelay = 0.34f; private const float RepeatInterval = 0.075f; private const float HoldResetGap = 0.25f; private const float ButtonCooldown = 0.16f; private static float _lastUpTime = -1f; private static float _lastDownTime = -1f; private static float _upPressTime = -1f; private static float _downPressTime = -1f; private static float _lastUpSignalTime = -1f; private static float _lastDownSignalTime = -1f; private static int _lastUpFrame = -1; private static int _lastDownFrame = -1; private static float _lastRightTime = -1f; private static float _lastBackTime = -1f; public static bool TryHandleUp(bool held) { return TryHandleDirection(ref _lastUpTime, ref _upPressTime, ref _lastUpSignalTime, ref _lastUpFrame, held, () => GBPaletteMenu.TryHandlePhoneUp() || VolumeMenu.TryHandlePhoneUp() || RomSelectMenu.TryHandlePhoneUp() || LinkCableConnectDialog.TryHandlePhoneUp() || LinkCablePlayerList.TryHandlePhoneUp() || MasterMenu.TryHandlePhoneUp()); } public static bool TryHandleDown(bool held) { return TryHandleDirection(ref _lastDownTime, ref _downPressTime, ref _lastDownSignalTime, ref _lastDownFrame, held, () => GBPaletteMenu.TryHandlePhoneDown() || VolumeMenu.TryHandlePhoneDown() || RomSelectMenu.TryHandlePhoneDown() || LinkCableConnectDialog.TryHandlePhoneDown() || LinkCablePlayerList.TryHandlePhoneDown() || MasterMenu.TryHandlePhoneDown()); } public static void ResetUp() { _upPressTime = -1f; _lastUpSignalTime = -1f; } public static void ResetDown() { _downPressTime = -1f; _lastDownSignalTime = -1f; } public static bool ConsumedUpThisFrame() { return _lastUpFrame == Time.frameCount; } public static bool ConsumedDownThisFrame() { return _lastDownFrame == Time.frameCount; } public static bool TryHandleRight() { if (!AnyMenuVisible()) { if (PopupState.ShouldSuppressMenuInputForChat()) { return false; } if (AppCodeDmg.ShouldBlockRightMenuOpenSignal()) { return true; } float unscaledTime = Time.unscaledTime; if (unscaledTime - _lastRightTime < 0.16f) { return true; } bool flag = AppCodeDmg.TryOpenMasterMenuFromRightInput(); if (flag) { _lastRightTime = unscaledTime; } return flag; } bool flag2 = TryHandle(ref _lastRightTime, 0.16f, () => GBPaletteMenu.TryHandlePhoneRight() || VolumeMenu.TryHandlePhoneRight() || RomSelectMenu.TryHandlePhoneRight() || LinkCableConnectDialog.TryHandlePhoneRight() || LinkCablePlayerList.TryHandlePhoneRight() || MasterMenu.TryHandlePhoneRight()); if (flag2 && !AnyMenuVisible()) { AppCodeDmg.BeginRightMenuOpenReleaseGuard(); } return flag2; } public static bool TryHandleBack() { return TryHandle(ref _lastBackTime, 0.16f, () => GBPaletteMenu.TryHandlePhoneBack() || VolumeMenu.TryHandlePhoneBack() || RomSelectMenu.TryHandlePhoneBack() || LinkCableConnectDialog.TryHandlePhoneBack() || LinkCablePlayerList.TryHandlePhoneBack() || MasterMenu.TryHandlePhoneBack()); } private static bool TryHandleDirection(ref float lastTime, ref float pressTime, ref float lastSignalTime, ref int lastFrame, bool held, Func handler) { if (!AnyMenuVisible()) { return false; } if (PopupState.ShouldSuppressMenuInputForChat()) { return false; } float unscaledTime = Time.unscaledTime; lastFrame = Time.frameCount; if (held) { if (pressTime < 0f || unscaledTime - lastSignalTime > 0.25f) { pressTime = unscaledTime; } lastSignalTime = unscaledTime; if (unscaledTime - pressTime < 0.34f) { return true; } if (unscaledTime - lastTime < 0.075f) { return true; } } else { lastSignalTime = unscaledTime; if (unscaledTime - lastTime < 0.11f) { return true; } pressTime = unscaledTime; } bool flag = handler(); if (flag) { lastTime = unscaledTime; } return flag; } private static bool TryHandle(ref float lastTime, float cooldown, Func handler) { if (!AnyMenuVisible()) { return false; } if (PopupState.ShouldSuppressMenuInputForChat()) { return false; } float unscaledTime = Time.unscaledTime; if (unscaledTime - lastTime < cooldown) { return true; } bool flag = handler(); if (flag) { lastTime = unscaledTime; } return flag; } private static bool AnyMenuVisible() { return GBPaletteMenu.IsVisible || VolumeMenu.IsVisible || RomSelectMenu.IsVisible || LinkCableConnectDialog.IsVisible || LinkCablePlayerList.IsVisible || MasterMenu.IsVisible; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }