Decompiled source of GungeonTogether LAN v0.3.7
BepInEx/plugins/GungeonTogetherLAN/GungeonTogether.LAN-0.3.7-alpha.dll
Decompiled 13 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using BepInEx; using Dungeonator; using FullInspector; using GungeonTogether.Lan; using GungeonTogether.Networking; using HarmonyLib; using InControl; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("GungeonTogether LAN Dungeon Sync Alpha")] [assembly: AssemblyDescription("Experimental host-authoritative two-PC dungeon synchronization")] [assembly: AssemblyFileVersion("0.3.7.0")] [assembly: AssemblyVersion("0.3.7.0")] namespace GungeonTogether.Networking { public sealed class RemotePlayerAvatar : MonoBehaviour { private readonly SnapshotBuffer<LanPlayerState> frames = new SnapshotBuffer<LanPlayerState>(0.06); private tk2dSprite sprite; private string collectionKey; private tk2dSpriteCollectionData collection; private Vector3 firstPosition; public int RenderedFrames { get; private set; } public float DistanceMoved { get; private set; } public string CollectionName { get { if (!((Object)(object)collection == (Object)null)) { return ((Object)collection).name; } return ""; } } public static RemotePlayerAvatar Create(ulong peer, Vector2 position, float rotation) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) return new GameObject("GT_FoyerPeer_" + peer).AddComponent<RemotePlayerAvatar>(); } public void Apply(LanPlayerState state) { frames.Push(state, state.Sequence, state.SentAt, Time.realtimeSinceStartup); } private void LateUpdate() { //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_012d: 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_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0148: 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) //IL_0142: 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_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: 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_0169: Unknown result type (might be due to invalid IL or missing references) if (!frames.Sample(Time.realtimeSinceStartup, out var from, out var to, out var blend)) { return; } string text = from.CollectionGuid + "|" + from.Collection; if ((Object)(object)collection == (Object)null || collectionKey != text) { NativeWorld.CharacterPrefab(from.Character); collection = FindCollection(from); collectionKey = text; } if (!((Object)(object)collection == (Object)null) && from.SpriteId < collection.spriteDefinitions.Length) { if ((Object)(object)sprite == (Object)null) { sprite = ((Component)this).gameObject.AddComponent<tk2dSprite>(); } if ((Object)(object)((tk2dBaseSprite)sprite).Collection != (Object)(object)collection || ((tk2dBaseSprite)sprite).spriteId != from.SpriteId) { ((tk2dBaseSprite)sprite).SetSprite(collection, from.SpriteId); } Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(from.X, from.Y, from.Z); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(to.X, to.Y, to.Z); Transform transform = ((Component)this).transform; Vector3 val3 = val2 - val; transform.position = ((((Vector3)(ref val3)).sqrMagnitude < 64f) ? Vector3.Lerp(val, val2, blend) : val); if (RenderedFrames == 0) { firstPosition = ((Component)this).transform.position; } RenderedFrames++; DistanceMoved = Mathf.Max(DistanceMoved, Vector3.Distance(firstPosition, ((Component)this).transform.position)); ((Component)this).transform.rotation = Quaternion.Euler(0f, 0f, Mathf.LerpAngle(from.Rotation, to.Rotation, blend)); ((Component)this).transform.localScale = new Vector3(from.ScaleX, from.ScaleY, 1f); ((tk2dBaseSprite)sprite).HeightOffGround = from.Height; ((tk2dBaseSprite)sprite).FlipX = from.FlipX; } } private static tk2dSpriteCollectionData FindCollection(LanPlayerState state) { tk2dSpriteCollectionData[] array = Resources.FindObjectsOfTypeAll<tk2dSpriteCollectionData>(); foreach (tk2dSpriteCollectionData val in array) { if ((!string.IsNullOrEmpty(state.CollectionGuid) && val.spriteCollectionGUID == state.CollectionGuid) || (string.IsNullOrEmpty(state.CollectionGuid) && (((Object)val).name == state.Collection || val.spriteCollectionName == state.Collection))) { return val.inst; } } if (!string.IsNullOrEmpty(state.CollectionGuid)) { try { return tk2dSystem.LoadResourceByGUID<tk2dSpriteCollectionData>(state.CollectionGuid); } catch { } } return null; } } } namespace GungeonTogether.Lan { public sealed class SoundSwitch { public uint Group; public uint Value; } public sealed class SoundCue { public uint Event; public uint Emitter; public float X; public float Y; public readonly List<SoundSwitch> Switches = new List<SoundSwitch>(); } public sealed class SoundBatch { public const int MaxCues = 64; public const int MaxSwitches = 8; public int Epoch; public uint Sequence; public double SentAt; public readonly List<SoundCue> Cues = new List<SoundCue>(); public byte[] Encode() { Validate(); return Wire.Write(DungeonMessage.Audio, delegate(BinaryWriter w) { w.Write(Epoch); w.Write(Sequence); w.Write(SentAt); w.Write((byte)Cues.Count); foreach (SoundCue cue in Cues) { w.Write(cue.Event); w.Write(cue.Emitter); w.Write(cue.X); w.Write(cue.Y); w.Write((byte)cue.Switches.Count); foreach (SoundSwitch @switch in cue.Switches) { w.Write(@switch.Group); w.Write(@switch.Value); } } }); } public static SoundBatch Decode(byte[] bytes) { return Wire.Read(bytes, DungeonMessage.Audio, delegate(BinaryReader r) { SoundBatch soundBatch = new SoundBatch { Epoch = r.ReadInt32(), Sequence = r.ReadUInt32(), SentAt = r.ReadDouble() }; int num = r.ReadByte(); if (num > 64) { throw new IOException("Audio batch too large."); } for (int i = 0; i < num; i++) { SoundCue soundCue = new SoundCue { Event = r.ReadUInt32(), Emitter = r.ReadUInt32(), X = r.ReadSingle(), Y = r.ReadSingle() }; int num2 = r.ReadByte(); if (num2 > 8) { throw new IOException("Too many audio switches."); } for (int j = 0; j < num2; j++) { soundCue.Switches.Add(new SoundSwitch { Group = r.ReadUInt32(), Value = r.ReadUInt32() }); } soundBatch.Cues.Add(soundCue); } soundBatch.Validate(); return soundBatch; }); } private void Validate() { if (Epoch < 1 || Cues.Count > 64) { throw new IOException("Invalid audio epoch or count."); } Wire.Time(SentAt); foreach (SoundCue cue in Cues) { if (cue == null || cue.Event == 0 || cue.Emitter == 0 || cue.Switches.Count > 8) { throw new IOException("Invalid sound cue."); } Wire.Range(cue.X, -100000f, 100000f); Wire.Range(cue.Y, -100000f, 100000f); HashSet<uint> hashSet = new HashSet<uint>(); foreach (SoundSwitch @switch in cue.Switches) { if (@switch == null || @switch.Group == 0 || @switch.Value == 0 || !hashSet.Add(@switch.Group)) { throw new IOException("Invalid sound switch."); } } } } } public sealed class SoundSequence { private int epoch; private uint sequence; private bool seen; private double clockOffset = double.PositiveInfinity; private void EnsureEpoch(int currentEpoch) { if (epoch != currentEpoch) { epoch = currentEpoch; sequence = 0u; seen = false; clockOffset = double.PositiveInfinity; } } public void ObserveClock(int currentEpoch, double senderTime, double localTime) { EnsureEpoch(currentEpoch); Wire.Time(senderTime); Wire.Time(localTime); clockOffset = Math.Min(clockOffset, localTime - senderTime); } public bool Accept(SoundBatch batch, int currentEpoch, double latestSnapshotTime, double localTime = -1.0) { if (batch.Epoch != currentEpoch) { return false; } EnsureEpoch(currentEpoch); if (seen && !Wire.Newer(batch.Sequence, sequence)) { return false; } seen = true; sequence = batch.Sequence; if (localTime >= 0.0 && !double.IsPositiveInfinity(clockOffset)) { latestSnapshotTime = Math.Max(latestSnapshotTime, localTime - clockOffset); } if (!(latestSnapshotTime <= 0.0)) { return batch.SentAt >= latestSnapshotTime - 0.3; } return true; } } public sealed class AudioReplication : IDisposable { private sealed class Source { public uint Id; public GameObject Object; public float Touched; public readonly Dictionary<uint, uint> Switches = new Dictionary<uint, uint>(); } [HarmonyPatch] private static class Events { private static IEnumerable<MethodBase> TargetMethods() { MethodInfo[] methods = typeof(AkSoundEngine).GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "PostEvent" && (object)methodInfo.ReturnType == typeof(uint)) { yield return methodInfo; } } } [HarmonyPriority(800)] private static bool Prefix(object __0, GameObject __1, ref uint __result) { DungeonSession session = Session; if (session == null || session.Phase != DungeonSession.RunPhase.Running || replaying || !BattleEvent(__0)) { return true; } if (session.IsHost) { session.Audio.Capture(__0, __1); return true; } __result = 0u; return false; } } [HarmonyPatch] private static class Switches { private static IEnumerable<MethodBase> TargetMethods() { MethodInfo[] methods = typeof(AkSoundEngine).GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "SetSwitch" && methodInfo.GetParameters().Length == 3 && (object)methodInfo.GetParameters()[2].ParameterType == typeof(GameObject)) { yield return methodInfo; } } } private static void Prefix(object __0, object __1, GameObject __2) { DungeonSession session = Session; if (session == null || !session.IsHost || session.Phase != DungeonSession.RunPhase.Running || replaying) { return; } Source source = session.Audio.GetSource(__2); if (source != null) { uint key = Id(__0); if (source.Switches.ContainsKey(key) || source.Switches.Count < 8) { source.Switches[key] = Id(__1); } } } } private readonly Dictionary<int, Source> sources = new Dictionary<int, Source>(); private readonly Dictionary<uint, Source> receivers = new Dictionary<uint, Source>(); private readonly List<SoundCue> pending = new List<SoundCue>(); private readonly List<int> expired = new List<int>(); private readonly List<uint> expiredReceivers = new List<uint>(); private readonly SoundSequence sequence = new SoundSequence(); private uint nextSource; private uint nextBatch; private float nextFlush; private float oldest; [ThreadStatic] private static bool replaying; public int Captured { get; private set; } public int Dispatched { get; private set; } public int Dropped { get; private set; } public int FailedEvents { get; private set; } public int SwitchesApplied { get; private set; } public int Queued => pending.Count; private static DungeonSession Session { get { if (!((Object)(object)DungeonPlugin.Instance == (Object)null)) { return DungeonPlugin.Instance.Session; } return null; } } public void ObserveClock(int epoch, double senderTime) { sequence.ObserveClock(epoch, senderTime, Time.realtimeSinceStartup); } private static bool BattleEvent(object id) { if (!(id is string text)) { if (id is uint) { return (uint)id != 0; } return false; } string[] array = new string[8] { "WPN_", "ENM_", "OBJ_", "CHR_", "FS_", "VO_", "PLR_", "BOSS_" }; foreach (string text2 in array) { if (text.StartsWith("Play_" + text2, StringComparison.OrdinalIgnoreCase) || text.StartsWith("Stop_" + text2, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static uint Id(object value) { if (!(value is uint)) { return AkSoundEngine.GetIDFromString((string)value); } return (uint)value; } public void PlayHurt(PlayerController player, int character) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return; } replaying = true; try { AkSoundEngine.SetSwitch("CHR_Player", CharacterCatalog.Identity(character), ((Component)player).gameObject); AkSoundEngine.PostEvent("Play_CHR_general_hurt_01", ((Component)player).gameObject); } finally { replaying = false; } } private Source GetSource(GameObject obj) { if ((Object)(object)obj == (Object)null) { return null; } if (!sources.TryGetValue(((Object)obj).GetInstanceID(), out var value)) { if (sources.Count >= 256) { return null; } value = new Source { Id = ++nextSource, Object = obj }; Gun component = obj.GetComponent<Gun>(); if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(component.gunSwitchGroup)) { value.Switches[AkSoundEngine.GetIDFromString("WPN_Guns")] = AkSoundEngine.GetIDFromString(component.gunSwitchGroup); } PlayerController component2 = obj.GetComponent<PlayerController>(); if ((Object)(object)component2 != (Object)null) { value.Switches[AkSoundEngine.GetIDFromString("CHR_Player")] = AkSoundEngine.GetIDFromString(((object)Unsafe.As<PlayableCharacters, PlayableCharacters>(ref component2.characterIdentity)/*cast due to .constrained prefix*/).ToString()); } sources.Add(((Object)obj).GetInstanceID(), value); } value.Touched = Time.realtimeSinceStartup; return value; } private void Capture(object id, GameObject obj) { //IL_0037: 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_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0082: Unknown result type (might be due to invalid IL or missing references) if (pending.Count >= 256) { Dropped++; return; } Source source = GetSource(obj); if (source == null) { return; } Vector3 position = obj.transform.position; PlayerController secondaryPlayer = GameManager.Instance.SecondaryPlayer; if (((Object)(object)secondaryPlayer != (Object)null && (Object)(object)obj == (Object)(object)((Component)secondaryPlayer).gameObject && Id(id) == AkSoundEngine.GetIDFromString("Play_CHR_general_hurt_01")) || ((Object)(object)secondaryPlayer != (Object)null && Vector2.Distance(Vector3Extensions.XY(position), ((GameActor)secondaryPlayer).CenterPosition) > 80f)) { return; } SoundCue soundCue = new SoundCue { Event = Id(id), Emitter = source.Id, X = position.x, Y = position.y }; foreach (KeyValuePair<uint, uint> @switch in source.Switches) { soundCue.Switches.Add(new SoundSwitch { Group = @switch.Key, Value = @switch.Value }); } if (pending.Count == 0) { oldest = Time.realtimeSinceStartup; } pending.Add(soundCue); Captured++; } public void Flush(int epoch, LanTransport transport) { float realtimeSinceStartup = Time.realtimeSinceStartup; expired.Clear(); foreach (KeyValuePair<int, Source> source in sources) { if ((Object)(object)source.Value.Object == (Object)null || realtimeSinceStartup - source.Value.Touched > 10f) { expired.Add(source.Key); } } foreach (int item in expired) { sources.Remove(item); } if (!(realtimeSinceStartup < nextFlush) && pending.Count != 0) { nextFlush = realtimeSinceStartup + 1f / 30f; if (realtimeSinceStartup - oldest > 0.25f || transport.OldestPendingMilliseconds > 150.0) { Dropped += pending.Count; pending.Clear(); return; } SoundBatch soundBatch = new SoundBatch { Epoch = epoch, Sequence = ++nextBatch, SentAt = realtimeSinceStartup }; int count = Math.Min(64, pending.Count); soundBatch.Cues.AddRange(pending.GetRange(0, count)); pending.RemoveRange(0, count); transport.Send(soundBatch.Encode()); } } public void Receive(SoundBatch batch, int epoch, WorldSnapshot snapshot) { //IL_00ec: 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_00b2: Expected O, but got Unknown //IL_0122: Unknown result type (might be due to invalid IL or missing references) if (!sequence.Accept(batch, epoch, snapshot?.SentAt ?? 0.0, Time.realtimeSinceStartup)) { Dropped += batch.Cues.Count; return; } replaying = true; try { foreach (SoundCue cue in batch.Cues) { if (!receivers.TryGetValue(cue.Emitter, out var value)) { if (receivers.Count >= 256) { Dropped++; continue; } value = new Source { Object = new GameObject("GT_NetworkSound") }; receivers.Add(cue.Emitter, value); } value.Touched = Time.realtimeSinceStartup; value.Object.transform.position = new Vector3(cue.X, cue.Y, 0f); foreach (SoundSwitch @switch in cue.Switches) { AkSoundEngine.SetSwitch(@switch.Group, @switch.Value, value.Object); SwitchesApplied++; } uint num = AkSoundEngine.PostEvent(cue.Event, value.Object); Dispatched++; if (num == 0 && !DungeonPlugin.Instance.IsSmoke) { FailedEvents++; } } } finally { replaying = false; } Maintain(); } public void Maintain() { expiredReceivers.Clear(); foreach (KeyValuePair<uint, Source> receiver in receivers) { if (Time.realtimeSinceStartup - receiver.Value.Touched > 5f) { expiredReceivers.Add(receiver.Key); } } foreach (uint expiredReceiver in expiredReceivers) { DestroySource(receivers[expiredReceiver]); receivers.Remove(expiredReceiver); } } private static void DestroySource(Source source) { if (!((Object)(object)source.Object == (Object)null)) { AkSoundEngine.StopAll(source.Object); Object.Destroy((Object)(object)source.Object); } } public void Dispose() { foreach (Source value in receivers.Values) { DestroySource(value); } receivers.Clear(); sources.Clear(); pending.Clear(); } } public static class CharacterCatalog { public const int GuestCount = 5; private static readonly string[] Prefabs = new string[9] { "PlayerCoopCultist", "PlayerRogue", "PlayerMarine", "PlayerConvict", "PlayerGuide", "PlayerRobot", "PlayerBullet", "PlayerGunslinger", "PlayerEevee" }; private static readonly string[] Identities = new string[9] { "CoopCultist", "Pilot", "Soldier", "Convict", "Guide", "Robot", "Bullet", "Gunslinger", "Eevee" }; private static readonly string[] Names = new string[9] { "Cultist", "Pilot", "Marine", "Convict", "Hunter", "Robot", "Bullet", "Gunslinger", "Paradox" }; public static bool Valid(int id) { if (id >= 0) { return id < Prefabs.Length; } return false; } public static bool ValidGuest(int id) { if (id >= 0) { return id < 5; } return false; } public static string Prefab(int id) { if (!Valid(id)) { throw new IOException("Unknown character."); } return Prefabs[id]; } public static string Name(int id) { if (!Valid(id)) { return "?"; } return Names[id]; } public static string Identity(int id) { if (!Valid(id)) { throw new IOException("Unknown character."); } return Identities[id]; } public static int FromIdentity(string identity) { for (int i = 0; i < Identities.Length; i++) { if (Identities[i] == identity) { return i; } } throw new IOException("Unsupported host character: " + identity); } } public sealed class LobbyChoice { public int Character; public byte[] Encode() { return Wire.Write(DungeonMessage.Character, delegate(BinaryWriter w) { w.Write(Character); }); } public static LobbyChoice Decode(byte[] bytes) { return Wire.Read(bytes, DungeonMessage.Character, delegate(BinaryReader r) { int num = r.ReadInt32(); if (!CharacterCatalog.ValidGuest(num)) { throw new IOException("Unsupported guest character."); } return new LobbyChoice { Character = num }; }); } } internal static class ClientSimulationGuard { [HarmonyPatch(typeof(GenericIntroDoer), "TriggerSequence")] private static class ClientBossIntro { private static bool Prefix() { return !Mirroring; } } [HarmonyPatch(typeof(PhysicsEngine), "LateUpdate")] private static class ClientPhysics { private static bool Prefix() { return !Mirroring; } } private static readonly FieldInfo CurrentRoomField = AccessTools.Field(typeof(PlayerController), "m_currentRoom"); private static readonly FieldInfo CurrentlyVisibleField = AccessTools.Field(typeof(RoomHandler), "m_currentlyVisible"); private static bool Mirroring => (((Object)(object)DungeonPlugin.Instance == (Object)null) ? null : DungeonPlugin.Instance.Session)?.Mirroring ?? false; internal static void UpdatePlaceholderRoom(PlayerController player, RoomHandler room) { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: 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_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || room == null || player.CurrentRoom == room) { return; } if ((object)CurrentRoomField == null || (object)CurrentlyVisibleField == null) { throw new MissingFieldException("The native client room fields are missing from this game build."); } RoomHandler currentRoom = player.CurrentRoom; CurrentRoomField.SetValue(player, room); bool flag = false; PlayerController[] allPlayers = GameManager.Instance.AllPlayers; foreach (PlayerController val in allPlayers) { if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)player && val.CurrentRoom == currentRoom) { flag = true; } } if (currentRoom != null && !flag) { CurrentlyVisibleField.SetValue(currentRoom, false); currentRoom.visibility = (VisibilityStatus)1; } CurrentlyVisibleField.SetValue(room, true); room.visibility = (VisibilityStatus)2; if ((Object)(object)Pixelator.Instance != (Object)null) { IntVector2 val2 = Vector3Extensions.IntXY(((BraveBehaviour)player).transform.position, (VectorConversions)0); if (currentRoom != null) { Pixelator.Instance.ProcessOcclusionChange(val2, 0.3f, currentRoom, false); } Pixelator.Instance.ProcessOcclusionChange(val2, 1f, room, false); Pixelator.Instance.ProcessRoomAdditionalExits(val2, room, false); } ShadowSystem.ForceRoomLightsUpdate(room, 0.1f); } internal static void MovePlaceholder(PlayerController player, Vector2 position) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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_0040: 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) ((BraveBehaviour)player).transform.position = new Vector3(position.x, position.y, ((BraveBehaviour)player).transform.position.z); if ((Object)(object)((BraveBehaviour)player).specRigidbody != (Object)null) { ((BraveBehaviour)player).specRigidbody.Position = new Position(position); } } } internal sealed class DesktopSmoke : MonoBehaviour { private delegate bool WindowCallback(IntPtr handle, IntPtr parameter); private IntPtr window; private float nextCheck; public bool Verified { get; private set; } [DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr window, int index); [DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr window, int index, int value); [DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr window, IntPtr after, int x, int y, int width, int height, uint flags); [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] private static extern bool EnumWindows(WindowCallback callback, IntPtr parameter); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr handle, out uint process); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassName(IntPtr handle, StringBuilder name, int size); [DllImport("kernel32.dll")] private static extern uint GetCurrentProcessId(); private void Awake() { Application.runInBackground = true; Application.targetFrameRate = 60; QualitySettings.vSyncCount = 0; Screen.SetResolution(800, 600, false); Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; ConfigureWindow(); } private void ConfigureWindow() { uint current = GetCurrentProcessId(); EnumWindows(delegate(IntPtr handle, IntPtr parameter) { GetWindowThreadProcessId(handle, out var process); if (process != current) { return true; } StringBuilder stringBuilder = new StringBuilder(128); GetClassName(handle, stringBuilder, stringBuilder.Capacity); if (stringBuilder.ToString() != "UnityWndClass") { return true; } window = handle; return false; }, IntPtr.Zero); if (!(window == IntPtr.Zero)) { SetWindowLong(window, -20, GetWindowLong(window, -20) | 0x8000000); SetWindowPos(window, new IntPtr(1), 20, 20, 0, 0, 81u); } } private void LateUpdate() { //IL_00df: Unknown result type (might be due to invalid IL or missing references) AudioListener.pause = true; AudioListener.volume = 0f; Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; if (!(Time.realtimeSinceStartup < nextCheck)) { nextCheck = Time.realtimeSinceStartup + 1f; if (GameManager.HasInstance) { AccessTools.Field(typeof(GameManager), "m_applicationHasFocus").SetValue(GameManager.Instance, true); } if (window == IntPtr.Zero) { ConfigureWindow(); } if (Screen.fullScreen || Screen.width > 820 || Screen.height > 620) { Screen.SetResolution(800, 600, false); } Verified = window != IntPtr.Zero && !Screen.fullScreen && Screen.width <= 820 && Screen.height <= 620 && (int)Cursor.lockState == 0 && GetForegroundWindow() != window; } } } [BepInPlugin("com.gungeontogether.lan", "Gungeon Together LAN Alpha", "0.3.7")] [BepInIncompatibility("com.llamerrr.gungeontogether")] public sealed class DungeonPlugin : BaseUnityPlugin { private bool hostTab = true; private bool showNetworkDetails; private GUIStyle menuTitle; private GUIStyle menuText; private GUIStyle menuSmall; private GUIStyle menuButton; private GUIStyle menuField; private GUIStyle menuValue; private static readonly Color Ink = new Color(0.075f, 0.068f, 0.068f, 0.98f); private static readonly Color Panel = new Color(0.13f, 0.115f, 0.105f, 1f); private static readonly Color Border = new Color(0.38f, 0.32f, 0.23f, 1f); private static readonly Color Gold = new Color(0.79f, 0.66f, 0.39f, 1f); private static readonly Color Paper = new Color(0.91f, 0.87f, 0.76f, 1f); private static readonly Color Muted = new Color(0.61f, 0.59f, 0.53f, 1f); public static DungeonPlugin Instance; private LanTransport transport; private Harmony harmony; private string ip; private string bind; private string port; private string code; private string fingerprint; private string error = ""; private Rect window = new Rect(36f, 36f, 620f, 586f); private bool visible = true; private readonly bool presentationProbe = Array.IndexOf(Environment.GetCommandLineArgs(), "--gt-presentation-probe") >= 0; private bool clientMovementPrediction; private RemotePlayerAvatar foyerAvatar; private float nextPosition; private uint foyerSequence; private int guestChoice; private PlayerController menuPlayer; private string smokeRole; private bool profileCheck; private bool profilePathChecked; private string profileObservedPath; private int smokePort = 27639; private float smokeDeadline; private float smokeStarted; private float nextSmokeDiagnostic; private float nextControlsDiagnostic; private bool smokeRunStarted; private bool smokeLayout; private bool smokeSnapshot; private bool smokeMovePassed; private bool smokeShotPassed; private bool smokeCaptureRequested; private AIActor smokeEnemy; private float smokeEnemyHealth; private bool smokeEnemyCreated; private bool smokeDamagePassed; private bool smokeSecondFloor; private float smokeSecondFloorAt; private bool smokeComplete; private Vector3 smokeCultistStart; private int smokeStartAmmo; private DesktopSmoke desktopSmoke; private float smokeLobbyAt; private bool smokeFoyerPassed; private bool smokeDesktopPassed; private bool smokeMenuRequested; private bool smokeMenuVisible; public DungeonSession Session { get; private set; } public bool IsSmoke { get { if (smokeRole == null) { return profileCheck; } return true; } } public bool UsesSeparateProfile { get; private set; } private void InitMenuStyles() { //IL_000c: 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_0032: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Expected O, but got Unknown //IL_00aa: 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) if (menuTitle == null) { menuTitle = TextStyle(25, Gold, (TextAnchor)3); menuText = TextStyle(16, Paper, (TextAnchor)0); menuSmall = TextStyle(13, Muted, (TextAnchor)0); menuValue = TextStyle(19, Paper, (TextAnchor)4); menuButton = TextStyle(16, Paper, (TextAnchor)4); menuField = new GUIStyle(GUI.skin.textField); menuField.fontSize = 18; menuField.padding = new RectOffset(10, 8, 7, 5); menuField.normal.textColor = Paper; menuField.focused.textColor = Paper; menuField.normal.background = Texture2D.whiteTexture; menuField.focused.background = Texture2D.whiteTexture; } } private static GUIStyle TextStyle(int size, Color color, TextAnchor anchor) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_0040: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.label) { fontSize = size }; val.normal.textColor = color; val.alignment = anchor; val.wordWrap = true; val.padding = new RectOffset(0, 0, 0, 0); return val; } private static void Fill(Rect rect, Color color) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) Color color2 = GUI.color; GUI.color = color; GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture); GUI.color = color2; } private static void Frame(Rect rect, Color fill, Color edge) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) Fill(rect, edge); Fill(new Rect(((Rect)(ref rect)).x + 2f, ((Rect)(ref rect)).y + 2f, ((Rect)(ref rect)).width - 4f, ((Rect)(ref rect)).height - 4f), fill); } private bool MenuButton(Rect rect, string text, bool enabled, bool accent) { //IL_000a: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_0079: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: 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_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: 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_0101: Unknown result type (might be due to invalid IL or missing references) bool num = enabled && ((Rect)(ref rect)).Contains(Event.current.mousePosition); Color val = (accent ? Gold : Border); Color fill = (Color)(num ? new Color(0.25f, 0.215f, 0.155f) : Panel); Frame(rect, fill, (Color)(enabled ? val : new Color(0.22f, 0.205f, 0.18f))); GUIStyle val2 = menuButton; Color textColor = val2.normal.textColor; val2.normal.textColor = ((!enabled) ? Muted : (accent ? Gold : Paper)); GUIStyleState hover = val2.hover; GUIStyleState active = val2.active; Color val3 = (val2.focused.textColor = val2.normal.textColor); Color textColor3 = (active.textColor = val3); hover.textColor = textColor3; bool enabled2 = GUI.enabled; GUI.enabled = enabled; bool result = GUI.Button(rect, "", val2); GUI.enabled = enabled2; GUI.Label(rect, text, val2); val2.normal.textColor = textColor; return result; } private string MenuField(Rect rect, string value, int length, bool enabled) { //IL_0000: 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_0015: 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) Frame(rect, new Color(0.055f, 0.05f, 0.05f), Border); Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = new Color(0.055f, 0.05f, 0.05f); bool enabled2 = GUI.enabled; GUI.enabled = enabled; string result = GUI.TextField(new Rect(((Rect)(ref rect)).x + 2f, ((Rect)(ref rect)).y + 2f, ((Rect)(ref rect)).width - 4f, ((Rect)(ref rect)).height - 4f), value, length, menuField); GUI.enabled = enabled2; GUI.backgroundColor = backgroundColor; return result; } private void OnGUI() { //IL_0164: 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_0055: 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_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: 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_0149: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Expected O, but got Unknown //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) if (transport == null) { return; } if (Session != null && Session.Mirroring && InventoryBook.BookOpen) { InventoryBook.DrawCaption(Session); } if (IsSmoke && !smokeMenuVisible) { return; } InitMenuStyles(); if (!visible) { return; } Matrix4x4 matrix = GUI.matrix; float num = Mathf.Min(1f, Mathf.Min((float)Screen.width / 692f, (float)Screen.height / 642f)); num = Mathf.Max(0.4f, num); GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(num, num, 1f)); try { float num2 = (float)Screen.width / num; float num3 = (float)Screen.height / num; ((Rect)(ref window)).x = Mathf.Clamp(((Rect)(ref window)).x, 8f, Mathf.Max(8f, num2 - ((Rect)(ref window)).width - 8f)); ((Rect)(ref window)).y = Mathf.Clamp(((Rect)(ref window)).y, 8f, Mathf.Max(8f, num3 - ((Rect)(ref window)).height - 8f)); window = GUI.Window(27631, window, new WindowFunction(DrawMenu), "", GUIStyle.none); } finally { GUI.matrix = matrix; } } private void DrawMenu(int id) { //IL_0014: 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) //IL_004b: 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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00be: 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_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: 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_0152: 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_01b1: 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_021e: 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_02a2: 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_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_0383: Unknown result type (might be due to invalid IL or missing references) //IL_03ae: Unknown result type (might be due to invalid IL or missing references) //IL_03e8: Unknown result type (might be due to invalid IL or missing references) //IL_0421: Unknown result type (might be due to invalid IL or missing references) //IL_0426: Unknown result type (might be due to invalid IL or missing references) //IL_042b: Unknown result type (might be due to invalid IL or missing references) //IL_0449: Unknown result type (might be due to invalid IL or missing references) //IL_08ea: Unknown result type (might be due to invalid IL or missing references) //IL_0910: Unknown result type (might be due to invalid IL or missing references) //IL_0963: Unknown result type (might be due to invalid IL or missing references) //IL_099a: Unknown result type (might be due to invalid IL or missing references) //IL_0507: Unknown result type (might be due to invalid IL or missing references) //IL_09c8: Unknown result type (might be due to invalid IL or missing references) //IL_0535: Unknown result type (might be due to invalid IL or missing references) //IL_0560: Unknown result type (might be due to invalid IL or missing references) //IL_04d8: Unknown result type (might be due to invalid IL or missing references) //IL_059c: Unknown result type (might be due to invalid IL or missing references) //IL_0674: Unknown result type (might be due to invalid IL or missing references) Fill(new Rect(6f, 6f, 614f, 580f), new Color(0f, 0f, 0f, 0.5f)); Frame(new Rect(0f, 0f, 614f, 580f), Ink, Border); Fill(new Rect(6f, 6f, 602f, 2f), Gold); Fill(new Rect(26f, 26f, 12f, 23f), Gold); Fill(new Rect(28f, 21f, 8f, 5f), Gold); Fill(new Rect(24f, 50f, 16f, 3f), Border); GUI.Label(new Rect(55f, 18f, 480f, 39f), "GUNGEON TOGETHER", menuTitle); GUI.Label(new Rect(55f, 59f, 490f, 23f), "NETWORK CO-OP / LAN 0.3.7 · ALPHA", menuSmall); if (MenuButton(new Rect(550f, 23f, 36f, 32f), "×", enabled: true, accent: false)) { visible = false; } if (!UsesSeparateProfile) { GUI.Label(new Rect(28f, 113f, 558f, 130f), "Normal saves are active.\n\nFor co-op, use Start Modded in a separate mod profile or launch with the LAN launcher.", menuText); GUI.Label(new Rect(28f, 480f, 558f, 44f), "Close menu: Esc or Ctrl+P", menuSmall); GUI.DragWindow(new Rect(0f, 0f, 540f, 82f)); DrawMenuCursor(); return; } if (transport.Active) { hostTab = transport.IsHost; } if (MenuButton(new Rect(28f, 99f, 272f, 37f), "HOST", !transport.Active, hostTab)) { hostTab = true; } if (MenuButton(new Rect(314f, 99f, 272f, 37f), "JOIN", !transport.Active, !hostTab)) { hostTab = false; } GUI.Label(new Rect(28f, 152f, 558f, 23f), hostTab ? "Listen address" : "Host Radmin / LAN IPv4", menuSmall); if (hostTab) { bind = MenuField(new Rect(28f, 177f, 558f, 38f), bind, 64, !transport.Active); } else { ip = MenuField(new Rect(28f, 177f, 558f, 38f), ip, 64, !transport.Active); } GUI.Label(new Rect(28f, 225f, 105f, 20f), "TCP port", menuSmall); GUI.Label(new Rect(150f, 225f, 436f, 20f), "Room code - use the same code on both PCs", menuSmall); port = MenuField(new Rect(28f, 250f, 105f, 38f), port, 5, !transport.Active); code = MenuField(new Rect(150f, 250f, 436f, 38f), code, 64, !transport.Active); Frame(new Rect(28f, 306f, 558f, 88f), Panel, Border); GUI.Label(new Rect(44f, 317f, 525f, 20f), "PLAYER TWO · RUN CHARACTER", menuSmall); bool enabled = !hostTab && (Session == null || !Session.InRun); int id2 = ((hostTab && Session != null) ? Session.GuestCharacter : guestChoice); if (hostTab && (Session == null || !Session.GuestChoiceReceived)) { GUI.Label(new Rect(44f, 348f, 525f, 32f), "Your friend chooses a hero on their PC", menuText); } else { if (MenuButton(new Rect(44f, 344f, 42f, 34f), "<", enabled, accent: false)) { ChangeGuest(-1); } GUI.Label(new Rect(98f, 343f, 391f, 35f), CharacterCatalog.Name(id2), menuValue); if (MenuButton(new Rect(528f, 344f, 42f, 34f), ">", enabled, accent: false)) { ChangeGuest(1); } } if (!transport.Active) { if (MenuButton(new Rect(28f, 410f, 558f, 43f), hostTab ? "CREATE ROOM" : "JOIN BY IP", enabled: true, accent: true)) { Connect(hostTab); } } else { bool enabled2 = transport.IsHost && transport.Connected && Session != null && Session.GuestChoiceReceived && !Session.InRun; string text = ((!transport.Connected) ? "WAITING FOR CONNECTION…" : ((Session != null && Session.InRun) ? "RUN STARTED" : (transport.IsHost ? "START RUN" : "WAITING FOR HOST TO START…"))); if (MenuButton(new Rect(28f, 410f, 558f, 43f), text, enabled2, accent: true)) { try { ClearAvatar(); Session.StartHost(); visible = false; error = ""; } catch (Exception ex) { error = ex.Message; Log(error); } } } string text2 = "Connected · RTT " + transport.RoundTripMilliseconds.ToString("0") + " ms · smoothing " + ((Session == null) ? 40.0 : Session.SmoothingMilliseconds).ToString("0") + " ms"; if (Session != null && Session.Mirroring) { text2 = text2 + " · response " + Session.InputLatencyMs.ToString("0") + " ms"; } string text3 = ((error.Length != 0) ? error : (transport.Connected ? text2 : (transport.Active ? "Check the address and room code on both PCs." : (hostTab ? "Share your Radmin / LAN IPv4 and room code with your friend." : "Both players must be on the same LAN or Radmin network.")))); if (error.Length == 0 && Session != null && Session.InRun && Session.Phase != DungeonSession.RunPhase.Running) { text3 = Session.Status; } if (showNetworkDetails) { text3 = (transport.Connected ? (text2 + " · queue " + transport.PendingBytes / 1024 + " KiB\n") : (transport.Status + "\n")) + ((Session == null) ? "" : Session.Status) + ((error.Length == 0) ? "" : ("\n" + error)); } if (showNetworkDetails && Session != null && Session.MovementBlockReason.Length > 0) { text3 = text3 + "\nPlayer two controls: " + Session.MovementBlockReason; } GUI.Label(new Rect(28f, 465f, 558f, 54f), text3, menuSmall); if (MenuButton(new Rect(28f, 534f, 248f, 30f), "DISCONNECT / FOYER", transport.Active || (Session != null && Session.InRun), accent: false)) { ReturnToFoyer(); } if (MenuButton(new Rect(290f, 534f, 142f, 30f), "DETAILS", enabled: true, accent: false)) { showNetworkDetails = !showNetworkDetails; } if (MenuButton(new Rect(446f, 534f, 140f, 30f), "CLOSE · Esc", enabled: true, accent: false)) { visible = false; } GUI.DragWindow(new Rect(0f, 0f, 540f, 82f)); DrawMenuCursor(); } private void DrawMenuCursor() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: 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) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_0083: 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_00a6: 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_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: 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_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) if ((int)Event.current.type == 7) { Vector2 mousePosition = Event.current.mousePosition; Rect val = new Rect(0f, 0f, 614f, 580f); if (((Rect)(ref val)).Contains(mousePosition)) { float num = Mathf.Round(mousePosition.x); float num2 = Mathf.Round(mousePosition.y); Color color = default(Color); ((Color)(ref color))..ctor(0.025f, 0.02f, 0.015f); Fill(new Rect(num - 7f, num2 - 2f, 15f, 5f), color); Fill(new Rect(num - 2f, num2 - 7f, 5f, 15f), color); Fill(new Rect(num - 6f, num2 - 1f, 13f, 3f), Paper); Fill(new Rect(num - 1f, num2 - 6f, 3f, 13f), Paper); Fill(new Rect(num - 1f, num2 - 1f, 3f, 3f), Gold); } } } private void ChangeGuest(int direction) { guestChoice = (guestChoice + direction + 5) % 5; ((BaseUnityPlugin)this).Config.Bind<int>("LAN", "GuestCharacter", 0, "Guest run character: 0 Cultist, 1 Pilot, 2 Marine, 3 Convict, 4 Hunter.").Value = guestChoice; if (Session != null) { Session.ChooseGuest(guestChoice); } } private void Awake() { //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Expected O, but got Unknown Instance = this; clientMovementPrediction = ((BaseUnityPlugin)this).Config.Bind<bool>("Network", "ClientMovementPrediction", true, "Bounded visual prediction for normal guest walking. Disable to retain the fast authoritative player stream without speculative movement.").Value; Application.runInBackground = true; ProfilePolicy.SaveProfileMode value = ((BaseUnityPlugin)this).Config.Bind<ProfilePolicy.SaveProfileMode>("Profile", "SaveProfileMode", ProfilePolicy.SaveProfileMode.Auto, "Auto: manager profile uses separate LAN saves; direct Steam launch keeps normal saves. Normal: LAN disabled. LAN: always use LAN saves.").Value; UsesSeparateProfile = ProfilePolicy.UsesSeparateProfile(Environment.GetCommandLineArgs(), Paths.GameRootPath, Paths.BepInExRootPath, value); string[] commandLineArgs = Environment.GetCommandLineArgs(); foreach (string obj in commandLineArgs) { if (obj == "--gt-dungeon-smoke-host") { smokeRole = "host"; } if (obj == "--gt-dungeon-smoke-client") { smokeRole = "client"; } if (obj == "--gt-profile-check") { profileCheck = true; } } visible = UsesSeparateProfile && !profileCheck; if (UsesSeparateProfile && !IsSmoke) { string text = Path.Combine(Paths.GameRootPath, "BepInEx/GungeonTogetherLAN/dungeon-profile"); try { ProfileDisplayPolicy.Initialize(Application.persistentDataPath, text, Log); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("LAN display settings could not be initialized: " + ex.Message)); } Directory.CreateDirectory(text); SaveManager.SavePath = text; SaveManager.OldSavePath = text; } if (IsSmoke) { GameManager.AUDIO_ENABLED = false; AudioListener.pause = true; AudioListener.volume = 0f; string text2 = Path.Combine(Paths.GameRootPath, "BepInEx/GungeonTogetherLAN/dungeon-profile"); Directory.CreateDirectory(text2); SaveManager.SavePath = text2; SaveManager.OldSavePath = text2; desktopSmoke = ((Component)this).gameObject.AddComponent<DesktopSmoke>(); } if (smokeRole != null) { Foyer.DoIntroSequence = false; Foyer.DoMainMenu = false; visible = false; smokeDeadline = Time.realtimeSinceStartup + 150f; } harmony = new Harmony("com.gungeontogether.lan.dungeon-0.3.7"); try { harmony.PatchAll(typeof(DungeonPlugin).Assembly); } catch (Exception ex2) { if (IsSmoke) { Log("GT_DUNGEON_SMOKE_FAIL: patch initialization: " + ex2); Application.Quit(); } throw; } ip = ((BaseUnityPlugin)this).Config.Bind<string>("LAN", "HostIP", "127.0.0.1", "Host's Radmin IPv4.").Value; bind = ((BaseUnityPlugin)this).Config.Bind<string>("LAN", "BindIP", "0.0.0.0", "Host's own Radmin IPv4 or all interfaces.").Value; port = ((BaseUnityPlugin)this).Config.Bind<int>("LAN", "Port", 27631, "TCP port.").Value.ToString(); guestChoice = ((BaseUnityPlugin)this).Config.Bind<int>("LAN", "GuestCharacter", 0, "Guest run character: 0 Cultist, 1 Pilot, 2 Marine, 3 Convict, 4 Hunter.").Value; if (!CharacterCatalog.ValidGuest(guestChoice)) { guestChoice = 0; } commandLineArgs = Environment.GetCommandLineArgs(); foreach (string text3 in commandLineArgs) { if (smokeRole != null && text3.StartsWith("--gt-smoke-character=", StringComparison.Ordinal) && int.TryParse(text3.Substring("--gt-smoke-character=".Length), out var result) && CharacterCatalog.ValidGuest(result)) { guestChoice = result; } if (smokeRole != null && text3.StartsWith("--gt-smoke-port=", StringComparison.Ordinal) && int.TryParse(text3.Substring("--gt-smoke-port=".Length), out var result2) && result2 >= 1024 && result2 <= 65535) { smokePort = result2; } } code = Guid.NewGuid().ToString("N").Substring(0, 8); fingerprint = GameFingerprint(); transport = new LanTransport(() => Time.realtimeSinceStartup); transport.PacketReceived += Receive; transport.ConnectedEvent += delegate { ClearAvatar(); foyerSequence = 0u; if (Session != null) { Session.Dispose(); } Session = new DungeonSession(transport, ((Component)this).gameObject, Log); Session.EnableMovementPrediction = clientMovementPrediction; if (!transport.IsHost) { Session.ChooseGuest(guestChoice); } Log("GT_DUNGEON_CONNECTED"); }; transport.DisconnectedEvent += delegate { ClearAvatar(); if (Session != null) { Session.Disconnected(); } Log(transport.Status); }; Log("GT_DUNGEON_READY 0.3.7; buffered rendering, character selection; game=" + fingerprint); Log("GT_PROFILE_MODE " + (UsesSeparateProfile ? "LAN" : "NORMAL")); RuntimeDisplayRestore.Attach(((Component)this).gameObject, Log); } private static string GameFingerprint() { string path = Path.Combine(Paths.GameRootPath, "EtG_Data/Managed"); using MemoryStream memoryStream = new MemoryStream(); using SHA256 sHA = SHA256.Create(); string[] array = new string[3] { "Assembly-CSharp.dll", "Assembly-CSharp-firstpass.dll", "UnityEngine.CoreModule.dll" }; foreach (string path2 in array) { using FileStream inputStream = File.OpenRead(Path.Combine(path, path2)); byte[] array2 = sHA.ComputeHash(inputStream); memoryStream.Write(array2, 0, array2.Length); } return BitConverter.ToString(sHA.ComputeHash(memoryStream.ToArray())).Replace("-", "").ToLowerInvariant(); } private void Update() { //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0175: 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) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0212: 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_023a: Unknown result type (might be due to invalid IL or missing references) if (transport == null) { return; } if (Input.GetKeyDown((KeyCode)112) && (Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305))) { visible = !visible; } if (visible && Input.GetKeyDown((KeyCode)27)) { visible = false; } UpdateMenuInput(); transport.Poll(); if (Session != null) { Session.MuteClientInput = visible; Session.Update(); } transport.FlushPending(); if (Session != null && Session.Phase == DungeonSession.RunPhase.Fault && smokeRole == null) { visible = true; } if (profileCheck) { TickProfileCheck(); return; } if (smokeRole != null) { TickSmoke(); } if (Session != null && Session.InRun) { return; } if (transport.Connected && Time.realtimeSinceStartup >= nextPosition) { nextPosition = Time.realtimeSinceStartup + 0.05f; GameManager val = Object.FindObjectOfType<GameManager>(); if ((Object)(object)val != (Object)null && val.IsFoyer && (Object)(object)val.PrimaryPlayer != (Object)null && (Object)(object)((BraveBehaviour)val.PrimaryPlayer).sprite != (Object)null) { PlayerController primaryPlayer = val.PrimaryPlayer; Vector3 position = ((BraveBehaviour)((BraveBehaviour)primaryPlayer).sprite).transform.position; Vector3 lossyScale = ((BraveBehaviour)((BraveBehaviour)primaryPlayer).sprite).transform.lossyScale; transport.SendLatest(new LanPlayerState { InFoyer = true, Sequence = ++foyerSequence, SentAt = Time.realtimeSinceStartup, X = position.x, Y = position.y, Z = position.z, Height = ((BraveBehaviour)primaryPlayer).sprite.HeightOffGround, ScaleX = lossyScale.x * ((BraveBehaviour)primaryPlayer).sprite.scale.x, ScaleY = lossyScale.y * ((BraveBehaviour)primaryPlayer).sprite.scale.y, Rotation = ((BraveBehaviour)((BraveBehaviour)primaryPlayer).sprite).transform.eulerAngles.z, SpriteId = ((BraveBehaviour)primaryPlayer).sprite.spriteId, FlipX = ((BraveBehaviour)primaryPlayer).sprite.FlipX, Collection = ((Object)((BraveBehaviour)primaryPlayer).sprite.Collection).name, CollectionGuid = (((BraveBehaviour)primaryPlayer).sprite.Collection.spriteCollectionGUID ?? ""), Character = CharacterCatalog.FromIdentity(((object)Unsafe.As<PlayableCharacters, PlayableCharacters>(ref primaryPlayer.characterIdentity)/*cast due to .constrained prefix*/).ToString()) }.Encode()); } } transport.FlushPending(); } private void Receive(byte[] bytes) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) if (bytes.Length == 0) { return; } if (bytes[0] == 1) { if (Session != null && Session.InRun) { return; } LanPlayerState lanPlayerState = LanPlayerState.Decode(bytes); GameManager val = Object.FindObjectOfType<GameManager>(); if (lanPlayerState.InFoyer && !((Object)(object)val == (Object)null) && val.IsFoyer && !((Object)(object)val.PrimaryPlayer == (Object)null)) { if ((Object)(object)foyerAvatar == (Object)null) { foyerAvatar = RemotePlayerAvatar.Create((ulong)(transport.IsHost ? 2 : 1), new Vector2(lanPlayerState.X, lanPlayerState.Y), lanPlayerState.Rotation); } foyerAvatar.Apply(lanPlayerState); } } else if (Session != null) { if (bytes[0] == 3) { ClearAvatar(); visible = false; } Session.Receive(bytes); } } private void ClearAvatar() { if ((Object)(object)foyerAvatar != (Object)null) { Object.Destroy((Object)(object)((Component)foyerAvatar).gameObject); } foyerAvatar = null; } private void LateUpdate() { if (Session != null) { Session.LateUpdate(); } } private void UpdateMenuInput() { PlayerController val = (GameManager.HasInstance ? GameManager.Instance.PrimaryPlayer : null); if ((Object)(object)menuPlayer != (Object)null && ((Object)(object)menuPlayer != (Object)(object)val || !visible || !UsesSeparateProfile)) { menuPlayer.ClearInputOverride("GT_Menu"); } menuPlayer = val; if ((Object)(object)menuPlayer != (Object)null && visible && UsesSeparateProfile && !IsSmoke) { menuPlayer.SetInputOverride("GT_Menu"); } } private void Connect(bool host) { try { if (!UsesSeparateProfile) { throw new IOException("Start LAN using the updated GungeonTogether-LAN.exe; normal save profile is active."); } if (!int.TryParse(port, out var result) || result < 1 || result > 65535) { throw new IOException("Invalid TCP port."); } GameManager val = Object.FindObjectOfType<GameManager>(); if ((Object)(object)val == (Object)null || !val.IsFoyer || (Object)(object)val.PrimaryPlayer == (Object)null) { throw new IOException("Choose a character in the foyer before connecting."); } if (host) { transport.Host(bind.Trim(), result, code, fingerprint); } else { transport.Join(ip.Trim(), result, code, fingerprint); } error = ""; } catch (Exception ex) { error = ex.Message; } } private void ReturnToFoyer() { bool num = Session != null && Session.InRun; if (Session != null) { Session.Dispose(); Session = null; } transport.Dispose(); ClearAvatar(); error = ""; if (num && GameManager.HasInstance) { GameManager.Instance.LoadCharacterSelect(false, true); } } private void Log(string text) { ((BaseUnityPlugin)this).Logger.LogInfo((object)text); if (smokeRole != null) { File.AppendAllText(Path.Combine(Paths.GameRootPath, "dungeon-smoke-" + smokeRole + ".txt"), text + "\n"); } } internal void ProfileInitialized() { if (profileCheck && !profilePathChecked) { profilePathChecked = true; profileObservedPath = Path.GetFullPath(SaveManager.SavePath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); string path = (UsesSeparateProfile ? Path.Combine(Paths.GameRootPath, "BepInEx/GungeonTogetherLAN/dungeon-profile") : Application.persistentDataPath); path = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); bool flag = string.Equals(profileObservedPath, path, StringComparison.OrdinalIgnoreCase); string text = Path.Combine(Paths.GameRootPath, "BepInEx/GungeonTogetherLAN/profile-check-fixture"); Directory.CreateDirectory(text); SaveManager.SavePath = text; SaveManager.OldSavePath = text; string text2 = "GT_PROFILE_PATH_" + (flag ? "PASS" : "FAIL") + " mode=" + (UsesSeparateProfile ? "LAN" : "NORMAL"); File.WriteAllText(Path.Combine(Paths.GameRootPath, "profile-check-result.txt"), text2 + "\n"); ((BaseUnityPlugin)this).Logger.LogInfo((object)text2); } } private void TickProfileCheck() { if (profilePathChecked && GameManager.HasInstance && !GameManager.Instance.IsLoadingLevel) { string path = Path.Combine(Paths.GameRootPath, "profile-check-result.txt"); if (File.ReadAllText(path).IndexOf("GT_PROFILE_GAME_READY", StringComparison.Ordinal) < 0) { GameStatsManager val = default(GameStatsManager); bool flag = SaveManager.Load<GameStatsManager>(SaveManager.GameSave, ref val, true, 0u, (Func<string, uint, string>)null, (SaveSlot?)null); File.AppendAllText(path, "GT_PROFILE_SAVE_LOAD_" + ((flag && val != null) ? "PASS" : "FAIL") + "\n"); File.AppendAllText(path, "GT_PROFILE_GAME_READY\n"); ((BaseUnityPlugin)this).Logger.LogInfo((object)"GT_PROFILE_GAME_READY"); } } } private void TickSmoke() { //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: 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_0615: Unknown result type (might be due to invalid IL or missing references) //IL_061a: Unknown result type (might be due to invalid IL or missing references) //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_041b: Unknown result type (might be due to invalid IL or missing references) //IL_08ca: Unknown result type (might be due to invalid IL or missing references) //IL_08d6: Unknown result type (might be due to invalid IL or missing references) //IL_07bd: Unknown result type (might be due to invalid IL or missing references) //IL_07c2: Unknown result type (might be due to invalid IL or missing references) //IL_07cc: 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_07d6: Unknown result type (might be due to invalid IL or missing references) //IL_07db: Unknown result type (might be due to invalid IL or missing references) //IL_0a1f: Unknown result type (might be due to invalid IL or missing references) //IL_0a24: Unknown result type (might be due to invalid IL or missing references) //IL_0a41: Unknown result type (might be due to invalid IL or missing references) //IL_0a46: Unknown result type (might be due to invalid IL or missing references) if (smokeComplete) { return; } try { if (Time.realtimeSinceStartup > smokeDeadline) { throw new IOException("Smoke timed out: " + ((Session == null) ? transport.Status : Session.Status)); } GameManager val = Object.FindObjectOfType<GameManager>(); if ((Object)(object)val == (Object)null || (Object)(object)val.Dungeon == (Object)null || Dungeon.IsGenerating || val.IsLoadingLevel) { return; } if ((Object)(object)desktopSmoke != (Object)null && desktopSmoke.Verified && !smokeDesktopPassed) { smokeDesktopPassed = true; Log("GT_DESKTOP_SAFE_PASS fullscreen=" + Screen.fullScreen + " size=" + Screen.width + "x" + Screen.height + " cursor=" + ((object)Cursor.lockState/*cast due to .constrained prefix*/).ToString() + " background=" + Application.runInBackground); } if (Time.realtimeSinceStartup >= nextSmokeDiagnostic) { nextSmokeDiagnostic = Time.realtimeSinceStartup + 5f; Log("GT_SMOKE_STATE foyer=" + val.IsFoyer + " loading=" + val.IsLoadingLevel + " generating=" + Dungeon.IsGenerating + " player=" + ((Object)(object)val.PrimaryPlayer != (Object)null) + " scene=" + ((val.GetLastLoadedLevelDefinition() == null) ? "null" : val.GetLastLoadedLevelDefinition().dungeonSceneName)); } if (!InputManager.IsSetup) { typeof(InputManager).GetMethod("SetupInternal", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null); } if (val.IsFoyer && (Object)(object)val.PrimaryPlayer == (Object)null) { GameObject val2 = (GameManager.PlayerPrefabForNewGame = NativeWorld.CharacterPrefab((smokeRole == "host") ? 1 : 4)); RoomHandler val4 = val.Dungeon.data.rooms[0]; Vector3 val5 = default(Vector3); ((Vector3)(ref val5))..ctor((float)val4.area.basePosition.x + (float)val4.area.dimensions.x / 2f, (float)val4.area.basePosition.y + (float)val4.area.dimensions.y / 2f, 0f); val.PrimaryPlayer = Object.Instantiate<GameObject>(val2, val5, Quaternion.identity).GetComponent<PlayerController>(); val.PrimaryPlayer.PlayerIDX = 0; val.PrimaryPlayer.ForceChangeRoom(val4); } if (!transport.Active && Session == null) { if (smokeRole == "host") { transport.Host("127.0.0.1", smokePort, "dungeon-smoke", fingerprint); } else { transport.Join("127.0.0.1", smokePort, "dungeon-smoke", fingerprint); } Log("GT_DUNGEON_SMOKE_CONNECTING"); } if (transport.Connected && val.IsFoyer && Session != null && !Session.InRun) { if (smokeLobbyAt == 0f) { smokeLobbyAt = Time.realtimeSinceStartup; } float num = Time.realtimeSinceStartup - smokeLobbyAt; val.PrimaryPlayer.usingForcedInput = num > 0.3f; val.PrimaryPlayer.forcedInput = new Vector2((float)((Mathf.Sin(num * 4f) >= 0f) ? 1 : (-1)), 0f); if (num > 1.7f && !smokeFoyerPassed) { if ((Object)(object)foyerAvatar != (Object)null && foyerAvatar.RenderedFrames >= 10 && foyerAvatar.DistanceMoved >= 0.2f) { smokeFoyerPassed = true; Log("GT_FOYER_INTERPOLATION_PASS frames=" + foyerAvatar.RenderedFrames + " distance=" + foyerAvatar.DistanceMoved + " collection=" + foyerAvatar.CollectionName); } else if (num > 15f) { throw new IOException("Foyer interpolation did not render remote movement."); } } if (num > 1.8f && !smokeMenuRequested) { smokeMenuRequested = true; ((MonoBehaviour)this).StartCoroutine(CaptureSmokeMenu()); } } if (smokeRole == "host" && transport.Connected && Session != null && Session.GuestChoiceReceived && smokeFoyerPassed && Time.realtimeSinceStartup - smokeLobbyAt > 3f && !smokeRunStarted) { val.PrimaryPlayer.usingForcedInput = false; smokeRunStarted = true; Session.StartHost(); Log("GT_DUNGEON_SMOKE_RUN_STARTED"); } if (Session != null && Session.Phase == DungeonSession.RunPhase.Fault) { throw new IOException(Session.Status); } if (Session != null && Session.Phase == DungeonSession.RunPhase.Running && !smokeLayout) { smokeLayout = true; smokeStarted = Time.realtimeSinceStartup; Log("GT_DUNGEON_SMOKE_LAYOUT_PASS"); if (smokeRole == "host") { smokeCultistStart = ((BraveBehaviour)val.SecondaryPlayer).transform.position; smokeStartAmmo = ((!((Object)(object)((GameActor)val.SecondaryPlayer).CurrentGun == (Object)null)) ? ((GameActor)val.SecondaryPlayer).CurrentGun.CurrentAmmo : 0); } VerifySmokeCharacters(val); } if (smokeRole == "client" && Session != null && Session.Snapshot != null && !smokeSnapshot) { smokeSnapshot = true; Log("GT_DUNGEON_SMOKE_SNAPSHOT_PASS sprites=" + Session.Snapshot.Sprites.Count); } if (smokeLayout && smokeRole == "client") { float num2 = Time.realtimeSinceStartup - smokeStarted; Session.SmokeControls = Session.Epoch == 1; Session.SmokeMoveX = ((num2 > 2f && num2 < 2.4f) ? 1 : 0); Session.SmokeShoot = num2 > 3f && num2 < 12f && (int)(num2 * 6f) % 2 == 0; } if (smokeLayout && smokeRole == "host" && Session.Epoch == 1 && !smokeEnemyCreated && Time.realtimeSinceStartup - smokeStarted > 3f) { smokeEnemyCreated = true; AIActor orLoadByGuid = EnemyDatabase.GetOrLoadByGuid("01972dee89fc4404a5c408d50007dad5"); PlayerController secondaryPlayer = val.SecondaryPlayer; Vector2 val6 = ((BraveBehaviour)secondaryPlayer).specRigidbody.UnitCenter + Vector2.right * 2.5f; smokeEnemy = AIActor.Spawn(orLoadByGuid, val6, secondaryPlayer.CurrentRoom, true, (AwakenAnimationType)0, true); ((Behaviour)((BraveBehaviour)smokeEnemy).behaviorSpeculator).enabled = false; smokeEnemyHealth = ((BraveBehaviour)smokeEnemy).healthHaver.GetCurrentHealth(); Log("GT_DUNGEON_SMOKE_ENEMY_SPAWN health=" + smokeEnemyHealth + " guid=" + smokeEnemy.EnemyGuid); } if (smokeEnemyCreated && !smokeDamagePassed && ((Object)(object)smokeEnemy == (Object)null || ((BraveBehaviour)smokeEnemy).healthHaver.GetCurrentHealth() < smokeEnemyHealth)) { smokeDamagePassed = true; Log("GT_DUNGEON_SMOKE_DAMAGE_PASS: native remote-player projectile damaged enemy"); } if (smokeLayout && smokeRole == "host" && Time.realtimeSinceStartup - smokeStarted > 3f) { PlayerController secondaryPlayer2 = val.SecondaryPlayer; float num3 = Vector3.Distance(smokeCultistStart, ((BraveBehaviour)secondaryPlayer2).transform.position); if (num3 > 0.2f && !smokeMovePassed) { smokeMovePassed = true; Log("GT_DUNGEON_SMOKE_REMOTE_MOVE_PASS distance=" + num3); } Projectile[] array = Object.FindObjectsOfType<Projectile>(); for (int i = 0; i < array.Length; i++) { if ((Object)(object)array[i].Owner == (Object)(object)secondaryPlayer2 && !smokeShotPassed) { smokeShotPassed = true; Log("GT_DUNGEON_SMOKE_REMOTE_SHOOT_PASS native projectile owned by selected guest"); } } if ((!smokeMovePassed || !smokeShotPassed) && Time.realtimeSinceStartup >= nextControlsDiagnostic) { nextControlsDiagnostic = Time.realtimeSinceStartup + 1f; Log("GT_SMOKE_CONTROLS move=" + num3 + " ammo=" + (((Object)(object)((GameActor)secondaryPlayer2).CurrentGun == (Object)null) ? (-1) : ((GameActor)secondaryPlayer2).CurrentGun.CurrentAmmo) + " activeShoot=" + ((OneAxisInputControl)Session.Controls.Actions.ShootAction).Value + " activeMove=" + ((object)((TwoAxisInputControl)Session.Controls.Actions.Move).Vector/*cast due to .constrained prefix*/).ToString() + " input=" + ((object)secondaryPlayer2.CurrentInputState/*cast due to .constrained prefix*/).ToString() + " override=" + secondaryPlayer2.IsInputOverridden + " paused=" + val.IsPaused + " delta=" + BraveTime.DeltaTime + " packetSeq=" + Session.Controls.LastInputSequence + " packetMove=" + Session.Controls.LastMoveX + " attached=" + Session.Controls.Device.IsAttached); } } if (smokeLayout && !smokeCaptureRequested && Time.realtimeSinceStartup - smokeStarted > 9f) { smokeCaptureRequested = true; ScreenCapture.CaptureScreenshot(Path.Combine(Paths.GameRootPath, "dungeon-smoke-" + smokeRole + ".png")); } if (smokeLayout && smokeRole == "host" && !smokeSecondFloor && Time.realtimeSinceStartup - smokeStarted > 13f) { if (!smokeMovePassed || !smokeShotPassed || !smokeDamagePassed) { throw new IOException("Native remote move/fire/damage test failed."); } smokeSecondFloor = true; Log("GT_DUNGEON_SMOKE_TRANSITION_STARTED"); val.LoadCustomLevel("tt5"); } if (!smokeLayout || Session == null || Session.Epoch < 2 || Session.Phase != DungeonSession.RunPhase.Running || (!(smokeRole == "host") && (Session.Snapshot == null || Session.Snapshot.Epoch < 2))) { return; } if (smokeSecondFloorAt == 0f) { smokeSecondFloorAt = Time.realtimeSinceStartup; Log("GT_DUNGEON_SMOKE_SECOND_FLOOR_SNAPSHOT_READY"); } if (!(Time.realtimeSinceStartup - smokeSecondFloorAt < 3f)) { VerifySmokeCharacters(val); if (!smokeDesktopPassed) { throw new IOException("Desktop-safe window verification did not pass."); } if (smokeRole == "client" && Session.InterpolatedFrames < 10) { throw new IOException("Dungeon render interpolation did not execute."); } if (smokeRole == "client") { Log("GT_DUNGEON_INTERPOLATION_PASS renderFrames=" + Session.RenderedFrames + " interpolated=" + Session.InterpolatedFrames); } Log("GT_DUNGEON_SMOKE_SECOND_FLOOR_PASS"); Log("GT_DUNGEON_SMOKE_PASS"); smokeComplete = true; smokeDeadline = float.MaxValue; } } catch (Exception ex) { Log("GT_DUNGEON_SMOKE_FAIL: " + ex); Application.Quit(); smokeDeadline = float.MaxValue; } } private void VerifySmokeCharacters(GameManager gm) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (gm.AllPlayers.Length != 2 || (Object)(object)gm.SecondaryPlayer == (Object)null || CharacterCatalog.FromIdentity(((object)Unsafe.As<PlayableCharacters, PlayableCharacters>(ref gm.SecondaryPlayer.characterIdentity)/*cast due to .constrained prefix*/).ToString()) != Session.GuestCharacter) { throw new IOException("Selected guest character was not retained across generation."); } if ((int)gm.PrimaryPlayer.characterIdentity != 0) { throw new IOException("Host character identity differs between PCs."); } if ((Object)(object)((GameActor)gm.SecondaryPlayer).CurrentGun == (Object)null) { throw new IOException("Selected guest has no starting gun."); } Log("GT_CHARACTER_PASS epoch=" + Session.Epoch + " identity=" + ((object)Unsafe.As<PlayableCharacters, PlayableCharacters>(ref gm.SecondaryPlayer.characterIdentity)/*cast due to .constrained prefix*/).ToString() + " gun=" + ((PickupObject)((GameActor)gm.SecondaryPlayer).CurrentGun).PickupObjectId); } private IEnumerator CaptureSmokeMenu() { hostTab = smokeRole == "host"; visible = true; smokeMenuVisible = true; yield return (object)new WaitForEndOfFrame(); ScreenCapture.CaptureScreenshot(Path.Combine(Paths.GameRootPath, "menu-smoke-" + smokeRole + ".png")); yield return (object)new WaitForEndOfFrame(); visible = false; smokeMenuVisible = false; Log("GT_MENU_CAPTURED"); } private void OnDestroy() { if ((Object)(object)menuPlayer != (Object)null) { menuPlayer.ClearInputOverride("GT_Menu"); } if (Session != null) { Session.Dispose(); } if (transport != null) { transport.Dispose(); } if (harmony != null) { harmony.UnpatchSelf(); } Instance = null; ClearAvatar(); } } public enum DungeonMessage : byte { Input = 2, Level, Ready, Snapshot, Collection, Stop, Character, Audio, Motion, Equipment, Secrets, SceneEffects } public sealed class RemoteInput { public const float DefaultViewAspect = 1.7777778f; public const float MinViewAspect = 0.25f; public const float MaxViewAspect = 8f; public uint Sequence; public int Epoch; public float MoveX; public float MoveY; public float AimX; public float AimY; public float ViewAspect = 1.7777778f; public ulong Buttons; public byte[] Encode() { return Wire.Write(DungeonMessage.Input, delegate(BinaryWriter w) { w.Write(Epoch); w.Write(Sequence); w.Write(MoveX); w.Write(MoveY); w.Write(AimX); w.Write(AimY); w.Write(Buttons); w.Write(ViewAspect); }); } public static RemoteInput Decode(byte[] bytes) { return Wire.Read(bytes, DungeonMessage.Input, delegate(BinaryReader r) { RemoteInput remoteInput = new RemoteInput { Epoch = r.ReadInt32(), Sequence = r.ReadUInt32(), MoveX = r.ReadSingle(), MoveY = r.ReadSingle(), AimX = r.ReadSingle(), AimY = r.ReadSingle(), Buttons = r.ReadUInt64(), ViewAspect = r.ReadSingle() }; Wire.Range(remoteInput.MoveX, -1f, 1f); Wire.Range(remoteInput.MoveY, -1f, 1f); Wire.Range(remoteInput.AimX, -1f, 1f); Wire.Range(remoteInput.AimY, -1f, 1f); Wire.Range(remoteInput.ViewAspect, 0.25f, 8f); if (remoteInput.Buttons >> 41 != 0L || remoteInput.Epoch < 1) { throw new IOException("Invalid remote controls."); } return remoteInput; }); } } public sealed class LevelCommand { public int Epoch; public int Seed; public int HostCharacter = 1; public int GuestCharacter; public string Scene = "tt_castle"; public string LayoutHash = ""; public string GenerationData = ""; public byte[] Encode() { return Wire.Write(DungeonMessage.Level, delegate(BinaryWriter w) { w.Write(Epoch); w.Write(Seed); w.Write(Scene); w.Write(LayoutHash); w.Write(GenerationData); w.Write(HostCharacter); w.Write(GuestCharacter); }); } public static LevelCommand Decode(byte[] bytes) { return Wire.Read(bytes, DungeonMessage.Level, delegate(BinaryReader r) { LevelCommand levelCommand = new LevelCommand { Epoch = r.ReadInt32(), Seed = r.ReadInt32(), Scene = Wire.String(r, 128), LayoutHash = Wire.String(r, 64), GenerationData = Wire.String(r, 131072) }; levelCommand.HostCharacter = r.ReadInt32(); levelCommand.GuestCharacter = r.ReadInt32(); if (!CharacterCatalog.Valid(levelCommand.HostCharacter) || !CharacterCatalog.ValidGuest(levelCommand.GuestCharacter)) { throw new IOException("Invalid run characters."); } if (levelCommand.Epoch < 1 || levelCommand.Seed <= 0 || !ValidScene(levelCommand.Scene) || levelCommand.LayoutHash.Length != 64) { throw new IOException("Invalid dungeon command."); } return levelCommand; }); } public static bool ValidScene(string scene) { if (string.IsNullOrEmpty(scene) || scene.Length > 128 || (!scene.StartsWith("tt", StringComparison.Ordinal) && !scene.StartsWith("ss_", StringComparison.Ordinal) && !scene.StartsWith("fs_", StringComparison.Ordinal))) { return false; } foreach (char c in scene) { if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') && c != '_') { return false; } } return true; } } public sealed class CollectionInfo { public int Epoch; public int Id; public string Guid = ""; public string Name = ""; public string Asset = ""; public string EnemyGuid = ""; public byte[] Encode() { return Wire.Write(DungeonMessage.Collection, delegate(BinaryWriter w) { w.Write(Epoch); w.Write(Id); w.Write(Guid); w.Write(Name); w.Write(Asset); w.Write(EnemyGuid); }); } public static CollectionInfo Decode(byte[] bytes) { return Wire.Read(bytes, DungeonMessage.Collection, delegate(BinaryReader r) { CollectionInfo collectionInfo = new CollectionInfo { Epoch = r.ReadInt32(), Id = r.ReadInt32(), Guid = Wire.String(r, 256), Name = Wire.String(r, 256), Asset = Wire.String(r, 256), EnemyGuid = Wire.String(r, 32) }; if (collectionInfo.Id < 1 || collectionInfo.Id > 4096 || collectionInfo.Epoch < 1) { throw new IOException("Invalid sprite collection ID."); } if (!EnemyAssetIdentity.Valid(collectionInfo.EnemyGuid)) { throw new IOException("Invalid native enemy asset identifier."); } return collectionInfo; }); } } public sealed class SpriteState { public int Id; public int CollectionId; public int SpriteId; public int SortingOrder; public float X; public float Y; public float Z; public float Rotation; public float ScaleX; public float ScaleY; public float ScaleZ; public float Height; public uint Color; public uint OverrideColor; public byte Flags; public byte Layer; public byte Kind; public byte Anchor; public float Width; public float HeightPixels; public float ClipX; public float ClipY; public float ClipRight = 1f; public float ClipTop = 1f; public void Write(BinaryWriter w) { w.Write(Id); w.Write(CollectionId); w.Write(SpriteId); w.Write(SortingOrder); w.Write(X); w.Write(Y); w.Write(Z); w.Write(Rotation); w.Write(ScaleX); w.Write(ScaleY); w.Write(ScaleZ); w.Write(Height); w.Write(Color); w.Write(OverrideColor); w.Write(Flags); w.Write(Layer); w.Write(Kind); w.Write(Anchor); w.Write(Width); w.Write(HeightPixels); w.Write(ClipX); w.Write(ClipY); w.Write(ClipRight); w.Write(ClipTop); } public static SpriteState Read(BinaryReader r) { SpriteState spriteState = new SpriteState { Id = r.ReadInt32(), CollectionId = r.ReadInt32(), SpriteId = r.ReadInt32(), SortingOrder = r.ReadInt32(), X = r.ReadSingle(), Y = r.ReadSingle(), Z = r.ReadSingle(), Rotation = r.ReadSingle(), ScaleX = r.ReadSingle(), ScaleY = r.ReadSingle(), ScaleZ = r.ReadSingle(), Height = r.ReadSingle(), Color = r.ReadUInt32(), OverrideColor = r.ReadUInt32(), Flags = r.ReadByte(), Layer = r.ReadByte() }; spriteState.Kind = r.ReadByte(); spriteState.Anchor = r.ReadByte(); spriteState.Width = r.ReadSingle(); spriteState.HeightPixels = r.ReadSingle(); spriteState.ClipX = r.ReadSingle(); spriteState.ClipY = r.ReadSingle(); spriteState.ClipRight = r.ReadSingle(); spriteState.ClipTop = r.ReadSingle(); if (spriteState.Id == 0 || spriteState.CollectionId < 1 || spriteState.CollectionId > 4096 || spriteState.SpriteId < 0 || spriteState.SpriteId > 100000 || (spriteState.Flags & -32) != 0 || spriteState.Layer > 31) { throw new IOException("Invalid sprite snapshot."); } Wire.Range(spriteState.X, -100000f, 100000f); Wire.Range(spriteState.Y, -100000f, 100000f); Wire.Range(spriteState.Z, -100000f, 100000f); Wire.Range(spriteState.Rotation, -3600f, 3600f); Wire.Range(spriteState.ScaleX, -1000f, 1000f); Wire.Range(spriteState.ScaleY, -1000f, 1000f); Wire.Range(spriteState.ScaleZ, -1000f, 1000f); Wire.Range(spriteState.Height, -1000f, 1000f); if (spriteState.Kind > 3 || spriteState.Anchor > 8) { throw new IOException("Unknown sprite geometry."); } Wire.Range(spriteState.Width, 0f, 100000f); Wire.Range(spriteState.HeightPixels, 0f, 100000f); Wire.Range(spriteState.ClipX, -1000f, 1000f); Wire.Range(spriteState.ClipY, -1000f, 1000f); Wire.Range(spriteState.ClipRight, -1000f, 1000f); Wire.Range(spriteState.ClipTop, -1000f, 1000f); return spriteState; } } public sealed class WorldSnapshot { public const int MaxHudCounter = 1000000; public const int MaxHudNameLength = 96; public int Epoch; public uint Sequence; public double SentAt; public uint InputSequence; public float CameraX; public float CameraY; public float CameraZ; public float CameraSize; public float PlayerX; public float PlayerY; public float Health; public float Armor; public int Ammo; public int Currency; public int Keys; public int Blanks; public float MaxHealth; public int GunId = -1; public int ItemId = -1; public int ClipAmmo; public int ClipCapacity; public bool Reloading; public string GunName = ""; public string ActiveItemName = ""; public float ActiveItemCooldown; public bool Paused; public bool InfiniteAmmo; public bool InfiniteKeys; public bool ActiveItemReady; public bool ActiveItemActive; public bool IsGhost; public bool IsDead; public bool GameOver; public readonly List<SpriteState> Sprites = new List<SpriteState>(); public byte[] Encode() { ValidateHud(); if (Sprites.Count > 4096) { throw new IOException("Too many replicated sprites."); } return Wire.Write(DungeonMessage.Snapshot, delegate(BinaryWriter w) { w.Write(Epoch); w.Write(Sequence); w.Write(SentAt); w.Write(InputSequence); w.Write(CameraX); w.Write(CameraY); w.Write(CameraZ); w.Write(CameraSize); w.Write(PlayerX); w.Write(PlayerY); w.Write(Health); w.Write(Armor); w.Write(Ammo); w.Write(Paused); w.Write(Currency); w.Write(Keys); w.Write(Blanks); w.Write(GunName); w.Write(ActiveItemName); w.Write(ActiveItemCooldown); w.Write(InfiniteAmmo); w.Write(InfiniteKeys); w.Write(ActiveItemReady); w.Write(ActiveItemActive); w.Write(IsGhost); w.Write(IsDead); w.Write(GameOver); w.Write(MaxHealth); w.Write(GunId); w.Write(ItemId); w.Write(ClipAmmo); w.Write(ClipCapacity); w.Write(Reloading); w.Write(Sprites.Count); foreach (SpriteState sprite in Sprites) { sprite.Write(w); } }); } public static WorldSnapshot Decode(byte[] bytes) { return Wire.Read(bytes, DungeonMessage.Snapshot, delegate(BinaryReader r) { WorldSnapshot worldSnapshot = new WorldSnapshot { Epoch = r.ReadInt32(), Sequence = r.ReadUInt32(), SentAt = r.ReadDouble(), InputSequence = r.ReadUInt32(), CameraX = r.ReadSingle(), CameraY = r.ReadSingle(), CameraZ = r.ReadSingle(), CameraSize = r.ReadSingle(), PlayerX = r.ReadSingle(), PlayerY = r.ReadSingle(), Health = r.ReadSingle(), Armor = r.ReadSingle(), Ammo = r.ReadInt32(), Paused = r.ReadBoolean() }; worldSnapshot.Currency = r.ReadInt32(); worldSnapshot.Keys = r.ReadInt32(); worldSnapshot.Blanks = r.ReadInt32(); worldSnapshot.GunName = Wire.String(r, 96); worldSnapshot.ActiveItemName = Wire.String(r, 96); worldSnapshot.ActiveItemCooldown = r.ReadSingle(); worldSnapshot.InfiniteAmmo = r.ReadBoolean(); worldSnapshot.InfiniteKeys = r.ReadBoolean(); worldSnapshot.ActiveItemReady = r.ReadBoolean(); worldSnapshot.ActiveItemActive = r.ReadBoolean(); worldSnapshot.IsGhost = r.ReadBoolean(); worldSnapshot.IsDead = r.ReadBoolean(); worldSnapshot.GameOver = r.ReadBoolean(); worldSnapshot.MaxHealth = r.ReadSingle(); worldSnapshot.GunId = r.ReadInt32(); worldSnapshot.ItemId = r.ReadInt32(); worldSnapshot.ClipAmmo = r.ReadInt32(); worldSnapshot.ClipCapacity = r.ReadInt32(); worldSnapshot.Reloading = r.ReadBoolean(); worldSnapshot.ValidateHud(); Wire.Time(worldSnapshot.SentAt); Wire.Range(worldSnapshot.CameraX, -100000f, 100000f); Wire.Range(worldSnapshot.CameraY, -100000f, 100000f); Wire.Range(worldSnapshot.CameraZ, -100000f, 100000f); Wire.Range(worldSnapshot.CameraSize, 0.01f, 10000f); Wire.Range(worldSnapshot.PlayerX, -100000f, 100000f); Wire.Range(worldSnapshot.PlayerY, -100000f, 100000f); Wire.Range(worldSnapshot.Health, 0f, 10000f); Wire.Range(worldSnapshot.Armor, 0f, 10000f); int num = r.ReadInt32(); if (num < 0 || num > 4096 || worldSnapshot.Epoch < 1 || worldSnapshot.Ammo < 0) { throw new IOException("Invalid world snapshot size."); } HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < num; i++) { SpriteState spriteState = SpriteState.Read(r); if (!hashSet.Add(spriteState.Id)) { throw new IOException("Duplicate entity ID."); } worldSnapshot.Sprites.Add(spriteState); } return worldSnapshot; }); } private void ValidateHud() { Wire.Range(MaxHealth, 0f, 10000f); if (GunId < -1 || GunId > 1000000 || ItemId < -1 || ItemId > 1000000 || ClipAmmo < 0 || ClipAmmo > 1000000 || ClipCapacity < 0 || ClipCapacity > 1000000) { throw new IOException("Invalid guest equipment HUD data."); } if (Ammo < 0 || Ammo > 1000000 || Currency < 0 || Currency > 1000000 || Keys < 0 || Keys > 1000000 || Blanks < 0 || Blanks > 1000000) { throw new IOException("Invalid guest HUD counter."); } Wire.Range(ActiveItemCooldown, 0f, 1f); if (GunName == null || ActiveItemName == null || GunName.Length > 96 || ActiveItemName.Length > 96) { throw new IOException("Invalid guest HUD name length."); } string gunName = GunName; for (int i = 0; i < gunName.Length; i++) { if (char.IsControl(gunName[i])) { throw new IOException("Invalid guest gun name."); } } gunName = ActiveItemName; for (int i = 0; i < gunName.Length; i++) { if (char.IsControl(gunName[i])) { throw new IOException("Invalid guest active item name."); } } } public static string CleanHudName(string value) { if (string.IsNullOrEmpty(value)) { return ""; } int num = Math.Min(value.Length, 96); if (num < value.Length && char.IsHighSurrogate(value[num - 1])) { num--; } char[] array = value.Substring(0, num).ToCharArray(); for (int i = 0; i < array.Length; i++) { if (char.IsControl(array[i])) { array[i] = ' '; } } return new string(array); } } public static class Wire { public static byte[] Write(DungeonMessage type, Action<BinaryWriter> action) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write((byte)type); action(binaryWriter); return memoryStream.ToArray(); } public static T Read<T>(byte[] bytes, DungeonMessage type, Func<BinaryReader, T> action) { using MemoryStream memoryStream = new MemoryStream(bytes); using BinaryReader binaryReader = new BinaryReader(memoryStream); if ((uint)binaryReader.ReadByte() != (uint)type) { throw new IOException("Unexpected dungeon packet."); } T result = action(binaryReader); if (memoryStream.Position != memoryStream.Length) { throw new IOException("Trailing packet bytes."); } return result; } public static string String(BinaryReader r, int max) { string text = r.ReadString(); if (text.Length > max) { throw new IOException("String too long."); } return text; } public static void Range(float value, float min, float max) { if (float.IsNaN(value) || float.IsInfinity(value) || value < min || value > max) { throw new IOException("Invalid numeric snapshot value."); } } public static void Time(double value) { if (double.IsNaN(value) || double.IsInfinity(value) || value < 0.0 || value > 1000000000000.0) { throw new IOException("Invalid frame timestamp."); } } public static bool Newer(uint value, uint previous) { return (int)(value - previous) > 0; } } public sealed class DungeonSession : IDisposable { public enum RunPhase { Lobby, Generating, AwaitingClient, Running, Fault } private readonly LanTransport transport; private readonly GameObject timeOwner; private readonly Action<string> log; private SpriteReplication sprites = new SpriteReplication(); private RemoteControls controls; private string scene = "tt_castle"; private string hash = ""; private string generationContext; private string clientOriginalContext; private int epoch; private int seed; private uint inputSequence; private uint snapshotSequence; private uint lastSnapshot; private float deadline; private double nextInput; private double nextSnapshot; private double nextMotion; private uint motionSequence; private ulong previousButtons; public bool EnableMovementPrediction = true; private bool loadingCommand; private readonly InputAcknowledgements inputAcks = new InputAcknowledgements(); private readonly WorldPresentation presentation = new WorldPresentation(); public readonly NativeHud Hud = new NativeHud(); internal readonly ScenePresentation SceneEffects = new ScenePresentation(); private float nextNetworkLog; private int hostCharacter = 1; private GameObject clientOriginalPrefab; public bool MuteClientInput; public bool SmokeControls; public float SmokeMoveX; public bool SmokeShoot; public AudioReplication Audio { get; private set; } = new AudioReplication(); public double SmoothingMilliseconds => sprites.SmoothingMilliseconds; public InventoryState Equipment { get; private set; } internal InventoryBook Book { get; private set; } public int RevealedSecretRooms => presentation.AppliedSecretRooms; public string MovementBlockReason => SceneEffects.MovementBlock; public double InputLatencyMs => inputAcks.Milliseconds; public int GuestCharacter { get; private set; } public bool GuestChoiceReceived { get; private set; } public RunPhase Phase { get; private set; } public bool IsHost => transport.IsHost; public bool Mirroring { get { if (!IsHost) { return Phase == RunPhase.Running; } return false; } } public bool InRun => Phase != RunPhase.Lobby; public string Status { get; private set; } public int Epoch => epoch; public int Seed => seed; public RemoteControls Controls => controls; public WorldSnapshot Snapshot => sprites.LastSnapshot; public int RenderedFrames => sprites.RenderedFrames; public int InterpolatedFrames => sprites.InterpolatedFrames; public bool CanClientLoadLevel { get { if (!IsHost && InRun) { return loadingCommand; } return true; } } public DungeonSession(LanTransport transport, GameObject timeOwner, Action<string> log) { this.transport = transport; this.timeOwner = timeOwner; this.log = log; Phase = RunPhase.Lobby; Status = "Connected: host can start synchronized dungeon"; } public void ChooseGuest(int character) { if (!IsHost && Phase == RunPhase.Lobby && CharacterCatalog.ValidGuest(character)) { GuestCharacter = character; Send(new LobbyChoice { Character = character }.Encode()); } } public void StartHost() { if (!IsHost || !transport.Connected || Phase != RunPhase.Lobby) { throw new IOException("Connect two PCs in the foyer first."); } GameManager instance = GameManager.Instance; if (!instance.IsFoyer || (Object)(object)instance.PrimaryPlayer == (Object)null) { throw new IOException("Start from the foyer with a selected character."); } if (!GuestChoiceReceived) { throw new IOException("Wait for the friend's character selection."); } hostCharacter = CharacterCatalog.FromIdentity(((object)Unsafe.As<PlayableCharacters, PlayableCharacters>(ref instance.PrimaryPlayer.characterIdentity)/*cast due to .constrained prefix*/).ToString()); controls = new RemoteControls(); NativeWorld.EnsureGuest(GuestCharacter); controls.Bind(); controls.Enabled = false; generationContext = NativeWorld.ExportGenerationContext(); epoch = 1; seed = Random.Range(1, 999999999); scene = "tt_castle"; if ((Object)(object)DungeonPlugin.Instance != (Object)null && DungeonPlugin.Instance.IsSmoke) { string[] commandLineArgs = Environment.GetCommandLineArgs(); foreach (string text in commandLineArgs) { if (text.StartsWith("--gt-smoke-seed=", StringComparison.Ordinal) && int.TryParse(text.Substring("--gt-smoke-seed=".Length), out var result) && result > 0) { seed = result; } } } SetGenerating(); NativeWorld.DepartFoyer(); instance.InitializeForRunWithSeed(seed); ins