Decompiled source of 4 Player Co oP Mod v0.20.7
BepInEx/plugins/FateDiagnostics/FateDiagnostics.dll
Decompiled 4 days 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.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.AI; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("FateDiagnostics")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.20.6.0")] [assembly: AssemblyInformationalVersion("0.20.6")] [assembly: AssemblyProduct("FateDiagnostics")] [assembly: AssemblyTitle("FateDiagnostics")] [assembly: AssemblyVersion("0.20.6.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace FateDiagnostics { internal static class ActorCodec { internal static int Activity(ActorState actor) { int hash = 17; float[] position = actor.position; for (int i = 0; i < position.Length; i++) { Number(position[i]); } position = actor.rotation; for (int i = 0; i < position.Length; i++) { Number(position[i]); } Number(actor.health); Number(actor.shrineSeconds); Add(actor.state); Add(actor.dead ? 1 : 0); Number(actor.animation.speed); AnimationLayer[] layers = actor.animation.layers; foreach (AnimationLayer animationLayer in layers) { Add(animationLayer.hash); Number(animationLayer.weight); if (!animationLayer.loop) { Number(animationLayer.time); } } AnimationValue[] values = actor.animation.values; foreach (AnimationValue obj in values) { Add(obj.hash); Add(obj.type); Add(obj.integer); Number(obj.value); Add(obj.flag ? 1 : 0); } return hash; void Add(int value) { hash = hash * 31 + value; } void Number(float value) { Add((int)Math.Round(value * 100f)); } } internal static string Encode(ActorSnapshot packet) { if (packet?.scope == null || packet.actors == null || packet.actors.Length > 64 || packet.sequence < 0) { throw new IOException("Invalid actor snapshot"); } using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write((byte)2); MotionCodec.Text(binaryWriter, packet.scope.scene, 128); MotionCodec.Text(binaryWriter, packet.scope.expedition, 128); binaryWriter.Write(packet.scope.campaign); binaryWriter.Write(packet.scope.realm); binaryWriter.Write(packet.scope.depth); MotionCodec.Text(binaryWriter, packet.generation, 64); binaryWriter.Write(packet.sequence); binaryWriter.Write(packet.actors.Length); ActorState[] actors = packet.actors; foreach (ActorState actorState in actors) { MotionCodec.Text(binaryWriter, actorState.id, 128); MotionCodec.Vector(binaryWriter, actorState.position, 3); MotionCodec.Vector(binaryWriter, actorState.rotation, 4); if (float.IsNaN(actorState.health) || float.IsInfinity(actorState.health) || actorState.health < 0f || actorState.health > 100000000f) { throw new IOException("Invalid actor health"); } if (float.IsNaN(actorState.shrineSeconds) || float.IsInfinity(actorState.shrineSeconds) || actorState.shrineSeconds < -1f || actorState.shrineSeconds > 3600f) { throw new IOException("Invalid shrine timer"); } binaryWriter.Write(actorState.health); binaryWriter.Write(actorState.shrineSeconds); binaryWriter.Write(actorState.state); binaryWriter.Write(actorState.dead); MotionCodec.WriteAnimation(binaryWriter, actorState.animation); } byte[] array = memoryStream.ToArray(); if (array.Length > 98304) { throw new IOException("Actor snapshot too large"); } return Convert.ToBase64String(array); } internal static ActorSnapshot Decode(string value) { if (value == null || value.Length > 131072) { throw new IOException("Actor snapshot too large"); } using MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(value)); using BinaryReader binaryReader = new BinaryReader(memoryStream); if (binaryReader.ReadByte() != 2) { throw new IOException("Actor protocol mismatch"); } ActorSnapshot actorSnapshot = new ActorSnapshot { scope = new WorldScope { scene = MotionCodec.Text(binaryReader, 128), expedition = MotionCodec.Text(binaryReader, 128), campaign = binaryReader.ReadInt32(), realm = binaryReader.ReadInt32(), depth = binaryReader.ReadInt32() }, generation = MotionCodec.Text(binaryReader, 64), sequence = binaryReader.ReadInt64() }; if (!Guid.TryParseExact(actorSnapshot.generation, "N", out var _) || actorSnapshot.sequence < 0) { throw new IOException("Invalid actor generation"); } actorSnapshot.actors = new ActorState[MotionCodec.Count(binaryReader, 64)]; HashSet<string> hashSet = new HashSet<string>(); for (int i = 0; i < actorSnapshot.actors.Length; i++) { ActorState actorState = new ActorState { id = MotionCodec.Text(binaryReader, 128), position = MotionCodec.Vector(binaryReader, 3), rotation = MotionCodec.Vector(binaryReader, 4), health = binaryReader.ReadSingle(), shrineSeconds = binaryReader.ReadSingle(), state = binaryReader.ReadInt32(), dead = binaryReader.ReadBoolean(), animation = MotionCodec.ReadAnimation(binaryReader) }; if (string.IsNullOrEmpty(actorState.id) || !hashSet.Add(actorState.id) || float.IsNaN(actorState.health) || float.IsInfinity(actorState.health) || actorState.health < 0f || actorState.health > 100000000f || actorState.state < 0 || actorState.state > 33 || float.IsNaN(actorState.shrineSeconds) || float.IsInfinity(actorState.shrineSeconds) || actorState.shrineSeconds < -1f || actorState.shrineSeconds > 3600f) { throw new IOException("Invalid actor state"); } float[] rotation = actorState.rotation; float num = rotation[0] * rotation[0] + rotation[1] * rotation[1] + rotation[2] * rotation[2] + rotation[3] * rotation[3]; if (num < 0.5f || num > 1.5f) { throw new IOException("Invalid actor rotation"); } actorSnapshot.actors[i] = actorState; } if (memoryStream.Position != memoryStream.Length) { throw new IOException("Trailing actor data"); } return actorSnapshot; } } [Serializable] internal sealed class ActorCatalog { public WorldScope scope; public string generation; public ActorDefinition[] actors; } [Serializable] internal sealed class ActorDefinition { public string id; public string key; public string name; public string questOwner; public string questToken; public int slot; public bool npc; public bool spawn; public bool unique; public int level; public int rank; public int kind; public int alignment; public float[] position; public float[] rotation; public float[] scale; } internal sealed class ActorSnapshot { internal WorldScope scope; internal string generation; internal long sequence; internal ActorState[] actors; } internal sealed class ActorState { internal string id; internal float[] position; internal float[] rotation; internal float health; internal float shrineSeconds = -1f; internal int state; internal bool dead; internal AnimationPose animation; } [Serializable] internal sealed class ActorHit { public WorldScope scope; public string generation; public string target; public string ticket; public int amount; public int type; public bool raw; public bool critical; public bool respond; } internal sealed class ActorReceipts { private readonly HashSet<string> seen = new HashSet<string>(); internal bool Accept(string ticket) { if (!Guid.TryParseExact(ticket, "N", out var _)) { throw new IOException("Invalid hit ticket"); } if (seen.Count >= 65536 && !seen.Contains(ticket)) { throw new IOException("Combat receipt limit reached; reconnect"); } return seen.Add(ticket); } } internal static class AggroPreference { internal static int Pick(int localStrength, int remoteStrength, bool localValid, bool remoteValid, bool localCloser, double roll) { if (roll >= 0.8 || (!localValid && !remoteValid)) { return -1; } if (!localValid) { return 1; } if (!remoteValid) { return 0; } if (localStrength != remoteStrength) { return (localStrength <= remoteStrength) ? 1 : 0; } return (!localCloser) ? 1 : 0; } } internal static class AssetIdentity { internal static string Name(string value) { string text = value ?? ""; while (true) { if (text.EndsWith(" (Instance)", StringComparison.Ordinal)) { text = text.Substring(0, text.Length - 11); continue; } if (!text.EndsWith(" Instance", StringComparison.Ordinal)) { break; } text = text.Substring(0, text.Length - 9); } return text; } internal static string Key(string value) { if (value == null) { return ""; } int num = value.IndexOf('|'); if (num >= 0) { return Name(value.Substring(0, num)) + value.Substring(num); } return Name(value); } } internal sealed class ClassMarkerTint { private sealed class SelectionMaterial { internal Material[] original; internal Material[] selected; internal int layer; internal MaterialPropertyBlock properties; internal readonly List<Material> owned = new List<Material>(); } private static readonly Dictionary<int, Texture2D[]> recoloured = new Dictionary<int, Texture2D[]>(); private readonly Dictionary<Renderer, MaterialPropertyBlock> originals = new Dictionary<Renderer, MaterialPropertyBlock>(); private readonly MaterialPropertyBlock paint = new MaterialPropertyBlock(); private Transform owner; private int last = -1; private float next; private bool wasSelected; private bool wasDowned; private bool selectionHeld; private float selectedAt; private readonly Dictionary<Renderer, SelectionMaterial> selectionMaterials = new Dictionary<Renderer, SelectionMaterial>(); private static readonly Dictionary<Renderer, SelectionMaterial> nativeAppearance = new Dictionary<Renderer, SelectionMaterial>(); internal static Material[] NativeMaterials(Renderer renderer) { if (!nativeAppearance.TryGetValue(renderer, out var value)) { return renderer.sharedMaterials; } return value.original; } internal static int NativeLayer(GameObject node) { Renderer component = node.GetComponent<Renderer>(); if (!Object.op_Implicit((Object)(object)component) || !nativeAppearance.TryGetValue(component, out var value)) { return node.layer; } return value.layer; } internal static MaterialPropertyBlock NativeProperties(Renderer renderer) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if (nativeAppearance.TryGetValue(renderer, out var value)) { return value.properties; } MaterialPropertyBlock val = new MaterialPropertyBlock(); renderer.GetPropertyBlock(val); return val; } private static string Key(string name) { return AssetIdentity.Name(name.StartsWith("FateRemote.", StringComparison.Ordinal) ? name.Substring(11) : name); } internal void Tick(Transform target, int choice, bool selected = false, bool self = false, bool downed = false) { //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Expected O, but got Unknown //IL_04de: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Expected O, but got Unknown //IL_0352: Unknown result type (might be due to invalid IL or missing references) //IL_036d: Unknown result type (might be due to invalid IL or missing references) //IL_037e: Unknown result type (might be due to invalid IL or missing references) //IL_038f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)owner != (Object)(object)target) { Restore(); owner = target; last = -1; next = 0f; } if (selected && !selectionHeld) { selectedAt = Time.unscaledTime; } selectionHeld = selected; float num = Mathf.SmoothStep(0f, 1f, Mathf.Clamp01((Time.unscaledTime - selectedAt - 0.6f) / 1.4f)); float num2 = Mathf.Lerp(1f, self ? 0f : 0.4f, num); selected = downed || (selected && (!self || num < 1f)); if (downed) { num2 = 1f; } if (selected) { foreach (KeyValuePair<Renderer, SelectionMaterial> selectionMaterial in selectionMaterials) { if (Object.op_Implicit((Object)(object)selectionMaterial.Key)) { selectionMaterial.Key.GetPropertyBlock(paint); paint.SetColor("_BaseColor", new Color(num2, num2, num2, 1f)); selectionMaterial.Key.SetPropertyBlock(paint); } } } if (!Object.op_Implicit((Object)(object)target) || (Time.unscaledTime < next && choice == last && selected == wasSelected && downed == wasDowned)) { return; } next = Time.unscaledTime + 1f; last = choice; wasSelected = selected; wasDowned = downed; Renderer[] componentsInChildren = ((Component)target).GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { selectionMaterials.TryGetValue(val, out var value); Material[] array = value?.original ?? val.sharedMaterials; Texture val2 = null; Material[] array2 = array; foreach (Material val3 in array2) { if (Object.op_Implicit((Object)(object)val3) && Key(((Object)val3).name) == "M_Stencil_Marker_Player") { val2 = val3.GetTexture("_MainTex"); break; } } if (!Object.op_Implicit((Object)(object)val2)) { continue; } if (!originals.TryGetValue(val, out var value2)) { value2 = new MaterialPropertyBlock(); val.GetPropertyBlock(value2); originals[val] = value2; } if (selected) { if (value == null) { Shader val4 = Shader.Find("Tableflip/Basic/Shader_ParticleEmissiveOneTex"); if (!Object.op_Implicit((Object)(object)val4)) { Debug.LogWarning((object)"FATE selected native marker: game particle shader unavailable"); continue; } value = new SelectionMaterial { original = array, selected = (Material[])array.Clone(), layer = ((Component)val).gameObject.layer, properties = value2 }; for (int k = 0; k < array.Length; k++) { if (Object.op_Implicit((Object)(object)array[k]) && !(Key(((Object)array[k]).name) != "M_Stencil_Marker_Player")) { Material val5 = new Material(val4) { name = "Fate.SelectedNativeMarker", renderQueue = 4000 }; val5.SetTexture("_BaseMap", (Texture)(object)ColourTexture(val2, downed ? 6 : 5)); val5.SetTextureScale("_BaseMap", array[k].GetTextureScale("_MainTex")); val5.SetTextureOffset("_BaseMap", array[k].GetTextureOffset("_MainTex")); val5.SetColor("_BaseColor", Color.white); val5.SetColor("_EmissionColor", Color.white); val5.SetFloat("_Surface", 1f); val5.SetFloat("_SrcBlend", 1f); val5.SetFloat("_DstBlend", 1f); val5.SetFloat("_ZTest", 8f); val5.SetFloat("_ZWrite", 0f); val5.SetFloat("_Cull", 0f); val5.EnableKeyword("_SURFACE_TYPE_TRANSPARENT"); val5.SetShaderPassEnabled("DepthOnly", false); val5.SetShaderPassEnabled("DepthNormalsOnly", false); val5.SetShaderPassEnabled("ShadowCaster", false); value.selected[k] = val5; value.owned.Add(val5); } } selectionMaterials[val] = value; nativeAppearance[val] = value; Debug.Log((object)"FATE selected marker uses native mesh and game particle shader"); } val.sharedMaterials = value.selected; ((Component)val).gameObject.layer = 0; val.GetPropertyBlock(paint); paint.SetTexture("_BaseMap", (Texture)(object)ColourTexture(val2, downed ? 6 : 5)); paint.SetColor("_BaseColor", new Color(num2, num2, num2, 1f)); val.SetPropertyBlock(paint); continue; } if (value != null) { val.sharedMaterials = value.original; ((Component)val).gameObject.layer = value.layer; } val.SetPropertyBlock(value2); if (choice != 0) { Texture2D val6 = ColourTexture(val2, choice); if (Object.op_Implicit((Object)(object)val6)) { val.GetPropertyBlock(paint); paint.SetTexture("_MainTex", (Texture)(object)val6); val.SetPropertyBlock(paint); } } } } private static Texture2D ColourTexture(Texture source, int choice) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0121: 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_013f: 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_014d: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_0331: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_02b9: Unknown result type (might be due to invalid IL or missing references) choice = ((choice >= 5 && choice <= 6) ? choice : PartyClass.Clamp(choice)); if (!recoloured.TryGetValue(((Object)source).GetInstanceID(), out var value)) { value = (Texture2D[])(object)new Texture2D[7]; recoloured[((Object)source).GetInstanceID()] = value; } if (Object.op_Implicit((Object)(object)value[choice])) { return value[choice]; } RenderTexture active = RenderTexture.active; RenderTexture val = null; Texture2D val2 = null; try { val = RenderTexture.GetTemporary(source.width, source.height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2); Graphics.Blit(source, val); RenderTexture.active = val; val2 = new Texture2D(source.width, source.height, (TextureFormat)4, false) { name = "Fate.ClassMarker." + choice, wrapMode = source.wrapMode, filterMode = source.filterMode }; val2.ReadPixels(new Rect(0f, 0f, (float)source.width, (float)source.height), 0, 0); Color32[] pixels = val2.GetPixels32(); Color val3 = (Color)(choice switch { 5 => Color.yellow, 6 => DownedState.ReviveColour, _ => PartyClass.RingTint(choice), }); for (int i = 0; i < pixels.Length; i++) { Color32 val4 = pixels[i]; byte b = Math.Max(val4.r, Math.Max(val4.g, val4.b)); float num = ((b == 0) ? 0f : ((float)(int)Math.Min(val4.r, Math.Min(val4.g, val4.b)) / (float)(int)b)); float num2 = ((choice >= 5) ? 0f : ((choice == 0) ? 1f : (0.72f * Mathf.SmoothStep(0f, 1f, num * num)))); Color val5 = Color.Lerp(val3, Color.white, num2); float num3 = ((choice == 0) ? ((float)(int)b) : ((float)(int)b * 0.94f)); pixels[i] = new Color32((byte)(num3 * val5.r), (byte)(num3 * val5.g), (byte)(num3 * val5.b), val4.a); } if (choice == 6) { Color32[] array = (Color32[])pixels.Clone(); int width = ((Texture)val2).width; int height = ((Texture)val2).height; int num4 = Math.Max(3, width / 45); for (int j = 0; j < height; j++) { for (int k = 0; k < width; k++) { float num5 = 0f; for (int l = -num4; l <= num4; l += 2) { for (int m = -num4; m <= num4; m += 2) { int num6 = k + m; int num7 = j + l; if (num6 >= 0 && num7 >= 0 && num6 < width && num7 < height) { Color32 val6 = array[num7 * width + num6]; float num8 = Mathf.Exp((float)(-(m * m + l * l)) / ((float)(num4 * num4) * 0.5f)); num5 = Mathf.Max(num5, (float)(int)val6.r * num8 * 0.55f); } } } Color32 val7 = array[j * width + k]; float num9 = Mathf.Max((float)(int)val7.r, num5); pixels[j * width + k] = new Color32((byte)num9, (byte)(num9 * val3.g), (byte)(num9 * val3.b), Math.Max(val7.a, (byte)num5)); } } } val2.SetPixels32(pixels); val2.Apply(false, true); value[choice] = val2; return val2; } catch (Exception ex) { if (Object.op_Implicit((Object)(object)val2)) { Object.Destroy((Object)(object)val2); } Debug.LogWarning((object)("FATE class marker recolour failed: " + ex.GetType().Name)); return null; } finally { RenderTexture.active = active; if (Object.op_Implicit((Object)(object)val)) { RenderTexture.ReleaseTemporary(val); } } } internal void Restore() { foreach (KeyValuePair<Renderer, SelectionMaterial> selectionMaterial in selectionMaterials) { if (Object.op_Implicit((Object)(object)selectionMaterial.Key)) { selectionMaterial.Key.sharedMaterials = selectionMaterial.Value.original; ((Component)selectionMaterial.Key).gameObject.layer = selectionMaterial.Value.layer; } nativeAppearance.Remove(selectionMaterial.Key); foreach (Material item in selectionMaterial.Value.owned) { if (Object.op_Implicit((Object)(object)item)) { Object.Destroy((Object)(object)item); } } } selectionMaterials.Clear(); foreach (KeyValuePair<Renderer, MaterialPropertyBlock> original in originals) { if (Object.op_Implicit((Object)(object)original.Key)) { original.Key.SetPropertyBlock(original.Value); } } originals.Clear(); owner = null; wasSelected = false; selectionHeld = false; } } internal sealed class CombatVisuals { private sealed class RemoteMotion { internal VisualMotion packet; internal long revision; internal long applied; } private sealed class Tracked { internal Component source; internal string id; internal string prefab; internal ParticleConfig[] particles; } private static CombatVisuals current; private readonly LoopbackSession network; private readonly Func<WorldScope> scope; private readonly Func<IReadOnlyList<Component>> actors; private readonly Action<string> log; private readonly Dictionary<int, Tracked> localEffects = new Dictionary<int, Tracked>(); private readonly Dictionary<string, GameObject> remoteEffects = new Dictionary<string, GameObject>(); private readonly Dictionary<string, GameObject> prefabs = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, Vector3> targets = new Dictionary<string, Vector3>(); private readonly HashSet<string> missing = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private bool prefabsReady; private readonly List<GameObject> oneShots = new List<GameObject>(); private WorldScope lastScope; private float nextSend; private float lastSent; private float nextMetrics; private int lastActivity; private readonly Dictionary<int, Animator> sourceAnimators = new Dictionary<int, Animator>(); private readonly Dictionary<int, AnimatorControllerParameter[]> sourceParameters = new Dictionary<int, AnimatorControllerParameter[]>(); private readonly Dictionary<int, int> sourceControllers = new Dictionary<int, int>(); private readonly Stopwatch timer = new Stopwatch(); private double captureMilliseconds; private double encodeMilliseconds; private double decodeMilliseconds; private int captures; private int encodes; private int decodes; private long revision; private long appliedRevision; private readonly Dictionary<string, RemoteMotion> receivedByMember = new Dictionary<string, RemoteMotion>(); private VisualMotion received { get { return Motion(null).packet; } set { Motion(null).packet = value; } } internal string Status { get; private set; } = "Combat visuals: awaiting connection"; private RemoteMotion Motion(string id) { id = id ?? network.ContextPeer ?? "host"; if (!receivedByMember.TryGetValue(id, out var value)) { value = new RemoteMotion(); receivedByMember[id] = value; } return value; } private static object Read(object target, string name) { return NativeMember.Read(target, name); } internal CombatVisuals(Harmony harmony, Assembly game, LoopbackSession network, Func<WorldScope> scope, Func<IReadOnlyList<Component>> actors, Action<string> log) { //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected O, but got Unknown //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Expected O, but got Unknown //IL_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Expected O, but got Unknown //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Expected O, but got Unknown //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Expected O, but got Unknown current = this; this.network = network; this.scope = scope; this.actors = actors; this.log = log; string[] array = new string[3] { "Tableflip.SpellProjectile", "Tableflip.SpellArea", "Tableflip.Spell" }; foreach (string name in array) { harmony.Patch((MethodBase)AccessTools.Method(game.GetType(name, throwOnError: true), "Setup", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "SpellSetup", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } harmony.Patch((MethodBase)AccessTools.Method(game.GetType("Tableflip.Character"), "CastingVFX", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "Casting", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(game.GetType("Tableflip.SpellVfxAutoDestroy"), "SetLifeTime", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "TimedVisual", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); array = new string[2] { "Tableflip.SpellVfxAutoDestroy", "Tableflip.SpellArea" }; foreach (string name2 in array) { harmony.Patch((MethodBase)AccessTools.Method(game.GetType(name2), "DestroySelf", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "EndVisual", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } harmony.Patch((MethodBase)AccessTools.Method(game.GetType("Tableflip.Character"), "AddPSEffect", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "PersistentEffect", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); array = new string[2] { "Tableflip.LevelGenerator.SpawnMonster", "Tableflip.Town.SpawnMonsterInTown" }; foreach (string entry in array) { int split = entry.LastIndexOf('.'); Type type = game.GetType(entry.Substring(0, split), throwOnError: true); foreach (MethodInfo item in from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.DeclaringType == type && m.Name == entry.Substring(split + 1) && m.ReturnType != typeof(void) select m) { harmony.Patch((MethodBase)item, (HarmonyMethod)null, new HarmonyMethod(typeof(CombatVisuals), "SummonBurst", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } log("COMBAT_VISUAL_HOOKS installed spell projectile/area/buff/casting/impact"); } private static string PrefabName(string name) { if (name != null) { if (!name.EndsWith("(Clone)", StringComparison.Ordinal)) { return name; } return name.Substring(0, name.Length - 7).TrimEnd(); } return ""; } internal bool IsLocal(object caster) { foreach (Component item in actors()) { if (item == caster) { return true; } } return false; } private void Track(Component source, string prefab) { if (Object.op_Implicit((Object)(object)source) && !localEffects.ContainsKey(((Object)source.gameObject).GetInstanceID()) && localEffects.Count < 64) { ParticleConfig[] particles = source.GetComponentsInChildren<ParticleSystem>(true).Take(128).Select(delegate(ParticleSystem ps) { //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) ParticleConfig obj = new ParticleConfig { seed = ps.randomSeed }; MainModule main = ps.main; obj.loop = ((MainModule)(ref main)).loop; return obj; }) .ToArray(); localEffects[((Object)source.gameObject).GetInstanceID()] = new Tracked { source = (Component)(object)source.transform, id = Guid.NewGuid().ToString("N"), prefab = PrefabName(prefab), particles = particles }; } } private static void SpellSetup(object __instance, object[] __args) { try { if (current.scope() == null) { return; } Component val = (Component)((__instance is Component) ? __instance : null); if (val != null && Object.op_Implicit((Object)(object)val) && current.localEffects.Count < 64) { object obj = ((__args.Length != 0) ? Read(__args[0], "caster") : null); if (obj == null) { object obj2 = Read(val, "data"); obj = ((obj2 == null) ? Read(val, "caster") : Read(obj2, "caster")); } if (current.IsLocal(obj)) { string text = PrefabName(((Object)val.gameObject).name); current.Track(val, text); current.log("LOCAL_SPELL_VISUAL prefab=" + text); } } } catch (Exception ex) { current.log("SPELL_CAPTURE_FAILED " + ex.GetBaseException().Message); } } private static void TimedVisual(object __instance, object[] __args) { try { if (SpellAudio.OwnedContext && current.scope() != null) { Component val = (Component)((__instance is Component) ? __instance : null); if (val != null && Object.op_Implicit((Object)(object)val)) { current.Track(val, ((Object)val.gameObject).name); current.log("LOCAL_TIMED_SPELL_VISUAL prefab=" + PrefabName(((Object)val).name) + " requested-life=" + __args[0]?.ToString() + " lifecycle=native"); } } } catch (Exception ex) { current.log("TIMED_VISUAL_FAILED " + ex.GetBaseException().Message); } } private static void EndVisual(object __instance) { Component val = (Component)((__instance is Component) ? __instance : null); if (val != null && Object.op_Implicit((Object)(object)val)) { current.localEffects.Remove(((Object)val.gameObject).GetInstanceID()); } } private static void Casting(object __instance, object[] __args) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) try { if (current.scope() == null || !current.IsLocal(__instance)) { return; } Component val = (Component)((__instance is Component) ? __instance : null); if (val == null) { return; } string text = Convert.ToString(__args[0]); bool flag = false; ParticleSystem[] componentsInChildren = val.GetComponentsInChildren<ParticleSystem>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Transform val2 = ((Component)componentsInChildren[i]).transform; while (Object.op_Implicit((Object)(object)val2) && (Object)(object)val2 != (Object)(object)val.transform) { if (PrefabName(((Object)val2).name).Equals(text, StringComparison.OrdinalIgnoreCase)) { current.Track((Component)(object)val2, ((Object)val2).name); flag = true; break; } val2 = val2.parent; } } if (!flag) { current.OneShot(text, val.transform.position + Vector3.up, val.transform.rotation, Mathf.Clamp(Convert.ToSingle(Read(val, "castTimer") ?? ((object)1f)), 0.2f, 10f)); } } catch (Exception ex) { current.log("CAST_VISUAL_FAILED " + ex.GetBaseException().Message); } } private static void PersistentEffect(object __instance, object[] __args) { try { if (current.scope() != null && __args.Length != 0 && (current.IsLocal(__instance) || current.IsLocal(Read(__args[0], "source")))) { current.TrackEffectChildren(__instance); } } catch (Exception ex) { current.log("PERSISTENT_VISUAL_FAILED " + ex.GetBaseException().Message); } } private void TrackEffectChildren(object actor) { if (!(Read(actor, "effectChildren") is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { GameObject val = (GameObject)((item is GameObject) ? item : null); if (val != null && Object.op_Implicit((Object)(object)val)) { Track((Component)(object)val.transform, ((Object)val).name); } } } private static void SummonBurst(object __result) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) try { if (SpellAudio.OwnedContext && current.scope() != null) { Component val = (Component)((__result is Component) ? __result : null); if (val != null && Object.op_Implicit((Object)(object)val)) { current.OneShot("PS_summonMonster", val.transform.position, Quaternion.identity, 2f); } } } catch (Exception ex) { current.log("SUMMON_BURST_FAILED " + ex.GetBaseException().Message); } } private void OneShot(string prefab, Vector3 position, Quaternion rotation, float seconds) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(prefab)) { EffectPose effectPose = Pose(Guid.NewGuid().ToString("N"), prefab, position, rotation, Vector3.one); VisualMotion visualMotion = new VisualMotion(); visualMotion.scope = scope(); visualMotion.actors = Array.Empty<AnimationPose>(); visualMotion.effects = new EffectPose[1] { effectPose }; VisualMotion packet = visualMotion; effectPose.duration = seconds; network.Send(new WireMessage("visual-once", MotionCodec.Encode(packet))); } } internal static void WorldBurst(string prefab, Vector3 position) { //IL_001e: 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) if (current?.scope() != null) { current.OneShot(prefab, position, Quaternion.identity, 3f); } } private static float[] Vec(Vector3 value) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) return new float[3] { value.x, value.y, value.z }; } private static EffectPose Pose(string id, string prefab, Vector3 position, Quaternion rotation, Vector3 scale) { //IL_0015: 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_0032: 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_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) EffectPose effectPose = new EffectPose(); effectPose.id = id; effectPose.prefab = prefab; effectPose.position = Vec(position); effectPose.rotation = new float[4] { rotation.x, rotation.y, rotation.z, rotation.w }; effectPose.scale = Vec(scale); return effectPose; } internal AnimationPose Capture(Component actor) { return Capture(actor, compact: false); } internal AnimationPose CaptureWorld(Component actor) { return Capture(actor, compact: true); } private AnimationPose Capture(Component actor, bool compact) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Expected I4, but got Unknown //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Invalid comparison between Unknown and I4 //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Invalid comparison between Unknown and I4 //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02b5: Invalid comparison between Unknown and I4 if (!Object.op_Implicit((Object)(object)actor)) { return new AnimationPose { layers = Array.Empty<AnimationLayer>(), values = Array.Empty<AnimationValue>(), speed = 1f }; } int instanceID = ((Object)actor).GetInstanceID(); if (!sourceAnimators.TryGetValue(instanceID, out var value) || !Object.op_Implicit((Object)(object)value)) { value = actor.GetComponentInChildren<Animator>(); sourceAnimators[instanceID] = value; } if (!Object.op_Implicit((Object)(object)value) || !Object.op_Implicit((Object)(object)value.runtimeAnimatorController)) { return new AnimationPose { layers = Array.Empty<AnimationLayer>(), values = Array.Empty<AnimationValue>(), speed = 1f }; } List<AnimationLayer> list = new List<AnimationLayer>(); for (int i = 0; i < Math.Min(value.layerCount, 8); i++) { AnimatorStateInfo val = (value.IsInTransition(i) ? value.GetNextAnimatorStateInfo(i) : value.GetCurrentAnimatorStateInfo(i)); list.Add(new AnimationLayer { hash = ((AnimatorStateInfo)(ref val)).fullPathHash, time = ((AnimatorStateInfo)(ref val)).normalizedTime, weight = value.GetLayerWeight(i), loop = ((AnimatorStateInfo)(ref val)).loop }); } List<AnimationValue> list2 = new List<AnimationValue>(); int instanceID2 = ((Object)value.runtimeAnimatorController).GetInstanceID(); if (!sourceControllers.TryGetValue(instanceID, out var value2) || value2 != instanceID2) { sourceControllers[instanceID] = instanceID2; sourceParameters[instanceID] = value.parameters.Take(128).ToArray(); } AnimatorControllerParameter[] array = sourceParameters[instanceID]; foreach (AnimatorControllerParameter val2 in array) { if (compact && val2.name != "IsWalking" && val2.name != "IsRunning" && val2.name != "IsAttacking" && val2.name != "IsDead" && val2.name != "IsCasting" && val2.name != "SpeedMove" && val2.name != "MoveX" && val2.name != "MoveZ" && val2.name != "Speed") { continue; } AnimationValue animationValue = new AnimationValue { hash = val2.nameHash, type = (int)val2.type }; if ((int)val2.type == 1) { animationValue.value = value.GetFloat(val2.nameHash); } else if ((int)val2.type == 3) { animationValue.integer = value.GetInteger(val2.nameHash); } else { if ((int)val2.type != 4) { continue; } animationValue.flag = value.GetBool(val2.nameHash); } list2.Add(animationValue); } return new AnimationPose { layers = list.ToArray(), values = list2.ToArray(), speed = value.speed }; } internal void Tick() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) WorldScope worldScope = scope(); if (worldScope == null) { Clear(); lastScope = null; return; } if (lastScope == null || !lastScope.Matches(worldScope)) { Clear(); lastScope = worldScope; } foreach (KeyValuePair<string, Vector3> target in targets) { if (remoteEffects.TryGetValue(target.Key, out var value) && Object.op_Implicit((Object)(object)value)) { value.transform.position = Vector3.Lerp(value.transform.position, target.Value, 1f - Mathf.Exp(-20f * Time.unscaledDeltaTime)); } } if (Time.realtimeSinceStartup < nextSend) { return; } nextSend = Time.realtimeSinceStartup + 0.1f; timer.Restart(); foreach (Component item in actors()) { if (Object.op_Implicit((Object)(object)item)) { TrackEffectChildren(item); } } List<EffectPose> list = new List<EffectPose>(); KeyValuePair<int, Tracked>[] array = localEffects.ToArray(); for (int i = 0; i < array.Length; i++) { KeyValuePair<int, Tracked> keyValuePair2 = array[i]; Tracked value2 = keyValuePair2.Value; if (!Object.op_Implicit((Object)(object)value2.source)) { localEffects.Remove(keyValuePair2.Key); continue; } Transform transform = value2.source.transform; EffectPose effectPose = Pose(value2.id, value2.prefab, transform.position, transform.rotation, transform.lossyScale); effectPose.particles = value2.particles; list.Add(effectPose); } VisualMotion packet = new VisualMotion { scope = worldScope, actors = actors().Take(5).Select(Capture).ToArray(), effects = list.ToArray() }; captureMilliseconds += timer.Elapsed.TotalMilliseconds; captures++; int num = MotionCodec.Activity(packet); if (num != lastActivity || Time.realtimeSinceStartup - lastSent >= 1f) { timer.Restart(); string data = MotionCodec.Encode(packet); encodeMilliseconds += timer.Elapsed.TotalMilliseconds; encodes++; network.Send(new WireMessage("visual-motion", data)); lastActivity = num; lastSent = Time.realtimeSinceStartup; } if (Time.realtimeSinceStartup >= nextMetrics && captures > 0) { nextMetrics = Time.realtimeSinceStartup + 15f; log("VISUAL_TIMING capture-ms=" + (captureMilliseconds / (double)captures).ToString("F3") + " encode-ms=" + (encodeMilliseconds / (double)Math.Max(encodes, 1)).ToString("F3") + " decode-ms=" + (decodeMilliseconds / (double)Math.Max(decodes, 1)).ToString("F3") + " captures=" + captures + " packets=" + encodes); captureMilliseconds = (encodeMilliseconds = (decodeMilliseconds = 0.0)); captures = (encodes = (decodes = 0)); } } internal void ApplyAnimations(string member, VisualAvatar avatar, IReadOnlyList<VisualAvatar> pets) { RemoteMotion remoteMotion = Motion(member); VisualMotion packet = remoteMotion.packet; if (avatar == null || packet == null || remoteMotion.applied == remoteMotion.revision) { return; } WorldScope worldScope = scope(); if (worldScope != null && worldScope.Matches(packet.scope)) { if (packet.actors.Length != 0) { avatar.ApplyPose(packet.actors[0]); } for (int i = 0; i < pets.Count && i + 1 < packet.actors.Length; i++) { pets[i].ApplyPose(packet.actors[i + 1]); } remoteMotion.applied = remoteMotion.revision; } } internal bool Receive(WireMessage message) { //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) if (message.Kind != "visual-motion" && message.Kind != "visual-once") { return false; } timer.Restart(); VisualMotion visualMotion = MotionCodec.Decode(message.Data); decodeMilliseconds += timer.Elapsed.TotalMilliseconds; decodes++; WorldScope worldScope = scope(); if (worldScope == null || visualMotion?.scope == null || !worldScope.Matches(visualMotion.scope)) { return true; } Validate(visualMotion); EffectPose[] effects; if (message.Kind == "visual-once") { effects = visualMotion.effects; foreach (EffectPose effectPose in effects) { GameObject val = Create(effectPose); if (Object.op_Implicit((Object)(object)val)) { oneShots.RemoveAll((GameObject obj) => !Object.op_Implicit((Object)(object)obj)); if (oneShots.Count >= 64) { Object.Destroy((Object)(object)val); continue; } oneShots.Add(val); Object.Destroy((Object)(object)val, Mathf.Clamp(effectPose.duration, 0.2f, 10f)); } } return true; } received = visualMotion; Motion(message.Source).revision++; revision++; HashSet<string> hashSet = new HashSet<string>(); effects = visualMotion.effects; foreach (EffectPose effectPose2 in effects) { string text = message.Source + ":" + effectPose2.id; hashSet.Add(text); if (!remoteEffects.TryGetValue(text, out var value) || !Object.op_Implicit((Object)(object)value)) { value = Create(effectPose2); if (!Object.op_Implicit((Object)(object)value)) { continue; } remoteEffects[text] = value; } targets[text] = new Vector3(effectPose2.position[0], effectPose2.position[1], effectPose2.position[2]); value.transform.rotation = new Quaternion(effectPose2.rotation[0], effectPose2.rotation[1], effectPose2.rotation[2], effectPose2.rotation[3]); value.transform.localScale = new Vector3(effectPose2.scale[0], effectPose2.scale[1], effectPose2.scale[2]); } string[] array = remoteEffects.Keys.ToArray(); foreach (string text2 in array) { if (text2.StartsWith(message.Source + ":", StringComparison.Ordinal) && !hashSet.Contains(text2)) { if (Object.op_Implicit((Object)(object)remoteEffects[text2])) { Object.Destroy((Object)(object)remoteEffects[text2]); } remoteEffects.Remove(text2); targets.Remove(text2); } } Status = "Combat poses synced | spell visuals: " + remoteEffects.Count; return true; } private void RefreshPrefabs() { object value = AccessTools.Property(NativeTypes.Get("Tableflip.FateDB"), "Instance").GetValue(null, null); string[] array = new string[2] { "particlePrefabs", "spellPrefabs" }; foreach (string name in array) { if (!(Read(value, name) is IEnumerable enumerable)) { continue; } foreach (object item in enumerable) { GameObject val = (GameObject)((item is GameObject) ? item : null); if (val != null && Object.op_Implicit((Object)(object)val)) { prefabs[((Object)val).name] = val; } } } if (Read(value, "allSpells") is IEnumerable enumerable2) { foreach (object item2 in enumerable2) { array = new string[2] { "particleProjectilePrefab", "particleImpactPrefab" }; foreach (string name2 in array) { object obj2 = Read(item2, name2); GameObject val2 = (GameObject)((obj2 is GameObject) ? obj2 : null); if (val2 != null && Object.op_Implicit((Object)(object)val2)) { prefabs[((Object)val2).name] = val2; } } } } KeyValuePair<string, GameObject>[] array2 = prefabs.ToArray(); for (int i = 0; i < array2.Length; i++) { KeyValuePair<string, GameObject> keyValuePair = array2[i]; if (keyValuePair.Key.StartsWith("PS_", StringComparison.OrdinalIgnoreCase)) { prefabs[keyValuePair.Key.Substring(3)] = keyValuePair.Value; } } prefabsReady = true; } private GameObject Create(EffectPose pose) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_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_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_028b: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) if (missing.Contains(pose.prefab)) { return null; } if (!prefabsReady) { RefreshPrefabs(); } prefabs.TryGetValue(pose.prefab, out var value); if (!Object.op_Implicit((Object)(object)value)) { if (missing.Add(pose.prefab)) { log("REMOTE_SPELL_ASSET_MISSING " + pose.prefab); } return null; } GameObject val = new GameObject("Fate.RemoteSpellVisual"); val.SetActive(false); try { GameObject val2 = Object.Instantiate<GameObject>(value, val.transform, false); val2.transform.localPosition = Vector3.zero; val2.transform.localRotation = Quaternion.identity; val2.transform.localScale = Vector3.one; Component[] componentsInChildren = val2.GetComponentsInChildren<Component>(true); foreach (Component val3 in componentsInChildren) { if (Object.op_Implicit((Object)(object)val3) && !(val3 is Transform) && !(val3 is Renderer) && !(val3 is MeshFilter) && !(val3 is ParticleSystem) && !(val3 is Animator) && !(val3 is Light) && !SpellVisualPolicy.AllowsScript(((object)val3).GetType().FullName)) { Object.DestroyImmediate((Object)(object)val3); } } Animator[] componentsInChildren2 = val2.GetComponentsInChildren<Animator>(true); foreach (Animator obj in componentsInChildren2) { obj.fireEvents = false; obj.applyRootMotion = false; } ParticleSystem[] componentsInChildren3 = val2.GetComponentsInChildren<ParticleSystem>(true); for (int j = 0; j < componentsInChildren3.Length; j++) { ParticleSystem val4 = componentsInChildren3[j]; CollisionModule collision = val4.collision; ((CollisionModule)(ref collision)).enabled = false; TriggerModule trigger = val4.trigger; ((TriggerModule)(ref trigger)).enabled = false; MainModule main = val4.main; ((MainModule)(ref main)).stopAction = (ParticleSystemStopAction)0; if (pose.particles != null && pose.particles.Length == componentsInChildren3.Length) { ((MainModule)(ref main)).loop = pose.particles[j].loop; if (pose.particles[j].seed != 0) { val4.randomSeed = pose.particles[j].seed; } } } componentsInChildren = val2.GetComponentsInChildren<Component>(true); foreach (Component val5 in componentsInChildren) { if ((val5 is MonoBehaviour && !SpellVisualPolicy.AllowsScript(((object)val5).GetType().FullName)) || val5 is Collider || val5 is Rigidbody) { throw new InvalidOperationException("Gameplay component survived spell visual filtering"); } } val.transform.position = new Vector3(pose.position[0], pose.position[1], pose.position[2]); val.transform.rotation = new Quaternion(pose.rotation[0], pose.rotation[1], pose.rotation[2], pose.rotation[3]); val.transform.localScale = new Vector3(pose.scale[0], pose.scale[1], pose.scale[2]); val.SetActive(true); log("REMOTE_SPELL_VISUAL_CREATED prefab=" + pose.prefab + " gameplay-scripts=0 colliders=0 audited-visual-helpers=allowed"); return val; } catch { Object.Destroy((Object)(object)val); throw; } } private static void Numbers(float[] values, int min, int max) { if (values == null || values.Length < min || values.Length > max || values.Any((float x) => float.IsNaN(x) || float.IsInfinity(x) || Math.Abs(x) > 100000f)) { throw new InvalidOperationException("Invalid visual numeric values"); } } private static void Validate(VisualMotion packet) { if (packet.actors == null || packet.actors.Length > 5 || packet.effects == null || packet.effects.Length > 64) { throw new InvalidOperationException("Invalid combat visual count"); } AnimationPose[] array = packet.actors; foreach (AnimationPose animationPose in array) { if (animationPose?.layers == null || animationPose.layers.Length > 8 || animationPose.values == null || animationPose.values.Length > 128 || float.IsNaN(animationPose.speed) || float.IsInfinity(animationPose.speed)) { throw new InvalidOperationException("Invalid animator pose"); } AnimationLayer[] layers = animationPose.layers; foreach (AnimationLayer animationLayer in layers) { if (animationLayer == null) { throw new InvalidOperationException("Invalid animation layer"); } Numbers(new float[2] { animationLayer.time, animationLayer.weight }, 2, 2); } AnimationValue[] values = animationPose.values; foreach (AnimationValue animationValue in values) { if (animationValue == null) { throw new InvalidOperationException("Invalid animation parameter"); } Numbers(new float[1] { animationValue.value }, 1, 1); } } EffectPose[] effects = packet.effects; foreach (EffectPose effectPose in effects) { if (effectPose == null || !Guid.TryParseExact(effectPose.id, "N", out var _) || string.IsNullOrEmpty(effectPose.prefab) || effectPose.prefab.Length > 256) { throw new InvalidOperationException("Invalid spell identity"); } Numbers(effectPose.position, 3, 3); Numbers(effectPose.rotation, 4, 4); Numbers(effectPose.scale, 3, 3); Numbers(new float[1] { effectPose.duration }, 1, 1); if (effectPose.particles != null && (effectPose.particles.Length > 128 || effectPose.particles.Any((ParticleConfig p) => p == null))) { throw new InvalidOperationException("Invalid particle configuration"); } } } internal void RemoveMember(string member) { receivedByMember.Remove(member); string[] array = remoteEffects.Keys.Where((string text) => text.StartsWith(member + ":", StringComparison.Ordinal)).ToArray(); foreach (string key in array) { if (Object.op_Implicit((Object)(object)remoteEffects[key])) { Object.Destroy((Object)(object)remoteEffects[key]); } remoteEffects.Remove(key); targets.Remove(key); } } private void Clear() { foreach (GameObject value in remoteEffects.Values) { if (Object.op_Implicit((Object)(object)value)) { Object.Destroy((Object)(object)value); } } foreach (GameObject oneShot in oneShots) { if (Object.op_Implicit((Object)(object)oneShot)) { Object.Destroy((Object)(object)oneShot); } } oneShots.Clear(); remoteEffects.Clear(); targets.Clear(); localEffects.Clear(); receivedByMember.Clear(); sourceAnimators.Clear(); sourceParameters.Clear(); sourceControllers.Clear(); lastSent = -10f; appliedRevision = revision; Status = "Combat visuals: waiting for same shared scene"; } } internal static class ConnectionHealth { internal const double StallSeconds = 2.0; internal const double DropSeconds = 5.0; internal const double SlowSeconds = 10.0; internal const double SlowRoundTripMs = 1000.0; internal static bool Block(double silence, double roundTrip, double slowFor) { if (!(silence >= 2.0)) { if (roundTrip >= 1000.0) { return slowFor >= 2.0; } return false; } return true; } internal static bool Drop(double silence, double roundTrip, double slowFor) { if (!(silence >= 5.0)) { if (roundTrip >= 1000.0) { return slowFor >= 10.0; } return false; } return true; } } [BepInPlugin("fate.local.diagnostics", "FATE Co-op", "0.20.6")] public sealed class Plugin : BaseUnityPlugin { private sealed class RemoteView { internal VisualAvatar avatar; internal VisualBundle appearance; internal readonly List<VisualAvatar> pets = new List<VisualAvatar>(); internal RemoteMinimap minimap; internal string name = "Teammate"; internal int partyClass; internal float nextAttempt; } private NativeCoopMenu nativeMenu; private float nextMenuRefresh; public const string Id = "fate.local.diagnostics"; private static Plugin self; private Harmony harmony; private static string saveDirectory; private StreamWriter events; private float nextPoll; private string status = "Waiting for player"; private string previous = ""; private int snapshotCount; private string snapshotFeedback = "No manual snapshots recorded yet."; private double feedbackUntil; private VisualAvatar visualAvatar; private string avatarFeedback = "Visual avatar: not spawned"; private int eventCount; private int frameCount; private double nextBudgetReset; private bool rateWarning; private Component cachedPlayer; private bool detailedEvents; private string instanceLabel = "SINGLE TEST"; private SessionPause sessionPause; private readonly LoopbackSession localSession = new LoopbackSession(); private bool appliedConnection; private bool appliedHosting; private bool gameplayLoading; private bool peerEstablished; private bool joinedAsHost; private bool connectionStalled; private bool recoveryPending; private bool recoveryStarted; private bool recoveryTown; private double slowSince = -1.0; private readonly Dictionary<string, double> peerSlowSince = new Dictionary<string, double>(); private readonly HashSet<string> stalledPeers = new HashSet<string>(); private string lastMemberSet; private string recoveryReason; private int sessionCampaign; private bool steamMode; private string joinCode = ""; private string connectionError = ""; private string localAddress = "127.0.0.1"; private string[] hostAddresses = new string[1] { "127.0.0.1" }; private int hostAddressIndex; private double codeCopiedUntil; private readonly Dictionary<string, RemoteView> remoteViews = new Dictionary<string, RemoteView>(); private float nextFrame; private Vector3 previousPosition; private bool hasPreviousPosition; private string remoteStatus = "No remote avatar"; private bool backgroundApplied; private bool previousRunInBackground; private SharedItems sharedItems; private SharedPortals sharedPortals; private PartyQuests partyQuests; private TeammateHud teammateHud; private SupportSpells supportSpells; private ReceiptSaveQueue receiptSaves; private string lastSentPlayerName; private int selectedClass; private int lastSentClass = -1; private readonly ClassMarkerTint localClassMarker = new ClassMarkerTint(); private ConfigEntry<int> classPreference; private DungeonDrag dungeonDrag; private SharedWorld sharedWorld; private CombatVisuals combatVisuals; private SharedActors sharedActors; private JoinerCombat joinerCombat; private RemoteMinimap remoteMinimap; private SharedFloors sharedFloors; private SharedDeath sharedDeath; private DownedState downedState; private HostMigrationState migrationState; private SpellAudio spellAudio; private readonly List<Component> localActors = new List<Component>(); private readonly Dictionary<int, Vector3> lastPetPositions = new Dictionary<int, Vector3>(); private string lastAppearanceSignature; private float nextAppearance; private float nextItemTick; private bool legacyInputAvailable = true; private float fpsElapsed; private int fpsFrames; private float fps; private float nextPerformanceLog; private static readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>(); private static readonly Dictionary<string, Object> ObjectCache = new Dictionary<string, Object>(); private readonly Stopwatch clock = Stopwatch.StartNew(); private readonly object logLock = new object(); private static readonly BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly string[] Hooks = new string[29] { "Tableflip.PlayerManager.RequestPlayerSpawn", "Tableflip.LevelGenerator.Create", "Tableflip.LevelGenerator.OnFinishCreation", "Tableflip.LevelGenerator.FinishLoadSave", "Tableflip.LevelGenerator.UpdateSaveData", "Tableflip.LevelGenerator.Remove", "Tableflip.Player.AttackFrameReached", "Tableflip.CharacterVitals.ApplyDamage", "Tableflip.CharacterVitals.Die", "Tableflip.Monster.Die", "Tableflip.Item.Drop", "Tableflip.DroppedItem.Interact", "Tableflip.Character.AddItemToInventory", "Tableflip.Character.RemoveItemFromInventory", "Tableflip.Character.ItemMovedInInventory", "Tableflip.Player.EquipItem", "Tableflip.Player.UnequipItem", "Tableflip.QuestManager.AcceptQuest", "Tableflip.QuestManager.ClaimRewards", "Tableflip.Quest.ClaimRewards", "Tableflip.Quest.GenerateObjective", "Tableflip.Quest.OnTargetMonsterKilled", "Tableflip.Quest.OnItemLooted", "Tableflip.LevelGenerator.ValidateQuestsPostLoad", "Tableflip.Character.AwardExperience", "Tableflip.SaveData.RequestSave", "Tableflip.SaveData.ParseSave", "Tivola.Framework.SaveManager.SaveAsync", "Tivola.Framework.SaveManager.LoadAsync" }; private static readonly string[] LightweightHooks = new string[5] { "Tableflip.PlayerManager.RequestPlayerSpawn", "Tableflip.LevelGenerator.OnFinishCreation", "Tableflip.LevelGenerator.FinishLoadSave", "Tableflip.LevelGenerator.Remove", "Tableflip.Quest.ClaimRewards" }; private static readonly HashSet<string> backedUpNormalSaves = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private GUIStyle indicatorStyle; private GUIStyle indicatorNameStyle; private Font indicatorFont; private static readonly Vector2[] haloDirections = (Vector2[])(object)new Vector2[8] { new Vector2(-1f, 0f), new Vector2(1f, 0f), new Vector2(0f, -1f), new Vector2(0f, 1f), new Vector2(-0.707f, -0.707f), new Vector2(0.707f, -0.707f), new Vector2(-0.707f, 0.707f), new Vector2(0.707f, 0.707f) }; internal static bool PanelOwnsInput { get { Plugin plugin = self; if (plugin == null) { return false; } return plugin.nativeMenu?.BlocksInput == true; } } private bool MenuAvailable { get { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) SessionPause obj = sessionPause; if (obj != null && obj.Ready && Object.op_Implicit((Object)(object)cachedPlayer) && !gameplayLoading) { Scene activeScene = SceneManager.GetActiveScene(); if (!SessionRules.Village(((Scene)(ref activeScene)).name)) { activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name == "Scene_Dungeon"; } return true; } return false; } } internal static bool ConnectionBlocked { get { if ((Object)(object)self != (Object)null) { if (!self.connectionStalled && !self.recoveryPending && !self.localSession.Migrating) { if (self.localSession.ContextPeer != null) { return self.stalledPeers.Contains(self.localSession.ContextPeer); } return false; } return true; } return false; } } internal static bool RecoveryPending => self?.recoveryPending ?? false; private string SelectedHostAddress => hostAddresses[hostAddressIndex % hostAddresses.Length]; private VisualAvatar remoteAvatar { get { return View(null).avatar; } set { View(null).avatar = value; } } private VisualBundle remoteAppearance { get { return View(null).appearance; } set { View(null).appearance = value; } } private List<VisualAvatar> remotePets => View(null).pets; private float nextRemoteAttempt { get { return View(null).nextAttempt; } set { View(null).nextAttempt = value; } } private string remotePlayerName { get { return View(null).name; } set { View(null).name = value; } } internal static bool FloorEntryPending { get { Plugin plugin = self; if (plugin == null || plugin.sharedFloors?.Loading != true) { return self?.localSession.Migrating ?? false; } return true; } } private bool UpdatePanelInput() { return nativeMenu?.BlocksInput ?? false; } private void ToggleCoopMenu() { if (MenuAvailable) { EnsureNativeMenu(); nativeMenu.SetOpen(!nativeMenu.IsOpen); nextMenuRefresh = 0f; } } private void EnsureNativeMenu() { if (nativeMenu != null) { return; } nativeMenu = new NativeCoopMenu(new CoopMenuActions { host = delegate { StartSession(hosting: true); }, join = delegate { StartSession(hosting: false); }, close = delegate { StopSession(); }, classChanged = delegate(int value) { selectedClass = PartyClass.Clamp(value); classPreference.Value = selectedClass; nextMenuRefresh = 0f; }, codeChanged = delegate(string value) { if (steamMode) { joinCode = value; } else { localAddress = value; } }, paste = delegate { string text = (GUIUtility.systemCopyBuffer ?? "").Trim(); if (steamMode) { joinCode = text; } else { localAddress = text; } nextMenuRefresh = 0f; }, copy = delegate { CopyLobbyCode(steamMode ? localSession.LobbyCode : (localSession.IsHosting ? SelectedHostAddress : localAddress)); nextMenuRefresh = 0f; }, nextAddress = delegate { hostAddressIndex = (hostAddressIndex + 1) % hostAddresses.Length; nextMenuRefresh = 0f; }, toggleTransport = delegate { if (!localSession.Busy) { steamMode = !steamMode; } nextMenuRefresh = 0f; } }, delegate(string message) { Log(message, essential: true); }); } private void TickNativeMenu() { //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) if (!MenuAvailable) { nativeMenu?.Dispose(); localClassMarker.Restore(); return; } bool flag = localSession.IsConnected && localSession.PeerIds.Length != 0; int num = (flag ? selectedClass : 0); localClassMarker.Tick(cachedPlayer.transform, num, flag && supportSpells?.Selected == localSession.MemberId, self: true, DownedState.Active); EnsureNativeMenu(); if (!(Time.unscaledTime < nextMenuRefresh)) { nextMenuRefresh = Time.unscaledTime + 0.2f; List<int> list = new List<int> { num }; List<string> list2 = new List<string> { LocalPlayerName() }; List<string> list3 = new List<string> { localSession.IsHosting ? "YOU / HOST" : "YOU" }; List<string> list4 = new List<string> { LocalLocation() }; string[] peerIds = localSession.PeerIds; foreach (string text in peerIds) { list.Add(View(text).partyClass); list2.Add(View(text).name); list3.Add((text == localSession.HostId) ? "HOST" : "ADVENTURER"); list4.Add(FrameLocation(localSession.Frame(text))); } Scene activeScene = SceneManager.GetActiveScene(); bool flag2 = SessionRules.Village(((Scene)(ref activeScene)).name); nativeMenu.Tick(ready: true, new CoopMenuState { multiplayer = flag, inVillage = flag2, classes = list.ToArray(), selectedClass = selectedClass, names = list2.ToArray(), roles = list3.ToArray(), locations = list4.ToArray(), hosting = localSession.IsHosting, busy = localSession.Busy, steam = steamMode, canConnect = (flag2 && !localSession.Busy), canJoin = (flag2 && !localSession.Busy && (!steamMode || !string.IsNullOrWhiteSpace(joinCode))), status = (localSession.Migrating ? "Choosing the next party leader…" : (localSession.IsConnected ? "Your party is ready for adventure." : (localSession.Busy ? "Your lobby is open. Gather your party." : "An adventure is better with company."))), code = ((!steamMode) ? (localSession.IsHosting ? SelectedHostAddress : localAddress) : (localSession.Busy ? localSession.LobbyCode : joinCode)), addressCount = hostAddresses.Length, addressIndex = hostAddressIndex, note = (steamMode ? ((clock.Elapsed.TotalSeconds < codeCopiedUntil) ? "Code copied. Send it to your friends." : ((!flag2) ? "Return to the village to host or join." : (localSession.Busy ? "Share this code. Friends join from their village." : "Up to four players. Everyone joins from the village."))) : ((clock.Elapsed.TotalSeconds < codeCopiedUntil) ? "IP copied." : (localSession.IsHosting ? "127.0.0.1: this PC. Next IP: other networks. TCP 28761." : "Enter the host's IPv4; use 127.0.0.1 on this PC. TCP 28761."))), error = connectionError, footer = "OPEN ALPHA 0.20.6 / F8 or Escape to close" }); } } private RemoteView View(string id) { id = id ?? localSession.ContextPeer ?? localSession.PeerIds.FirstOrDefault() ?? "pending"; if (!remoteViews.TryGetValue(id, out var value)) { value = new RemoteView { minimap = new RemoteMinimap(delegate(string message) { Log(message, essential: true); }) }; remoteViews[id] = value; } return value; } internal static void SaveReceipt(Action saved) { self.receiptSaves.Request(saved); } private void Awake() { //IL_0648: Unknown result type (might be due to invalid IL or missing references) //IL_0656: Expected O, but got Unknown //IL_047d: Unknown result type (might be due to invalid IL or missing references) //IL_048a: Expected O, but got Unknown //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Expected O, but got Unknown //IL_07e7: Unknown result type (might be due to invalid IL or missing references) //IL_07f5: Expected O, but got Unknown //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Expected O, but got Unknown //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Expected O, but got Unknown self = this; try { Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().First((Assembly a) => a.GetName().Name == "Assembly-CSharp"); string text = Hash(assembly.Location); if (text != "4590187091e0e8f064759ce9be534ebac862281744a19bdb030b8293dae5f72b") { throw new InvalidOperationException("Unsupported game build; diagnostics disabled. Do not use this build for copied-save testing."); } string value = ((BaseUnityPlugin)this).Config.Bind<string>("Testing", "SaveDirectory", "", "Optional dedicated copied-save directory. Empty uses normal game saves with an automatic backup.").Value; bool flag = !string.IsNullOrWhiteSpace(value); saveDirectory = (flag ? Path.GetFullPath(value) : Path.Combine(Paths.BepInExRootPath, "save-backups")); steamMode = ((BaseUnityPlugin)this).Config.Bind<bool>("Networking", "UseSteam", !flag, "Use Steam lobby networking. Disable for same-PC or local network play.").Value; localAddress = ((BaseUnityPlugin)this).Config.Bind<string>("Networking", "LocalHostAddress", "127.0.0.1", "Host IPv4 address for Local play joining. Use the host local network IPv4, or 127.0.0.1 for same-PC tests. TCP port 28761.").Value; instanceLabel = ((BaseUnityPlugin)this).Config.Bind<string>("Testing", "InstanceLabel", "SINGLE TEST", "Visible test-copy role; does not establish a network connection.").Value; classPreference = ((BaseUnityPlugin)this).Config.Bind<int>("Appearance", "CosmeticClass", 0, "Cosmetic role: 0 Unassigned, 1 Wizard, 2 Fighter, 3 Archer, 4 Assassin. Does not affect gameplay."); selectedClass = PartyClass.Clamp(classPreference.Value); detailedEvents = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "DetailedEvents", false, "Enable frequent combat/inventory hooks for a dedicated trace. Restart required; leave off for normal performance testing.").Value; if (flag && (!Directory.Exists(saveDirectory) || !File.Exists(Path.Combine(saveDirectory, "FateMainSave.save")))) { throw new InvalidOperationException("Copied test save is missing: " + saveDirectory); } MethodInfo method = assembly.GetType("Tivola.Framework.SaveManagerStandalone", throwOnError: true).GetMethod("GetFilePath", Flags, null, new Type[1] { typeof(string) }, null); if (method == null || method.ReturnType != typeof(string)) { throw new MissingMethodException("Save path hook missing"); } if ((bool)assembly.GetType("Tivola.Framework.SaveManager", throwOnError: true).GetProperty("HasEverLoaded", Flags).GetValue(null, null)) { throw new InvalidOperationException("Save backend already loaded; copied-save isolation cannot be established."); } harmony = new Harmony("fate.local.diagnostics"); if (flag) { harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(Plugin), "SavePathPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { harmony.Patch((MethodBase)method, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), "NormalSavePathObserved", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } string text2 = Path.Combine(Paths.BepInExRootPath, "diagnostics"); Directory.CreateDirectory(text2); events = new StreamWriter(Path.Combine(text2, "session-" + DateTime.UtcNow.ToString("yyyyMMdd-HHmmss") + ".log"), append: false); Log("START unity=" + Application.unityVersion + " assembly=" + text + " copied-save=" + saveDirectory); Log("MODE " + (detailedEvents ? "detailed events" : "lightweight: manual snapshots and lifecycle events")); Log("INSTANCE " + instanceLabel + " networking=not connected"); VisualAppearance.SelfTest(); Log("APPEARANCE_CODEC_SELFTEST passed", essential: true); string[] array = (detailedEvents ? Hooks : LightweightHooks); foreach (string text3 in array) { int num2 = text3.LastIndexOf('.'); Type type = assembly.GetType(text3.Substring(0, num2)); string name = text3.Substring(num2 + 1); MethodInfo[] array2 = (from m in type?.GetMethods(Flags) where m.DeclaringType == type && m.Name == name && !m.IsAbstract && !m.ContainsGenericParameters select m).ToArray(); if (array2 == null || array2.Length == 0) { Log("MISSING " + text3); continue; } MethodInfo[] array3 = array2; foreach (MethodInfo methodInfo in array3) { try { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Plugin), methodInfo.IsStatic ? "StaticObserved" : "Observed", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log("HOOK " + text3 + " token=" + methodInfo.MetadataToken.ToString("X8") + " return=" + methodInfo.ReturnType.Name + " args=" + string.Join(",", from p in methodInfo.GetParameters() select p.ParameterType.Name)); } catch (Exception ex) { Log("HOOK_FAILED " + text3 + " " + ex.GetType().Name); } } } SceneManager.sceneLoaded += SceneLoaded; teammateHud = new TeammateHud(assembly, localSession, () => cachedPlayer, () => MenuAvailable && !FloorEntryPending && !ConnectionBlocked, delegate(string message) { Log(message, essential: true); }) { CosmeticClass = () => selectedClass }; try { sessionPause = new SessionPause(harmony, assembly, delegate(string message) { Log(message, essential: true); }); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogError((object)("Menu pause feature disabled: " + ex2)); } MethodInfo methodInfo2 = AccessTools.Method(assembly.GetType("Tableflip.LevelGenerator"), "Create", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(Plugin), "BeginLevelLoading", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } try { sharedItems = new SharedItems(harmony, assembly, localSession, () => cachedPlayer, CurrentCampaign, delegate { //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) Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name; }, () => sessionPause != null && sessionPause.Ready && !gameplayLoading, SharedScope, Path.Combine(text2, "item-transactions.jsonl"), delegate(string message) { Log(message, essential: true); }); } catch (Exception ex3) { ((BaseUnityPlugin)this).Logger.LogError((object)("Shared item feature unavailable: " + ex3)); } try { sharedPortals = new SharedPortals(harmony, assembly, localSession, SharedScope, delegate(string message) { Log(message, essential: true); }); } catch (Exception ex4) { ((BaseUnityPlugin)this).Logger.LogError((object)("Shared portals unavailable: " + ex4)); } array = new string[3] { "Tableflip.UniqueIdleAnimationState", "Tableflip.AttackAnimationState", "Tableflip.DeathAnimationState" }; foreach (string name2 in array) { foreach (MethodInfo item in from m in assembly.GetType(name2, throwOnError: true).GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) where m.Name.StartsWith("OnState", StringComparison.Ordinal) && m.GetParameters().Length == 3 && m.GetParameters()[0].ParameterType == typeof(Animator) select m) { harmony.Patch((MethodBase)item, new HarmonyMethod(typeof(Plugin), "VisualIdleGuard", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } new VillageEntryGate(harmony, assembly, localSession, delegate(string message) { Log(message, essential: true); }); try { dungeonDrag = new DungeonDrag(harmony, assembly, localSession, () => sessionPause != null && sessionPause.Ready && !gameplayLoading, CurrentCampaign, () => saveDirectory, delegate(string message) { Log(message, essential: true); }); } catch (Exception ex5) { ((BaseUnityPlugin)this).Logger.LogError((object)("Dungeon drag unavailable: " + ex5)); } try { supportSpells = new SupportSpells(harmony, assembly, localSession, () => cachedPlayer, SupportScope, delegate(string message) { Log(message, essential: true); }); supportSpells.PickModel = (Ray ray) => (from p in remoteViews where p.Value.avatar != null select new { id = p.Key, distance = p.Value.avatar.PickDistance(ray) } into p where p.distance.HasValue orderby p.distance.Value select p.id).FirstOrDefault(); teammateHud.SelectTarget = delegate(string id) { supportSpells.Select(id); }; teammateHud.SelectedTarget = () => supportSpells.Selected; teammateHud.CastTarget = delegate(string id) { supportSpells.CastSelected(id); }; teammateHud.SameSpace = (string id) => supportSpells.IsMemberHere(id); } catch (Exception ex6) { ((BaseUnityPlugin)this).Logger.LogError((object)("Support spell feature unavailable: " + ex6)); } try { downedState = new DownedState(harmony, assembly, localSession, () => cachedPlayer, SupportScope, delegate(string message) { Log(message, essential: true); }); } catch (Exception ex7) { ((BaseUnityPlugin)this).Logger.LogError((object)("Downed feature unavailable: " + ex7)); } try { if (dungeonDrag != null) { sharedFloors = new SharedFloors(harmony, assembly, localSession, dungeonDrag, () => sessionPause != null && sessionPause.Ready && !gameplayLoading, CurrentCampaign, delegate(string message) { Log(message, essential: true); }); } } catch (Exception ex8) { ((BaseUnityPlugin)this).Logger.LogError((object)("Individual floor synchronization unavailable: " + ex8)); } try { sharedWorld = new SharedWorld(harmony, assembly, localSession, SharedScope, () => cachedPlayer, delegate(string message) { Log(message, essential: true); }); } catch (Exception ex9) { ((BaseUnityPlugin)this).Logger.LogError((object)("Shared objects unavailable: " + ex9)); } try { combatVisuals = new CombatVisuals(harmony, assembly, localSession, SharedScope, () => localActors, delegate(string message) { Log(message, essential: true); }); } catch (Exception ex10) { ((BaseUnityPlugin)this).Logger.LogError((object)("Combat visuals unavailable: " + ex10)); } try { spellAudio = new SpellAudio(harmony, assembly, localSession, SharedScope, (object actor) => (combatVisuals != null && combatVisuals.IsLocal(actor)) || (sharedActors?.OwnsAudio(actor) ?? false), delegate(string message) { Log(message, essential: true); }); } catch (Exception ex11) { ((BaseUnityPlugin)this).Logger.LogError((object)("Spell audio unavailable: " + ex11)); } try { sharedActors = new SharedActors(harmony, assembly, localSession, SharedScope, () => cachedPlayer, (object actor) => combatVisuals != null && combatVisuals.IsLocal(actor), (Component actor) => combatVisuals.CaptureWorld(actor), delegate(string message) { Log(message, essential: true); }); } catch (Exception ex12) { ((BaseUnityPlugin)this).Logger.LogError((object)("NPC/enemy synchronization unavailable: " + ex12)); } remoteMinimap = new RemoteMinimap(delegate(string message) { Log(message, essential: true); }); try { if (sharedActors != null) { joinerCombat = new JoinerCombat(harmony, assembly, localSession, SharedScope, () => cachedPlayer, sharedActors, delegate(string message) { Log(message, essential: true); }); } } catch (Exception ex13) { ((BaseUnityPlugin)this).Logger.LogError((object)("Joiner combat unavailable: " + ex13)); } try { partyQuests = new PartyQuests(harmony, assembly, localSession, () => cachedPlayer, CurrentCampaign, text2, delegate(string message) { Log(message, essential: true); }); } catch (Exception ex14) { ((BaseUnityPlugin)this).Logger.LogError((object)("Party quests unavailable: " + ex14)); } VillageEntryGate.QuestReady = () => partyQuests?.Ready ?? false; if (sharedFloors != null && partyQuests != null) { sharedFloors.QuestsReady = () => partyQuests.Ready; sharedFloors.NeedsQuestRecovery = partyQuests.NeedsFloorRecovery; partyQuests.RecoverFloor = sharedFloors.RequestQuestRecovery; partyQuests.HealthyFloor = sharedFloors.ReportHealthyFloor; } receiptSaves = new ReceiptSaveQueue(() => Convert.ToBoolean(AccessTools.Field(NativeTypes.Get("Tableflip.SaveData"), "isSaving").GetValue(null)), delegate(Action done) { AccessTools.Method(NativeTypes.Get("Tableflip.SaveData"), "RequestSave", (Type[])null, (Type[])null).Invoke(null, new object[1] { done }); }, delegate(Exception ex17) { Log("RECEIPT_SAVE_FAILED " + ex17.GetBaseException(), essential: true); }); if (partyQuests != null) { partyQuests.MemberColor = (string member) => PartyClass.NameTint(View(member).partyClass); partyQuests.MemberName = (string member) => (member != null) ? View(member).name : "Teammate"; partyQuests.PeerFollower = (string member) => joinerCombat?.TargetFor(member); if (sharedItems != null) { partyQuests.GrantItemReward = delegate(QuestReward reward, Action saved) { sharedItems.GrantQuestReward(reward, saved); }; } } if (sharedFloors != null) { sharedItems.Expedition = () => dungeonDrag?.Expedition; sharedItems.QuestItemKey = (object item) => partyQuests?.ItemObjective(item); sharedItems.QuestItemOwners = (object item) => partyQuests?.ItemOwners(item); sharedItems.QuestOwnerForMember = (string member) => partyQuests?.OwnerForMember(member); dungeonDrag.CaptureGold = (WorldScope active) => sharedItems.CaptureDragGold(active); dungeonDrag.ApplyGold = delegate(WorldScope active, DropPacket[] drops) { sharedItems.ApplySavedGold(active, drops); }; sharedFloors.ApplyGold = delegate(WorldScope active, DropPacket[] drops) { sharedItems.ApplySavedGold(active, drops); }; sharedFloors.FilterGold = (DropPacket[] drops) => sharedItems.FilterSavedGold(drops); sharedFloors.FilterItems = (FloorPacket packet) => sharedItems.FilterFloorSnapshot(packet); sharedFloors.CaptureArrival = delegate(WorldScope active) { FloorArrival floorArrival = new FloorArrival(); sharedActors.CaptureArrival(floorArrival, active); floorArrival.objects = sharedWorld.CaptureArrival(active); floorArrival.drops = sharedItems.CaptureArrival(active); return floorArrival; }; sharedFloors.ApplyArrival = delegate(WorldScope active, FloorArrival packet) { sharedActors.ApplyArrival(packet); sharedWorld.ApplyArrival(active, packet.objects); sharedItems.ApplyArrival(active, packet.drops); }; } if (sharedFloors != null) { try { sharedDeath = new SharedDeath(harmony, assembly, localSession, sharedFloors, delegate(string message) { Log(message, essential: true); }); } catch (Exception ex15) { ((BaseUnityPlugin)this).Logger.LogError((object)("Shared death unavailable: " + ex15)); } } migrationState = new HostMigrationState(localSession, Path.Combine(text2, "migration"), delegate { Dictionary<string, string> dictionary = sharedFloors?.ExportAuthorityParts() ?? new Dictionary<string, string>(); if (sharedItems != null) { dictionary["items"] = sharedItems.ExportAuthority(); } if (partyQuests != null) { dictionary["quests"] = partyQuests.ExportAuthority(); } return dictionary; }, delegate(string key, string body) { if (key == "items") { sharedItems.ImportAuthority(body); } else if (key == "quests") { partyQuests.ImportAuthority(body); } else { sharedFloors.ImportAuthority(body); } }, delegate(string message) { Log(message, essential: true); }); if (partyQuests != null) { partyQuests.MigrationFence = delegate(Action action) { migrationState.Fence(action); }; } EnsureDriver(); } catch (Exception ex16) { ((BaseUnityPlugin)this).Logger.LogError((object)ex16); ((Behaviour)this).enabled = false; } } private static void NormalSavePathObserved(string __result) { if (string.IsNullOrWhiteSpace(__result)) { throw new IOException("Native save path was empty"); } string fullPath = Path.GetFullPath(__result); saveDirectory = Path.GetDirectoryName(fullPath); if (File.Exists(fullPath) && !backedUpNormalSaves.Contains(fullPath)) { string text = Path.Combine(Paths.BepInExRootPath, "save-backups", DateTime.UtcNow.ToString("yyyyMMdd-HHmmss-fffffff")); Directory.CreateDirectory(text); string text2 = Path.Combine(text, Path.GetFileName(fullPath)); File.Copy(fullPath, text2, overwrite: false); if (Hash(fullPath) != Hash(text2)) { throw new IOException("Automatic normal-save backup verification failed"); } backedUpNormalSaves.Add(fullPath); self.Log("NORMAL_SAVE_BACKUP source=" + fullPath + " backup=" + text2, essential: true); } } private static bool SavePathPrefix(string __0, ref string __result) { if (string.IsNullOrEmpty(__0) || Path.GetFileName(__0) != __0 || __0.Contains("..")) { throw new InvalidOperationException("Unexpected save filename"); } __result = Path.Combine(saveDirectory, __0 + ".save"); self.Log("SAVE_PATH " + __result); return false; } private static void StaticObserved(MethodBase __originalMethod, object[] __args) { Observe(__originalMethod, null, __args); } private static bool VisualIdleGuard(Animator __0) { if (Object.op_Implicit((Object)(object)__0) && Object.op_Implicit((Object)(object)((Component)__0).GetComponent(NativeTypes.Get("Tableflip.Character")))) { SharedActors obj = self.sharedActors; if (obj == null) { return true; } return !obj.IsReplicaAnimator(__0); } return false; } private static void Observed(MethodBase __originalMethod, object __instance, object[] __args) { Observe(__originalMethod, __instance, __args); } private static void Observe(MethodBase method, object instance, object[] args) { try { if (method.DeclaringType.FullName == "Tableflip.PlayerManager" && method.Name == "RequestPlayerSpawn") { ObjectCache.Clear(); EnsureDriver((Component)((instance is Component) ? instance : null)); } if (method.DeclaringType.FullName == "Tableflip.LevelGenerator" && (method.Name == "OnFinishCreation" || method.Name == "FinishLoadSave")) { self.gameplayLoading = false; } self.Log("CALL " + method.DeclaringType.FullName + "." + method.Name + " object=" + Describe(instance) + " args=[" + string.Join(";", args.Select(Describe)) + "]"); } catch (Exception ex) { ((BaseUnityPlugin)self).Logger.LogWarning((object)("Observation failed: " + ex.GetType().Name)); } } private static void BeginLevelLoading() { self.gameplayLoading = true; self.localSession.SubmitFrame(PlayerFrame.Unavailable); self.RemoveAllRemoteAvatars(); self.remoteMinimap?.Dispose(); self.sharedItems?.ReleaseDungeonObjects(); PartyQuests.ReleaseSceneActors(); VisualAppearance.ClearAssets(); } private unsafe void SceneLoaded(Scene scene, LoadSceneMode mode) { if (recoveryStarted && SessionRules.Village(((Scene)(ref scene)).name)) { recoveryPending = (recoveryStarted = false); recoveryReason = null; } if (SessionRules.Village(((Scene)(ref scene)).name)) { sharedItems?.ReleaseDungeonObjects(); } if (((Scene)(ref scene)).name.Contains("MainMenu") || ((Scene)(ref scene)).name == "Scene_GameSelect" || ((Scene)(ref scene)).name == "Scene_Engagement" || ((Scene)(ref scene)).name == "Scene_Startup") { StopSession(); } sessionPause?.ResetScene(); gameplayLoading = ((Scene)(ref scene)).name == "Scene_Dungeon"; localSession.SubmitFrame(PlayerFrame.Unavailable); ObjectCache.Clear(); cachedPlayer = null; hasPreviousPosition = false; lastSentPlayerName = null; lastSentClass = -1; lastAppearanceSignature = null; nextAppearance = 0f; RemoveVisualAvatar(); RemoveAllRemoteAvatars(); EnsureDriver(); Log("SCENE " + ((Scene)(ref scene)).name + " mode=" + ((object)(*(LoadSceneMode*)(&mode))/*cast due to .constrained prefix*/).ToString()); remoteMinimap?.Dispose(); VisualAppearance.ClearAssets(); } private static void EnsureDriver(Component anchor = null) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)Object.FindObjectOfType<DiagnosticsDriver>())) { if (!Object.op_Implicit((Object)(object)anchor)) { object obj = Find("Tableflip.PlayerManager"); anchor = (Component)((obj is Component) ? obj : null); } GameObject val = (GameObject)(Object.op_Implicit((Object)(object)anchor) ? ((object)anchor.gameObject) : ((object)new GameObject("FateDiagnostics.Driver"))); if (!Object.op_Implicit((Object)(object)anchor)) { Object.DontDestroyOnLoad((Object)(object)val); } val.AddComponent<DiagnosticsDriver>(); self.Log("DRIVER attached to " + ((Object)val).name); } } internal static void DriverUpdate() { self?.Poll(); } internal static void DriverGUI() { self?.DrawTeammateIndicator(); self?.DrawConnectionRecovery(); DownedState.Draw(); SessionPause.DrawPetReturnNotice(); } internal static void DriverQuit() { self?.localSession.Dispose(); self?.FlushLogs(); } private void Poll() { //IL_116e: Unknown result type (might be due to invalid IL or missing references) //IL_1173: Unknown result type (might be due to invalid IL or missing references) //IL_0f36: Unknown result type (might be due to invalid IL or missing references) //IL_0f3b: Unknown result type (might be due to invalid IL or missing references) //IL_11ab: Unknown result type (might be due to invalid IL or missing references) //IL_11b0: Unknown result type (might be due to invalid IL or missing references) UpdatePanelInput(); try { localSession.Tick(); } catch (Exception ex) { Log("TRANSPORT_TICK_FAILED " + ex.GetBaseException().Message, essential: true); } string text = string.Join(",", localSession.PeerIds); if (text != lastMemberSet) { lastMemberSet = text; lastAppearanceSignature = null; lastSentPlayerName = null; lastSentClass = -1; nextAppearance = 0f; } try { migrationState?.Tick(clock.Elapsed.TotalSeconds); } catch (Exception ex2) { Log("HOST_MIGRATION_STATE_FAILED " + ex2.GetBaseException(), essential: true); } CheckConnectionHealth(); receiptSaves?.Tick(); string result; while (localSession.Events.TryDequeue(out result)) { Log("LOCAL_CONNECTION " + result, essential: true); lastAppearanceSignature = null; lastSentPlayerName = null; lastSentClass = -1; nextAppearance = 0f; } string[] array = remoteViews.Keys.Where((string id) => id != "pending" && !localSession.PeerIds.Contains(id)).ToArray(); foreach (string text2 in array) { DisposeView(remoteViews[text2]); remoteViews.Remove(text2); combatVisuals?.RemoveMember(text2); } while (true) { if (!localSession.Messages.TryDequeue(out var message)) { break; } if (message.Source != localSession.HostId && !localSession.PeerIds.Contains(message.Source)) { Log("STALE_MEMBER_MESSAGE " + message.Kind, essential: true); continue; } localSession.ContextPeer = message.Source; try { if (localSession.IsHosting && (message.Kind == "appearance" || message.Kind == "player-name" || message.Kind == "player-class" || message.Kind.StartsWith("visual-", StringComparison.Ordinal) || message.Kind == "spell-sound")) { localSession.Relay(message); } if (message.Kind == "party-hud") { teammateHud.Receive(message); if (localSession.IsHosting) { localSession.Relay(message); } continue; } if (localSession.IsHosting && !string.IsNullOrEmpty(message.Recipient) && message.Recipient != localSession.MemberId && (message.Kind == "actor-hit" || message.Kind.StartsWith("joiner-", StringComparison.Ordinal))) { WorldScope worldScope = ((message.Kind == "joiner-defense") ? PayloadCodec.Decode<JoinerDefense>(message.Data).scope : PayloadCodec.Decode<ActorHit>(message.Data).scope); string text3 = ((worldScope == null) ? null : localSession.Authority(worldScope.scene, worldScope.campaign, worldScope.realm, worldScope.depth)); PlayerFrame playerFrame = localSession.Frame(message.Source); PlayerFrame playerFrame2 = localSession.Frame(message.Recipient); if (worldScope == null || !playerFrame.Ready || playerFrame.Scene != worldScope.scene || playerFram