Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of PortalPeek v1.0.0
plugins/PortalPeek.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Unity.Collections; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = "")] [assembly: AssemblyCompany("PortalPeek")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("PortalPeek")] [assembly: AssemblyTitle("PortalPeek")] [assembly: AssemblyVersion("1.0.0.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 PortalPeek { internal static class PeekCapturer { private sealed class InRange { public float Since; public string Key; } private sealed class FrameJob { public byte[] Pixels; public int Faces; public int FacesDone; public int Returned; public bool Failed; public float Time; public Task<byte[]> Encode; } private sealed class DepthRig { private Camera m_cam; private Shader m_shader; private int m_n; private float m_maxDepth; private float[] m_depth; private int m_requested; private int m_returned; public bool Done => m_returned >= m_requested; public static DepthRig Create(Camera main, Transform parent, int n, float maxDepth, float[] depth) { //IL_0015: 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) //IL_005e: Unknown result type (might be due to invalid IL or missing references) Shader depth2 = Shaders.Depth; if (!Object.op_Implicit((Object)(object)depth2)) { return null; } GameObject val = new GameObject("Depth"); val.transform.SetParent(parent, false); Camera val2 = val.AddComponent<Camera>(); val2.CopyFrom(main); ((Behaviour)val2).enabled = false; val2.renderingPath = (RenderingPath)1; val2.clearFlags = (CameraClearFlags)2; val2.backgroundColor = new Color(0.5f, 0.5f, 1f, 1f); val2.allowHDR = false; val2.allowMSAA = false; val2.depthTextureMode = (DepthTextureMode)0; val2.cullingMask = main.cullingMask & ~LayerMask.GetMask(new string[13] { "character", "character_net", "character_ghost", "character_noenv", "character_trigger", "hitbox", "ghost", "effect", "smoke", "Water", "WaterVolume", "UI", "viewblock" }); val2.nearClipPlane = 0.05f; val2.farClipPlane = maxDepth; val2.aspect = 1f; val2.fieldOfView = 2f * Mathf.Atan(((float)n + 1f) / (float)n) * 57.29578f; return new DepthRig { m_cam = val2, m_shader = depth2, m_n = n, m_maxDepth = maxDepth, m_depth = depth }; } public void Face(Vector3 origin, Quaternion rot, int f) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) int n = m_n; int side = n + 1; RenderTexture rt = RenderTexture.GetTemporary(side, side, 24, (RenderTextureFormat)0, (RenderTextureReadWrite)1); try { m_cam.targetTexture = rt; ((Component)m_cam).transform.SetPositionAndRotation(origin, rot * CubeFaces.Rotation(f)); m_cam.RenderWithShader(m_shader, "RenderType"); } catch (Exception ex) { PortalPeekPlugin.Log.LogWarning((object)("GPU depth pass failed for a face, using raycasts there: " + ex.Message)); RenderTexture.ReleaseTemporary(rt); return; } finally { if (Object.op_Implicit((Object)(object)m_cam)) { m_cam.targetTexture = null; } } m_requested++; float[] depth = m_depth; float maxDepth = m_maxDepth; AsyncGPUReadback.Request((Texture)(object)rt, 0, (TextureFormat)4, (Action<AsyncGPUReadbackRequest>)delegate(AsyncGPUReadbackRequest req) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) RenderTexture.ReleaseTemporary(rt); m_returned++; if (!((AsyncGPUReadbackRequest)(ref req)).hasError) { NativeArray<byte> data = ((AsyncGPUReadbackRequest)(ref req)).GetData<byte>(0); if (data.Length >= side * side * 4) { int num = side * side; for (int i = 0; i < side; i++) { for (int j = 0; j < side; j++) { int num2 = (i * side + j) * 4; float num3 = (float)(int)data[num2 + 2] / 255f + (float)(int)data[num2 + 3] / 65025f; if (!(num3 >= 0.995f)) { Vector3 val = CubeFaces.Dir(f, (float)j * 2f / (float)n - 1f, (float)i * 2f / (float)n - 1f); float magnitude = ((Vector3)(ref val)).magnitude; int num4 = f * num + i * side + j; depth[num4] = Mathf.Min(depth[num4], num3 * maxDepth * magnitude); } } } } } }); } public void Dispose() { if (Object.op_Implicit((Object)(object)m_cam)) { Object.Destroy((Object)(object)((Component)m_cam).gameObject); } m_cam = null; } } private const float CheckCooldown = 60f; private const float RetryAfterCancel = 10f; private const int RayBuffer = 32; private static bool s_busy; private static int s_session; private static PeekPortal s_departing; private static bool s_departTried; private static PeekPortal s_capturing; private static readonly Dictionary<string, float> s_lastCheck = new Dictionary<string, float>(); private static readonly Dictionary<int, InRange> s_inRange = new Dictionary<int, InRange>(); private static readonly HashSet<int> s_seen = new HashSet<int>(); private static readonly List<int> s_gone = new List<int>(); private static readonly RaycastHit[] s_hits = (RaycastHit[])(object)new RaycastHit[32]; private static readonly FieldRef<Player, GameObject> s_placementGhost = AccessTools.FieldRefAccess<Player, GameObject>("m_placementGhost"); private const float ManualRange = 25f; private const int MaxEncoders = 2; private static readonly Stack<byte[]> s_pixelPool = new Stack<byte[]>(); private const int RaysPerFrame = 1200; private static readonly List<Light> s_hiddenLights = new List<Light>(); public static void Abort() { s_session++; s_busy = false; s_capturing = null; s_departing = null; s_inRange.Clear(); s_lastCheck.Clear(); } public static void OnDeparting(PeekPortal portal) { s_departing = portal; s_departTried = false; TryDepartureCapture(); } private static void TryDepartureCapture() { //IL_007e: Unknown result type (might be due to invalid IL or missing references) if (s_departTried || !Object.op_Implicit((Object)(object)s_departing)) { return; } if (s_busy) { if ((Object)(object)s_capturing == (Object)(object)s_departing) { s_departTried = true; } return; } s_departTried = true; PeekPortal peekPortal = s_departing; if (Cfg.Enabled.Value && Cfg.CaptureEnabled.Value && Object.op_Implicit((Object)(object)ZNetScene.instance) && PeekLibrary.Ready && peekPortal.IsConnected && ZNetScene.instance.IsAreaReady(((Component)peekPortal).transform.position)) { string text = PortalKey.For(peekPortal.Zdo); if (text != null && PeekLibrary.NeedsCaptureNow(text)) { s_lastCheck[text] = Time.time; ((MonoBehaviour)Runner.Instance).StartCoroutine(Capture(peekPortal, text, manual: false)); } } } private static bool Rushing(PeekPortal portal) { Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)portal) && (Object)(object)s_departing == (Object)(object)portal && Object.op_Implicit((Object)(object)localPlayer)) { return ((Character)localPlayer).IsTeleporting(); } return false; } public static void Update() { //IL_009e: 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_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) if (!Cfg.Enabled.Value || !Cfg.CaptureEnabled.Value || !Object.op_Implicit((Object)(object)ZNetScene.instance) || !PeekLibrary.Ready) { return; } Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || ((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting()) { s_inRange.Clear(); if (Object.op_Implicit((Object)(object)localPlayer) && ((Character)localPlayer).IsTeleporting()) { TryDepartureCapture(); } else { s_departing = null; } return; } s_departing = null; float time = Time.time; float value = Cfg.SettleSeconds.Value; float num = Sq(Cfg.CaptureRadius.Value); Vector3 position = ((Component)localPlayer).transform.position; PeekPortal peekPortal = null; string key = null; float num2 = float.MaxValue; s_seen.Clear(); foreach (PeekPortal item in PeekPortal.All) { if (!Object.op_Implicit((Object)(object)item) || !item.IsConnected) { continue; } Vector3 val = item.CaptureOriginWorld - position; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude >= num) { continue; } int instanceID = ((Object)item).GetInstanceID(); s_seen.Add(instanceID); if (!s_inRange.TryGetValue(instanceID, out var value2)) { string text = PortalKey.For(item.Zdo); if (text != null) { s_inRange[instanceID] = new InRange { Since = time, Key = text }; } } else if (!ZNetScene.instance.IsAreaReady(((Component)item).transform.position)) { value2.Since = time; } else { if (s_busy || time - value2.Since < value || sqrMagnitude >= num2) { continue; } string key2 = value2.Key; if (s_lastCheck.TryGetValue(key2, out var value3) && time - value3 < 60f) { continue; } if (!PeekLibrary.NeedsCapture(key2, force: false)) { if (!PeekLibrary.IsLoading(key2)) { s_lastCheck[key2] = time; } } else { peekPortal = item; key = key2; num2 = sqrMagnitude; } } } s_gone.Clear(); foreach (int key3 in s_inRange.Keys) { if (!s_seen.Contains(key3)) { s_gone.Add(key3); } } foreach (int item2 in s_gone) { s_inRange.Remove(item2); } if (Object.op_Implicit((Object)(object)peekPortal)) { s_lastCheck[key] = time; ((MonoBehaviour)Runner.Instance).StartCoroutine(Capture(peekPortal, key, manual: false)); } } public static void ForceNearest() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || !Cfg.Enabled.Value || !PeekLibrary.Ready) { return; } if (s_busy) { Notify("Portal Peek: already capturing"); return; } PeekPortal peekPortal = Nearest(((Component)localPlayer).transform.position, 25f); if (!Object.op_Implicit((Object)(object)peekPortal)) { Notify("Portal Peek: no connected portal nearby"); return; } string text = PortalKey.For(peekPortal.Zdo); if (text != null) { s_lastCheck[text] = Time.time; ((MonoBehaviour)Runner.Instance).StartCoroutine(Capture(peekPortal, text, manual: true)); } } private static void Notify(string text) { Player localPlayer = Player.m_localPlayer; if (Object.op_Implicit((Object)(object)localPlayer)) { ((Character)localPlayer).Message((MessageType)1, text, 0, (Sprite)null, false); } } private static PeekPortal Nearest(Vector3 pos, float radius) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: 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_0037: Unknown result type (might be due to invalid IL or missing references) PeekPortal result = null; float num = radius * radius; foreach (PeekPortal item in PeekPortal.All) { if (Object.op_Implicit((Object)(object)item) && item.IsConnected) { Vector3 val = item.CaptureOriginWorld - pos; float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; result = item; } } } return result; } private static bool StillValid(int session, PeekPortal portal, Vector3 origin, out string why) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) why = null; if (session != s_session) { why = "left the world"; } else if (!Object.op_Implicit((Object)(object)ZNet.instance) || !Object.op_Implicit((Object)(object)ZNetScene.instance)) { why = "disconnected"; } else if (!Object.op_Implicit((Object)(object)portal)) { why = "portal unloaded"; } else { Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer)) { why = "no local player"; } else if (((Character)localPlayer).IsDead()) { why = "player died"; } else if (((Character)localPlayer).IsTeleporting() && (Object)(object)s_departing != (Object)(object)portal) { why = "player teleported"; } else { Vector3 val = ((Component)localPlayer).transform.position - origin; if (((Vector3)(ref val)).sqrMagnitude > Sq(Mathf.Max(Cfg.CaptureRadius.Value, 25f) + 10f)) { why = "player left the area"; } else if (!Object.op_Implicit((Object)(object)ZoneSystem.instance) || !ZoneSystem.instance.IsZoneLoaded(origin)) { why = "area unloading"; } } } return why == null; } private static float Sq(float v) { return v * v; } private static IEnumerator Capture(PeekPortal portal, string key, bool manual) { int session = s_session; s_busy = true; s_capturing = portal; GameObject rig = null; DepthRig depthRig = null; List<Renderer> hidden = new List<Renderer>(); if (manual) { Notify("Portal Peek: capturing portal view..."); } PortalPeekPlugin.Log.LogInfo((object)("Capturing portal " + key + "...")); int faceSize = Cfg.FaceSize; int depthRes = Cfg.DepthResolution; int frameCount = Cfg.Frames; float fps = Cfg.FramesPerSecond.Value; float maxDepth = Cfg.MaxDepth.Value; Vector3 originLocal = Cfg.CaptureOrigin; try { GameCamera instance = GameCamera.instance; Camera main = PeekPortal.MainCamera(); if (!Object.op_Implicit((Object)(object)instance) || !Object.op_Implicit((Object)(object)main) || !Object.op_Implicit((Object)(object)portal)) { yield break; } Camera sky = instance.m_skyCamera; Transform transform = ((Component)portal).transform; Vector3 origin = transform.TransformPoint(originLocal); Quaternion rot = transform.rotation; if (!StillValid(session, portal, origin, out var why)) { PortalPeekPlugin.Log.LogInfo((object)("Capture of " + key + " skipped (" + why + ").")); yield break; } rig = new GameObject("PortalPeek_CaptureRig"); rig.SetActive(false); Camera skyCopy = (Object.op_Implicit((Object)(object)sky) ? CopyCamera(sky, rig.transform, "Sky") : null); Camera mainCopy = CopyCamera(main, rig.transform, "Main"); rig.SetActive(true); int side = depthRes + 1; float[] depth = new float[5 * side * side]; for (int i = 0; i < depth.Length; i++) { depth[i] = maxDepth; } depthRig = DepthRig.Create(main, rig.transform, depthRes, maxDepth, depth); if (depthRig != null) { for (int f = 0; f < 5; f++) { if (!StillValid(session, portal, origin, out var why2)) { Cancelled(key, why2); yield break; } HideForCapture(portal, hidden); try { depthRig.Face(origin, rot, f); } finally { Unhide(hidden); } if (!Rushing(portal)) { yield return null; } } float waitUntil = Time.time + 5f; while (!depthRig.Done && Time.time < waitUntil) { yield return null; } } int rowsPerSlice = Mathf.Max(1, 1200 / side); for (int f = 0; f < 5; f++) { for (int row = 0; row < side; row += rowsPerSlice) { if (!StillValid(session, portal, origin, out var why3)) { Cancelled(key, why3); yield break; } RaycastRows(origin, rot, ((Component)portal).transform, f, row, Mathf.Min(side, row + rowsPerSlice), depthRes, maxDepth, depth); if (!Rushing(portal)) { yield return null; } } } depthRig?.Dispose(); depthRig = null; int size = Snapshot.TextureSizeFor(faceSize); FrameJob sides = new FrameJob { Pixels = new byte[size * size * 4], Faces = 4 }; for (int f = 1; f < 5; f++) { if (!StillValid(session, portal, origin, out var why4)) { Cancelled(key, why4); yield break; } HideForCapture(portal, hidden); try { RenderFace(sides, f, mainCopy, skyCopy, main, sky, origin, rot, faceSize, size); } finally { Unhide(hidden); } if (!Rushing(portal)) { yield return null; } } List<FrameJob> jobs = new List<FrameJob>(); float start = Time.time; bool cutShort = false; for (int f = 0; f < frameCount; f++) { while (Time.time < start + (float)f / fps) { yield return null; } if (!StillValid(session, portal, origin, out var why5)) { if (jobs.Count == 0 || session != s_session || !Object.op_Implicit((Object)(object)ZNet.instance)) { Cancelled(key, why5); yield break; } PortalPeekPlugin.Log.LogInfo((object)$"Capture of {key} cut short ({why5}), keeping {jobs.Count} frame(s)."); cutShort = true; break; } Reclaim(jobs); PumpEncodes(jobs, sides, size); FrameJob frameJob = new FrameJob { Pixels = Rent(size * size * 4), Faces = 1, Time = Time.time - start }; jobs.Add(frameJob); HideForCapture(portal, hidden); try { RenderFace(frameJob, 0, mainCopy, skyCopy, main, sky, origin, rot, faceSize, size); } finally { Unhide(hidden); } yield return null; } float deadline = Time.time + 60f; while (Time.time < deadline) { PumpEncodes(jobs, sides, size); bool flag = sides.Failed || (sides.Encode != null && sides.Encode.IsCompleted); foreach (FrameJob item in jobs) { if (!item.Failed && (item.Encode == null || !item.Encode.IsCompleted)) { flag = false; break; } } if (flag) { break; } if (session != s_session || !Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } yield return null; } if (session != s_session || !Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } if (sides.Failed || sides.Encode == null || sides.Encode.Status != TaskStatus.RanToCompletion) { PortalPeekPlugin.Log.LogWarning((object)("Capture of " + key + ": side views could not be captured.")); yield break; } Snapshot snapshot = new Snapshot { Key = key, Version = DateTime.UtcNow.Ticks, Origin = originLocal, FaceSize = faceSize, DepthRes = depthRes, Fps = fps, DayFraction = (Object.op_Implicit((Object)(object)EnvMan.instance) ? EnvMan.instance.GetDayFraction() : 0f), MaxDepth = maxDepth, Depth = depth, Sides = sides.Encode.Result }; List<byte[]> list = new List<byte[]>(); List<float> list2 = new List<float>(); foreach (FrameJob item2 in jobs) { if (!item2.Failed && item2.Encode != null && item2.Encode.Status == TaskStatus.RanToCompletion) { list.Add(item2.Encode.Result); list2.Add(item2.Time); continue; } break; } snapshot.Frames = list.ToArray(); snapshot.FrameTimes = list2.ToArray(); if (snapshot.FrameCount == 0) { PortalPeekPlugin.Log.LogWarning((object)("Capture of " + key + " produced no frames.")); yield break; } if (cutShort || snapshot.FrameCount < frameCount) { snapshot.Version = PeekLibrary.StaleVersion(); } PeekLibrary.Submit(snapshot); if (manual) { Notify("Portal Peek: portal view captured"); } PortalPeekPlugin.Log.LogInfo((object)$"Captured {key}: {snapshot.FrameCount} frame(s) at {Snapshot.TextureSizeFor(faceSize)} px."); } finally { Unhide(hidden); depthRig?.Dispose(); if (Object.op_Implicit((Object)(object)rig)) { Object.Destroy((Object)(object)rig); } s_pixelPool.Clear(); if (session == s_session) { s_busy = false; s_capturing = null; } } } private static void Cancelled(string key, string why) { PortalPeekPlugin.Log.LogInfo((object)("Capture of " + key + " cancelled (" + why + ").")); s_lastCheck[key] = Time.time - 60f + 10f; } private static void PumpEncodes(List<FrameJob> jobs, FrameJob sides, int size) { int num = (Running(sides) ? 1 : 0); foreach (FrameJob job in jobs) { if (Running(job)) { num++; } } if (num < 2 && StartEncode(sides, size)) { num++; } foreach (FrameJob job2 in jobs) { if (num < 2 && StartEncode(job2, size)) { num++; } } } private static bool Running(FrameJob j) { if (j != null && j.Encode != null) { return !j.Encode.IsCompleted; } return false; } private static bool StartEncode(FrameJob j, int size) { if (j == null || j.Encode != null || j.Failed || j.Pixels == null || j.FacesDone < j.Faces) { return false; } byte[] pixels = j.Pixels; j.Encode = Task.Run(() => Bc1.Encode(pixels, size)); return true; } private static byte[] Rent(int size) { while (s_pixelPool.Count > 0) { byte[] array = s_pixelPool.Pop(); if (array.Length == size) { return array; } } return new byte[size]; } private static void Reclaim(List<FrameJob> jobs) { foreach (FrameJob job in jobs) { if (job.Pixels != null && job.Returned >= job.Faces && !((job.Encode == null) ? (!job.Failed) : (!job.Encode.IsCompleted))) { s_pixelPool.Push(job.Pixels); job.Pixels = null; } } } private static Camera CopyCamera(Camera source, Transform parent, string name) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); val.transform.SetParent(parent, false); Camera obj = val.AddComponent<Camera>(); obj.CopyFrom(source); ((Behaviour)obj).enabled = false; obj.depthTextureMode = source.depthTextureMode; obj.allowMSAA = false; obj.aspect = 1f; obj.rect = new Rect(0f, 0f, 1f, 1f); obj.targetTexture = null; return obj; } private static void RenderFace(FrameJob job, int face, Camera mainCopy, Camera skyCopy, Camera main, Camera sky, Vector3 origin, Quaternion rot, int faceSize, int width) { //IL_002f: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_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_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: 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) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) Snapshot.FaceRect(faceSize, face, out var fx, out var fy, out var fs); Quaternion val = rot * CubeFaces.Rotation(face); float fieldOfView = Snapshot.FaceFov(fs); RenderTexture rt = RenderTexture.GetTemporary(fs, fs, 24, (RenderTextureFormat)0, (RenderTextureReadWrite)0); RenderTexture active = RenderTexture.active; RenderTexture.active = rt; GL.Clear(true, true, Color.black); RenderTexture.active = active; if (Object.op_Implicit((Object)(object)skyCopy)) { Quaternion val2 = Quaternion.Inverse(((Component)main).transform.rotation); Vector3 val3 = val2 * (((Component)sky).transform.position - ((Component)main).transform.position); ((Component)skyCopy).transform.SetPositionAndRotation(origin + val * val3, val * (val2 * ((Component)sky).transform.rotation)); skyCopy.fieldOfView = fieldOfView; skyCopy.aspect = 1f; skyCopy.targetTexture = rt; skyCopy.Render(); skyCopy.targetTexture = null; } ((Component)mainCopy).transform.SetPositionAndRotation(origin, val); mainCopy.fieldOfView = fieldOfView; mainCopy.aspect = 1f; mainCopy.nearClipPlane = 0.05f; mainCopy.targetTexture = rt; mainCopy.Render(); mainCopy.targetTexture = null; AsyncGPUReadback.Request((Texture)(object)rt, 0, (TextureFormat)4, (Action<AsyncGPUReadbackRequest>)delegate(AsyncGPUReadbackRequest req) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) RenderTexture.ReleaseTemporary(rt); job.Returned++; if (((AsyncGPUReadbackRequest)(ref req)).hasError) { job.Failed = true; } if (!job.Failed && job.Pixels != null) { NativeArray<byte> data = ((AsyncGPUReadbackRequest)(ref req)).GetData<byte>(0); if (data.Length < fs * fs * 4) { job.Failed = true; } else { int num = fs * 4; for (int i = 0; i < fs; i++) { NativeArray<byte>.Copy(data, i * num, job.Pixels, ((fy + i) * width + fx) * 4, num); } job.FacesDone++; } } }); } private static void RaycastRows(Vector3 origin, Quaternion rot, Transform portal, int f, int rowStart, int rowEnd, int n, float maxDepth, float[] depth) { //IL_0089: 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_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) int num = n + 1; int num2 = num * num; int mask = LayerMask.GetMask(new string[7] { "Default", "static_solid", "Default_small", "piece", "piece_nonsolid", "terrain", "vehicle" }); float num3 = (Object.op_Implicit((Object)(object)ZoneSystem.instance) ? ZoneSystem.instance.m_waterLevel : 30f); for (int i = rowStart; i < rowEnd; i++) { for (int j = 0; j <= n; j++) { int num4 = f * num2 + i * num + j; Vector3 val = CubeFaces.Dir(f, (float)j * 2f / (float)n - 1f, (float)i * 2f / (float)n - 1f); Vector3 val2 = rot * ((Vector3)(ref val)).normalized; float num5 = Mathf.Min(depth[num4], NearestHit(origin, val2, depth[num4], mask, portal)); if (origin.y > num3 && val2.y < -0.0001f) { num5 = Mathf.Min(num5, (origin.y - num3) / (0f - val2.y)); } depth[num4] = Mathf.Clamp(num5, 0.15f, maxDepth); } } } private static float NearestHit(Vector3 origin, Vector3 dir, float maxDist, int mask, Transform ignore) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) int num = Physics.RaycastNonAlloc(origin, dir, s_hits, maxDist, mask, (QueryTriggerInteraction)1); float num2 = float.MaxValue; for (int i = 0; i < num; i++) { RaycastHit val = s_hits[i]; if (!(((RaycastHit)(ref val)).distance >= num2) && Object.op_Implicit((Object)(object)((RaycastHit)(ref val)).collider) && (!Object.op_Implicit((Object)(object)ignore) || !((Component)((RaycastHit)(ref val)).collider).transform.IsChildOf(ignore))) { num2 = ((RaycastHit)(ref val)).distance; } } return num2; } private static void HideForCapture(PeekPortal portal, List<Renderer> hidden) { PeekPortal.SetCaptureMode(capturing: true); if (Object.op_Implicit((Object)(object)portal)) { Hide(((Component)portal).gameObject, hidden); } foreach (Player allPlayer in Player.GetAllPlayers()) { if (!Object.op_Implicit((Object)(object)allPlayer)) { continue; } Hide(((Component)allPlayer).gameObject, hidden); Light[] componentsInChildren = ((Component)allPlayer).GetComponentsInChildren<Light>(); foreach (Light val in componentsInChildren) { if (((Behaviour)val).enabled) { ((Behaviour)val).enabled = false; s_hiddenLights.Add(val); } } } Player localPlayer = Player.m_localPlayer; GameObject val2 = (Object.op_Implicit((Object)(object)localPlayer) ? s_placementGhost.Invoke(localPlayer) : null); if (Object.op_Implicit((Object)(object)val2)) { Hide(val2, hidden); } } private static void Hide(GameObject root, List<Renderer> hidden) { Renderer[] componentsInChildren = root.GetComponentsInChildren<Renderer>(); foreach (Renderer val in componentsInChildren) { if (!val.forceRenderingOff) { val.forceRenderingOff = true; hidden.Add(val); } } } private static void Unhide(List<Renderer> hidden) { foreach (Renderer item in hidden) { if (Object.op_Implicit((Object)(object)item)) { item.forceRenderingOff = false; } } hidden.Clear(); foreach (Light s_hiddenLight in s_hiddenLights) { if (Object.op_Implicit((Object)(object)s_hiddenLight)) { ((Behaviour)s_hiddenLight).enabled = true; } } s_hiddenLights.Clear(); PeekPortal.SetCaptureMode(capturing: false); } } internal enum LoopMode { Crossfade, PingPong } internal enum QualityPreset { Low, Medium, High, Ultra, Custom } internal static class Cfg { public static ConfigEntry<bool> Enabled; public static ConfigEntry<bool> CaptureEnabled; public static ConfigEntry<KeyboardShortcut> ForceCaptureKey; public static ConfigEntry<QualityPreset> Quality; public static ConfigEntry<int> CustomResolution; public static ConfigEntry<int> CustomDepthResolution; public static ConfigEntry<int> CustomMaxActiveViews; public static ConfigEntry<float> CustomRenderScale; public static ConfigEntry<bool> Animate; public static ConfigEntry<float> ClipSeconds; public static ConfigEntry<float> FramesPerSecond; public static ConfigEntry<LoopMode> Loop; public static ConfigEntry<float> AnimationDistance; public static ConfigEntry<int> MaxLoadingClips; public static ConfigEntry<float> LookUpDegrees; public static ConfigEntry<float> MaxDistance; public static ConfigEntry<string> HideEffects; public static ConfigEntry<string> Openings; public static ConfigEntry<float> Shimmer; public static ConfigEntry<float> EdgeSoftness; public static ConfigEntry<float> CaptureRadius; public static ConfigEntry<float> SettleSeconds; public static ConfigEntry<float> RefreshMinutes; public static ConfigEntry<float> CaptureHeight; public static ConfigEntry<int> MemoryBudgetMB; public static ConfigEntry<float> MaxDepth; public static ConfigEntry<int> MaxTextureSize; public static ConfigEntry<string> StorageFolder; public static int FaceSize => Mathf.Clamp(Pick(256, 384, 512, 768, CustomResolution) / 4 * 4, 64, 1024); public static int DepthResolution => Mathf.Clamp(Pick(48, 64, 80, 112, CustomDepthResolution), 16, 160); public static int MaxActiveViews => Mathf.Clamp(Pick(6, 8, 12, 16, CustomMaxActiveViews), 1, 64); public static float RenderScale => Quality.Value switch { QualityPreset.Low => 0.75f, QualityPreset.Custom => CustomRenderScale.Value, _ => 1f, }; public static int Frames { get { if (!Animate.Value) { return 1; } return Mathf.Clamp(Mathf.RoundToInt(ClipSeconds.Value * FramesPerSecond.Value), 2, 160); } } public static Vector3 CaptureOrigin => new Vector3(0f, CaptureHeight.Value, 0.05f); public static void Bind(ConfigFile c) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Expected O, but got Unknown //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Expected O, but got Unknown //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Expected O, but got Unknown //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Expected O, but got Unknown //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Expected O, but got Unknown //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Expected O, but got Unknown //IL_0255: Unknown result type (might be due to invalid IL or missing references) //IL_025f: Expected O, but got Unknown //IL_028d: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Expected O, but got Unknown //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Expected O, but got Unknown //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_0307: Expected O, but got Unknown //IL_0335: Unknown result type (might be due to invalid IL or missing references) //IL_033f: Expected O, but got Unknown //IL_03ab: Unknown result type (might be due to invalid IL or missing references) //IL_03b5: Expected O, but got Unknown //IL_03e3: Unknown result type (might be due to invalid IL or missing references) //IL_03ed: Expected O, but got Unknown //IL_041b: Unknown result type (might be due to invalid IL or missing references) //IL_0425: Expected O, but got Unknown //IL_0453: Unknown result type (might be due to invalid IL or missing references) //IL_045d: Expected O, but got Unknown //IL_048b: Unknown result type (might be due to invalid IL or missing references) //IL_0495: Expected O, but got Unknown //IL_04c3: Unknown result type (might be due to invalid IL or missing references) //IL_04cd: Expected O, but got Unknown //IL_04fb: Unknown result type (might be due to invalid IL or missing references) //IL_0505: Expected O, but got Unknown Enabled = c.Bind<bool>("1 - General", "Enabled", true, "Show what is on the other side inside connected portals."); CaptureEnabled = c.Bind<bool>("1 - General", "AutoCapture", true, "Automatically snapshot a portal's surroundings when you stand near it for a few seconds. Snapshots are stored on this PC only; every player keeps their own."); ForceCaptureKey = c.Bind<KeyboardShortcut>("1 - General", "RecaptureKey", new KeyboardShortcut((KeyCode)289, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Re-snapshot the nearest connected portal right now (e.g. after building around it or at a nicer time of day)."); Quality = c.Bind<QualityPreset>("2 - Quality", "Preset", QualityPreset.High, "Overall quality. Lower = lighter captures, less memory and smaller files.\n Low : 256 px side views / 512 px forward view. Smoothest; noticeably soft. Good for older PCs.\n Medium : 384 / 768. Slightly soft up close.\n High : 512 / 1024 (default). Sharp.\n Ultra : 768 / 1536. Very sharp; heavier capture and loading. Strong PCs only.\n Custom : use the Custom... settings below.\nAffects new snapshots; press the RecaptureKey near a portal to refresh one."); CustomResolution = c.Bind<int>("2 - Quality", "CustomResolution", 512, new ConfigDescription("Only with Preset = Custom. Pixels per side view; the forward view is twice this. 256 = fastest, 384 = balanced, 512 = sharp, 768 = very sharp. If you notice stutter, go down one step.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[7] { 256, 320, 384, 448, 512, 640, 768 }), Array.Empty<object>())); CustomDepthResolution = c.Bind<int>("2 - Quality", "CustomDepthDetail", 80, new ConfigDescription("Only with Preset = Custom. How finely distances are measured (per view edge). Higher = more accurate 3D around object edges. 48 = light, 80 = good, 112 = fine.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(32, 128), Array.Empty<object>())); CustomMaxActiveViews = c.Bind<int>("2 - Quality", "CustomMaxPortals", 12, new ConfigDescription("Only with Preset = Custom. How many portals (closest first) show a peek at once. Each one costs a little GPU time; 6-12 is plenty.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>())); CustomRenderScale = c.Bind<float>("2 - Quality", "CustomRenderScale", 1f, new ConfigDescription("Only with Preset = Custom. Peek sharpness relative to its size on screen. 1 = pixel-perfect, 0.75 = cheaper and slightly softer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1.5f), Array.Empty<object>())); Animate = c.Bind<bool>("3 - Animation", "Animated", true, "On: portals show a short looping clip (moving trees, water, clouds).\nOff: portals show a still image. Captures are much quicker and it uses far less memory and disk space. Existing clips just show their first frame."); ClipSeconds = c.Bind<float>("3 - Animation", "ClipSeconds", 3f, new ConfigDescription("Length of the captured clip. Longer = less obvious looping, but more memory, disk space and a longer capture.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 8f), Array.Empty<object>())); FramesPerSecond = c.Bind<float>("3 - Animation", "FramesPerSecond", 10f, new ConfigDescription("Captured frames per second. Playback blends between frames, so 8-12 already looks smooth. Lower = lighter.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(4f, 20f), Array.Empty<object>())); Loop = c.Bind<LoopMode>("3 - Animation", "LoopMode", LoopMode.Crossfade, "Crossfade: plays forward and blends the end into the start (default).\nPingPong: plays forward then backward."); AnimationDistance = c.Bind<float>("3 - Animation", "AnimationDistance", 25f, new ConfigDescription("Only portals closer than this (meters) play their clip; farther ones show a still frame.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 150f), Array.Empty<object>())); MaxLoadingClips = c.Bind<int>("3 - Animation", "MaxLoadingClips", 2, new ConfigDescription("How many portals (nearest first) may load their clip at the same time; the others show a still image until it is their turn. Lower = smoother when arriving at a base full of portals.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>())); LookUpDegrees = c.Bind<float>("4 - Look", "LookUpDegrees", 12f, new ConfigDescription("Tilts the view through a portal upward. The third-person camera looks down, so without this you mostly see the ground on the other side. 0 = geometrically exact.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 35f), Array.Empty<object>())); Shimmer = c.Bind<float>("4 - Look", "Shimmer", 1f, new ConfigDescription("Strength of the magical ripple/swirl over the view (stronger toward the rim). 0 = a perfectly clear window, 1 = subtle (default), 2-3 = dreamy.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>())); EdgeSoftness = c.Bind<float>("4 - Look", "EdgeSoftness", 0.2f, new ConfigDescription("How far in from the rim the view fades out (fraction of the opening's radius). Hides the edge of the view inside the frame. 0.05 = crisp, 0.2 = soft (default), 0.4 = very soft.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.02f, 0.5f), Array.Empty<object>())); MaxDistance = c.Bind<float>("4 - Look", "MaxDistance", 40f, new ConfigDescription("Portals farther away than this (meters) keep their normal look.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 150f), Array.Empty<object>())); HideEffects = c.Bind<string>("4 - Look", "HideEffects", "Black_suck,suck particles", "Vanilla portal effect parts hidden while a peek is shown (comma-separated). Parts: Black_suck (dark inward smoke), suck particles (inward sparks), Particle System (swirling trails), blue flames, Runes and GroundFog (stone portal). Empty = hide nothing."); Openings = c.Bind<string>("4 - Look", "Openings", "portal_wood=0,1.37,1.42,1.42;portal_stone=0,2.4,2.25,2.25", "Shape of each portal's opening: prefab=centerX,centerY,radiusX,radiusY (meters). The frame hides a slightly oversized shape; only change this if the peek pokes out past the frame or leaves a gap. Applies instantly."); CaptureRadius = c.Bind<float>("5 - Capture", "ProximityRadius", 12f, new ConfigDescription("How close (meters) you must be to a connected portal for it to be snapshotted.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 40f), Array.Empty<object>())); SettleSeconds = c.Bind<float>("5 - Capture", "SettleSeconds", 5f, new ConfigDescription("Seconds you must be nearby (with the area fully loaded) before a snapshot is taken. Longer = never captures while things are still popping in after a teleport.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>())); RefreshMinutes = c.Bind<float>("5 - Capture", "RefreshMinutes", 30f, new ConfigDescription("A snapshot older than this is retaken the next time you hang around that portal.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10080f), Array.Empty<object>())); CaptureHeight = c.Bind<float>("5 - Capture", "EyeHeight", 1.7f, new ConfigDescription("Height above the portal's base the snapshot is taken from (a standing player's eye).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 4f), Array.Empty<object>())); MemoryBudgetMB = c.Bind<int>("6 - Advanced", "MemoryBudgetMB", 1024, new ConfigDescription("Memory (mostly video memory) for loaded peeks. They stay loaded, so returning to a portal is instant, until this is exceeded; then the least recently seen are unloaded. One animated portal takes about 5 MB at Low, 12 at Medium, 22 at High, 49 at Ultra.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(128, 8192), Array.Empty<object>())); MaxDepth = c.Bind<float>("6 - Advanced", "MaxDepth", 400f, new ConfigDescription("Anything farther than this (meters), including the sky, is treated as being at this distance.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(100f, 600f), Array.Empty<object>())); MaxTextureSize = c.Bind<int>("6 - Advanced", "MaxTextureSize", 1536, new ConfigDescription("Upper bound for one peek's on-screen render, in pixels per side.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(256, 4096), Array.Empty<object>())); StorageFolder = c.Bind<string>("6 - Advanced", "StorageFolder", "", "Where snapshots are kept. Empty = <Valheim save folder>\\portalpeek\\<world>\\ (next to worlds_local, never inside your world or character files)."); RemoveStaleEntries(c); } private static void RemoveStaleEntries(ConfigFile c) { try { if (AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(c) is IDictionary { Count: >0 } dictionary) { dictionary.Clear(); c.Save(); } } catch { } } private static int Pick(int low, int med, int high, int ultra, ConfigEntry<int> custom) { return Quality.Value switch { QualityPreset.Low => low, QualityPreset.Medium => med, QualityPreset.Ultra => ultra, QualityPreset.Custom => custom.Value, _ => high, }; } } [HarmonyPatch(typeof(TeleportWorld), "Awake")] internal static class TeleportWorldAwakePatch { private static void Postfix(TeleportWorld __instance) { try { if (Object.op_Implicit((Object)(object)__instance) && ((Behaviour)__instance).enabled && !PortalPeekPlugin.Headless && !Object.op_Implicit((Object)(object)((Component)__instance).GetComponent<PeekPortal>())) { ((Component)__instance).gameObject.AddComponent<PeekPortal>(); } } catch (Exception e) { Safe.Report("TeleportWorld.Awake", e); } } } [HarmonyPatch(typeof(TeleportWorld), "Teleport")] internal static class TeleportWorldTeleportPatch { private static void Postfix(TeleportWorld __instance, Player player) { try { if (Object.op_Implicit((Object)(object)__instance) && Object.op_Implicit((Object)(object)player) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && ((Character)player).IsTeleporting()) { PeekPortal component = ((Component)__instance).GetComponent<PeekPortal>(); if (Object.op_Implicit((Object)(object)component)) { PeekCapturer.OnDeparting(component); } } } catch (Exception e) { Safe.Report("TeleportWorld.Teleport", e); } } } [HarmonyPatch(typeof(ZNet), "OnDestroy")] internal static class ZNetOnDestroyPatch { private static void Postfix() { try { PeekCapturer.Abort(); PeekLibrary.Reset(); } catch (Exception e) { Safe.Report("ZNet.OnDestroy", e); } } } [BepInPlugin("tboods.PortalPeek", "Portal Peek", "1.0.0")] public sealed class PortalPeekPlugin : BaseUnityPlugin { public const string Guid = "tboods.PortalPeek"; public const string Name = "Portal Peek"; public const string Version = "1.0.0"; internal static ManualLogSource Log; private Harmony m_harmony; internal static bool Headless => (int)SystemInfo.graphicsDeviceType == 4; private void Awake() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Cfg.Bind(((BaseUnityPlugin)this).Config); Runner.Create(); m_harmony = new Harmony("tboods.PortalPeek"); m_harmony.PatchAll(typeof(PortalPeekPlugin).Assembly); Log.LogInfo((object)("Portal Peek 1.0.0 loaded (" + (Headless ? "server, headless" : "client") + ").")); } private void OnDestroy() { Harmony harmony = m_harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } internal sealed class Runner : MonoBehaviour { internal static Runner Instance { get; private set; } internal static void Create() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!Object.op_Implicit((Object)(object)Instance)) { GameObject val = new GameObject("PortalPeek_Runner"); Object.DontDestroyOnLoad((Object)val); Instance = val.AddComponent<Runner>(); } } private void Update() { //IL_003f: 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) if (PortalPeekPlugin.Headless) { return; } try { PeekLibrary.Update(); } catch (Exception e) { Safe.Report("Library.Update", e); } try { PeekPortal.UpdateBudget(); } catch (Exception e2) { Safe.Report("Portal.Budget", e2); } try { PeekCapturer.Update(); KeyboardShortcut value = Cfg.ForceCaptureKey.Value; if (((KeyboardShortcut)(ref value)).IsDown() && !IsTyping()) { PeekCapturer.ForceNearest(); } } catch (Exception e3) { Safe.Report("Capturer.Update", e3); } } private static bool IsTyping() { if ((!Object.op_Implicit((Object)(object)Chat.instance) || !Chat.instance.HasFocus()) && !Console.IsVisible() && !TextInput.IsVisible()) { return Menu.IsVisible(); } return true; } } internal static class FileUtil { public static void WriteAtomic(string path, Action<Stream> write) { string text = path + ".tmp"; using (FileStream obj = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None)) { write(obj); } if (File.Exists(path)) { try { File.Replace(text, path, null); return; } catch (PlatformNotSupportedException) { } catch (IOException) { } File.Delete(path); } File.Move(text, path); } } internal static class Safe { private const float RepeatSeconds = 10f; private static readonly Dictionary<string, float> s_lastReport = new Dictionary<string, float>(); public static void Report(string where, Exception e) { try { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!s_lastReport.TryGetValue(where, out var value) || !(realtimeSinceStartup - value < 10f)) { s_lastReport[where] = realtimeSinceStartup; PortalPeekPlugin.Log.LogError((object)$"[{where}] {e}"); } } catch { } } } internal static class CubeFaces { public const int Count = 5; public static readonly Vector3[] Forward = (Vector3[])(object)new Vector3[5] { new Vector3(0f, 0f, 1f), new Vector3(1f, 0f, 0f), new Vector3(-1f, 0f, 0f), new Vector3(0f, 1f, 0f), new Vector3(0f, -1f, 0f) }; public static readonly Vector3[] Up = (Vector3[])(object)new Vector3[5] { new Vector3(0f, 1f, 0f), new Vector3(0f, 1f, 0f), new Vector3(0f, 1f, 0f), new Vector3(0f, 0f, -1f), new Vector3(0f, 0f, 1f) }; public static Vector3 Right(int f) { //IL_0006: 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_0016: Unknown result type (might be due to invalid IL or missing references) return Vector3.Cross(Up[f], Forward[f]); } public static Quaternion Rotation(int f) { //IL_0006: 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_0016: Unknown result type (might be due to invalid IL or missing references) return Quaternion.LookRotation(Forward[f], Up[f]); } public static Vector3 Dir(int f, float s, float t) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) return Forward[f] + Right(f) * s + Up[f] * t; } } internal static class PortalKey { public static string For(ZDO zdo) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) if (zdo == null) { return null; } Vector3 position = zdo.GetPosition(); Quaternion rotation = zdo.GetRotation(); int num = (Mathf.RoundToInt(((Quaternion)(ref rotation)).eulerAngles.y) % 360 + 360) % 360; return $"{(uint)zdo.GetPrefab():x8}_{R(position.x)}_{R(position.y)}_{R(position.z)}_{num}"; } private static int R(float v) { return Mathf.RoundToInt(v * 10f); } public static bool IsValid(string key) { if (string.IsNullOrEmpty(key) || key.Length > 80) { return false; } foreach (char c in key) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f') && c != '_' && c != '-') { return false; } } return true; } } internal sealed class Snapshot { public const uint Magic = 1263554640u; public const int Format = 4; public long Version; public string Key; public Vector3 Origin; public int FaceSize; public int DepthRes; public float Fps; public float DayFraction; public float MaxDepth; public float[] Depth; public float[] FrameTimes; public byte[] Sides; public byte[][] Frames; public string FilePath; public long ImagesStart; public int TextureSize => TextureSizeFor(FaceSize); public int SamplesPerFace => (DepthRes + 1) * (DepthRes + 1); public int ImageBytes => Bc1.DataSize(TextureSize); public int MipCount => Bc1.MipCount(TextureSize); public int FrameCount => Frames.Length; public static int TextureSizeFor(int s) { return s * 2; } public static int Gutter(int faceSize) { return Math.Max(4, faceSize / 32); } public static float FaceFov(int faceSize) { int num = Gutter(faceSize); return 2f * (float)Math.Atan((double)faceSize / (double)(faceSize - 2 * num)) * 57.29578f; } public static void FaceRect(int s, int f, out int x, out int y, out int size) { switch (f) { case 0: x = 0; y = 0; size = s * 2; break; case 1: x = 0; y = 0; size = s; break; case 2: x = s; y = 0; size = s; break; case 3: x = 0; y = s; size = s; break; default: x = s; y = s; size = s; break; } } public long HeldBytes() { long num = ((Sides != null) ? Sides.Length : 0); byte[][] frames = Frames; for (int i = 0; i < frames.Length; i++) { if (frames[i] != null) { num += frames[i].Length; } } return num; } public byte[] Prefix() { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(1263554640u); binaryWriter.Write(4); binaryWriter.Write(Version); binaryWriter.Write(Key ?? ""); binaryWriter.Write(Origin.x); binaryWriter.Write(Origin.y); binaryWriter.Write(Origin.z); binaryWriter.Write(FaceSize); binaryWriter.Write(DepthRes); binaryWriter.Write(Fps); binaryWriter.Write(DayFraction); binaryWriter.Write(MaxDepth); binaryWriter.Write(Depth.Length); float[] depth = Depth; foreach (float num in depth) { binaryWriter.Write((ushort)Math.Min(65535, Math.Max(0, (int)Math.Round(num * 100f)))); } binaryWriter.Write(ImageBytes); binaryWriter.Write(Frames.Length); for (int j = 0; j < Frames.Length; j++) { binaryWriter.Write((j < FrameTimes.Length) ? FrameTimes[j] : ((float)j / Math.Max(1f, Fps))); } binaryWriter.Flush(); return memoryStream.ToArray(); } public void ReleaseToFile() { Sides = null; byte[][] frames = Frames; for (int i = 0; i < frames.Length; i++) { frames[i] = null; } } private static FileStream OpenRead(string path) { return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); } public static Snapshot FromFile(string path) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) using FileStream fileStream = OpenRead(path); using BinaryReader binaryReader = new BinaryReader(fileStream); Snapshot snapshot = new Snapshot(); ReadHeader(binaryReader, out snapshot.Version, out snapshot.Key); snapshot.Origin = new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle()); snapshot.FaceSize = binaryReader.ReadInt32(); snapshot.DepthRes = binaryReader.ReadInt32(); snapshot.Fps = binaryReader.ReadSingle(); snapshot.DayFraction = binaryReader.ReadSingle(); snapshot.MaxDepth = binaryReader.ReadSingle(); if (snapshot.FaceSize < 16 || snapshot.FaceSize > 1024 || snapshot.FaceSize % 4 != 0) { throw new InvalidDataException("bad face size"); } if (snapshot.DepthRes < 2 || snapshot.DepthRes > 256) { throw new InvalidDataException("bad depth resolution"); } int num = binaryReader.ReadInt32(); if (num != 5 * snapshot.SamplesPerFace) { throw new InvalidDataException("bad depth length"); } snapshot.Depth = new float[num]; for (int i = 0; i < num; i++) { snapshot.Depth[i] = (float)(int)binaryReader.ReadUInt16() / 100f; } if (binaryReader.ReadInt32() != snapshot.ImageBytes) { throw new InvalidDataException("bad image size"); } int num2 = binaryReader.ReadInt32(); if (num2 < 1 || num2 > 512) { throw new InvalidDataException("bad frame count"); } snapshot.FrameTimes = new float[num2]; float num3 = -1f; for (int j = 0; j < num2; j++) { float num4 = binaryReader.ReadSingle(); if (float.IsNaN(num4) || num4 < num3 || num4 > 600f) { throw new InvalidDataException("bad frame time"); } num3 = (snapshot.FrameTimes[j] = num4); } if (float.IsNaN(snapshot.Fps) || snapshot.Fps <= 0f) { snapshot.Fps = 10f; } snapshot.ImagesStart = fileStream.Position; if (fileStream.Length < snapshot.ImagesStart + ((long)num2 + 1L) * snapshot.ImageBytes) { throw new InvalidDataException("truncated"); } snapshot.Sides = binaryReader.ReadBytes(snapshot.ImageBytes); snapshot.Frames = new byte[num2][]; snapshot.Frames[0] = binaryReader.ReadBytes(snapshot.ImageBytes); snapshot.FilePath = path; return snapshot; } public byte[] ReadImage(int index) { using FileStream fileStream = OpenRead(FilePath); using BinaryReader binaryReader = new BinaryReader(fileStream); ReadHeader(binaryReader, out var version, out var key); if (version != Version || key != Key) { throw new InvalidDataException("snapshot file was replaced"); } fileStream.Position = ImagesStart + ((long)index + 1L) * ImageBytes; byte[] array = binaryReader.ReadBytes(ImageBytes); if (array.Length != ImageBytes) { throw new InvalidDataException("truncated"); } return array; } public static void ReadHeader(BinaryReader r, out long version, out string key) { if (r.ReadUInt32() != 1263554640) { throw new InvalidDataException("not a portal peek snapshot"); } if (r.ReadInt32() != 4) { throw new InvalidDataException("unsupported snapshot format"); } version = r.ReadInt64(); key = r.ReadString(); } public static bool TryReadHeader(string path, out long version, out string key) { version = 0L; key = null; try { using (FileStream input = OpenRead(path)) { using BinaryReader r = new BinaryReader(input); ReadHeader(r, out version, out key); } return true; } catch { return false; } } } internal static class Bc1 { private const int BlockBytes = 8; private static readonly ushort[] s_toLinear = BuildToLinear(); private static readonly byte[] s_toSrgb = BuildToSrgb(); public static int MipCount(int size) { int num = 1; while (size % 8 == 0) { size /= 2; num++; } return num; } public static int DataSize(int size) { int num = 0; int num2 = MipCount(size); int num3 = 0; while (num3 < num2) { num += size / 4 * (size / 4) * 8; num3++; size /= 2; } return num; } public static byte[] Encode(byte[] rgba, int size) { if (size <= 0 || size % 4 != 0 || rgba == null || rgba.Length < size * size * 4) { throw new ArgumentException("bad image for BC1"); } byte[] array = new byte[DataSize(size)]; int num = MipCount(size); int offset = 0; byte[] array2 = rgba; int[] block = new int[48]; int[] pal = new int[12]; for (int i = 0; i < num; i++) { if (i > 0) { array2 = Downsample(array2, size); size /= 2; } offset = EncodeLevel(array2, size, array, offset, block, pal); } return array; } private static int EncodeLevel(byte[] px, int size, byte[] output, int offset, int[] block, int[] pal) { int num = size / 4; for (int i = 0; i < num; i++) { for (int j = 0; j < num; j++) { for (int k = 0; k < 4; k++) { int num2 = ((i * 4 + k) * size + j * 4) * 4; int num3 = 0; while (num3 < 4) { int num4 = (k * 4 + num3) * 3; block[num4] = px[num2]; block[num4 + 1] = px[num2 + 1]; block[num4 + 2] = px[num2 + 2]; num3++; num2 += 4; } } EncodeBlock(block, output, offset, pal); offset += 8; } } return offset; } private static void EncodeBlock(int[] px, byte[] dst, int o, int[] pal) { int num = 255; int num2 = 255; int num3 = 255; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 0; int num9 = 0; for (int i = 0; i < 48; i += 3) { int num10 = px[i]; int num11 = px[i + 1]; int num12 = px[i + 2]; num7 += num10; num8 += num11; num9 += num12; if (num10 < num) { num = num10; } if (num10 > num4) { num4 = num10; } if (num11 < num2) { num2 = num11; } if (num11 > num5) { num5 = num11; } if (num12 < num3) { num3 = num12; } if (num12 > num6) { num6 = num12; } } if (num == num4 && num2 == num5 && num3 == num6) { ushort num13 = To565(num, num2, num3); Write(dst, o, num13, num13, 0u); return; } float num14 = (float)num7 / 16f; float num15 = (float)num8 / 16f; float num16 = (float)num9 / 16f; float num17 = 0f; float num18 = 0f; float num19 = 0f; float num20 = 0f; float num21 = 0f; float num22 = 0f; for (int j = 0; j < 48; j += 3) { float num23 = (float)px[j] - num14; float num24 = (float)px[j + 1] - num15; float num25 = (float)px[j + 2] - num16; num17 += num23 * num23; num18 += num23 * num24; num19 += num23 * num25; num20 += num24 * num24; num21 += num24 * num25; num22 += num25 * num25; } float num26 = num4 - num; float num27 = num5 - num2; float num28 = num6 - num3; for (int k = 0; k < 4; k++) { float num29 = num26 * num17 + num27 * num18 + num28 * num19; float num30 = num26 * num18 + num27 * num20 + num28 * num21; float num31 = num26 * num19 + num27 * num21 + num28 * num22; float num32 = Math.Max(Math.Abs(num29), Math.Max(Math.Abs(num30), Math.Abs(num31))); if (num32 < 1E-06f) { break; } num26 = num29 / num32; num27 = num30 / num32; num28 = num31 / num32; } if (Math.Abs(num26) + Math.Abs(num27) + Math.Abs(num28) < 1E-06f) { num26 = 0.299f; num27 = 0.587f; num28 = 0.114f; } float num33 = float.MaxValue; float num34 = float.MinValue; int num35 = 0; int num36 = 0; for (int l = 0; l < 48; l += 3) { float num37 = (float)px[l] * num26 + (float)px[l + 1] * num27 + (float)px[l + 2] * num28; if (num37 < num33) { num33 = num37; num35 = l; } if (num37 > num34) { num34 = num37; num36 = l; } } ushort num38 = To565(px[num36], px[num36 + 1], px[num36 + 2]); ushort num39 = To565(px[num35], px[num35 + 1], px[num35 + 2]); uint indices; int num40 = Match(px, num38, num39, pal, out indices); if (num38 != num39 && Refit(px, indices, out var c, out var c2) && (c != num38 || c2 != num39) && Match(px, c, c2, pal, out var indices2) < num40) { num38 = c; num39 = c2; indices = indices2; } Write(dst, o, num38, num39, indices); } private static int Match(int[] px, ushort c0, ushort c1, int[] pal, out uint indices) { indices = 0u; Unpack(c0, out pal[0], out pal[1], out pal[2]); Unpack(c1, out pal[3], out pal[4], out pal[5]); for (int i = 0; i < 3; i++) { pal[6 + i] = (2 * pal[i] + pal[3 + i]) / 3; pal[9 + i] = (pal[i] + 2 * pal[3 + i]) / 3; } int num = 0; for (int j = 0; j < 16; j++) { int num2 = j * 3; int num3 = int.MaxValue; int num4 = 0; for (int k = 0; k < 4; k++) { int num5 = px[num2] - pal[k * 3]; int num6 = px[num2 + 1] - pal[k * 3 + 1]; int num7 = px[num2 + 2] - pal[k * 3 + 2]; int num8 = num5 * num5 + num6 * num6 + num7 * num7; if (num8 < num3) { num3 = num8; num4 = k; } } num += num3; indices |= (uint)(num4 << j * 2); } return num; } private static bool Refit(int[] px, uint indices, out ushort c0, out ushort c1) { c0 = (c1 = 0); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; int num7 = 0; int num8 = 0; int num9 = 0; for (int i = 0; i < 16; i++) { int num10 = (int)((indices >> i * 2) & 3) switch { 2 => 2, 1 => 0, 0 => 3, _ => 1, }; int num11 = 3 - num10; int num12 = i * 3; num += num10 * num10; num2 += num11 * num11; num3 += num10 * num11; num4 += num10 * px[num12]; num5 += num10 * px[num12 + 1]; num6 += num10 * px[num12 + 2]; num7 += num11 * px[num12]; num8 += num11 * px[num12 + 1]; num9 += num11 * px[num12 + 2]; } float num13 = (float)num * (float)num2 - (float)num3 * (float)num3; if (Math.Abs(num13) < 0.001f) { return false; } float num14 = 3f / num13; c0 = To565(Clamp((float)(num4 * num2 - num7 * num3) * num14), Clamp((float)(num5 * num2 - num8 * num3) * num14), Clamp((float)(num6 * num2 - num9 * num3) * num14)); c1 = To565(Clamp((float)(num7 * num - num4 * num3) * num14), Clamp((float)(num8 * num - num5 * num3) * num14), Clamp((float)(num9 * num - num6 * num3) * num14)); return true; } private static void Write(byte[] dst, int o, ushort c0, ushort c1, uint indices) { if (c0 < c1) { ushort num = c0; c0 = c1; c1 = num; indices ^= 0x55555555; } else if (c0 == c1) { indices = 0u; } dst[o] = (byte)c0; dst[o + 1] = (byte)(c0 >> 8); dst[o + 2] = (byte)c1; dst[o + 3] = (byte)(c1 >> 8); dst[o + 4] = (byte)indices; dst[o + 5] = (byte)(indices >> 8); dst[o + 6] = (byte)(indices >> 16); dst[o + 7] = (byte)(indices >> 24); } private static int Clamp(float v) { if (!(v <= 0f)) { if (!(v >= 255f)) { return (int)(v + 0.5f); } return 255; } return 0; } private static int Mul8Bit(int a, int b) { int num = a * b + 128; return num + (num >> 8) >> 8; } private static ushort To565(int r, int g, int b) { return (ushort)((Mul8Bit(r, 31) << 11) | (Mul8Bit(g, 63) << 5) | Mul8Bit(b, 31)); } private static void Unpack(ushort c, out int r, out int g, out int b) { int num = (c >> 11) & 0x1F; int num2 = (c >> 5) & 0x3F; int num3 = c & 0x1F; r = (num << 3) | (num >> 2); g = (num2 << 2) | (num2 >> 4); b = (num3 << 3) | (num3 >> 2); } private static ushort[] BuildToLinear() { ushort[] array = new ushort[256]; for (int i = 0; i < 256; i++) { double num = (double)i / 255.0; double num2 = ((num <= 0.04045) ? (num / 12.92) : Math.Pow((num + 0.055) / 1.055, 2.4)); array[i] = (ushort)Math.Round(num2 * 65535.0); } return array; } private static byte[] BuildToSrgb() { byte[] array = new byte[65536]; for (int i = 0; i < 65536; i++) { double num = (double)i / 65535.0; double val = ((num <= 0.0031308) ? (num * 12.92) : (1.055 * Math.Pow(num, 5.0 / 12.0) - 0.055)); array[i] = (byte)Math.Round(Math.Min(1.0, Math.Max(0.0, val)) * 255.0); } return array; } private static byte[] Downsample(byte[] src, int size) { int num = size / 2; byte[] array = new byte[num * num * 4]; for (int i = 0; i < num; i++) { int num2 = i * 2 * size * 4; int num3 = num2 + size * 4; for (int j = 0; j < num; j++) { int num4 = num2 + j * 8; int num5 = num3 + j * 8; int num6 = (i * num + j) * 4; for (int k = 0; k < 3; k++) { int num7 = s_toLinear[src[num4 + k]] + s_toLinear[src[num4 + 4 + k]] + s_toLinear[src[num5 + k]] + s_toLinear[src[num5 + 4 + k]]; array[num6 + k] = s_toSrgb[num7 + 2 >> 2]; } array[num6 + 3] = byte.MaxValue; } } return array; } } internal static class PeekLibrary { private sealed class Entry { public string Key; public long Version; public Snapshot Snap; public PeekAsset Asset; public PeekAsset Previous; public float LastShown = float.NegativeInfinity; public float LastTouched; public float Distance = float.MaxValue; public int DistanceFrame = -1; public long Bytes; public bool DiskChecked; public Task<Snapshot> DiskTask; } private const float ShownRecently = 2f; private const float InUse = 5f; private const float ForgetAfter = 300f; private const float LoadMsPerFrame = 2f; private const float LoadMsBurst = 6f; private const int KeepDays = 60; private static string s_dir; private static bool s_dirFailed; private static float s_loadCredit; private static readonly Dictionary<string, Entry> s_entries = new Dictionary<string, Entry>(); private static readonly Stopwatch s_watch = new Stopwatch(); private static readonly List<Entry> s_tmp = new List<Entry>(); public static bool Ready => EnsureDir(); public static void Reset() { foreach (Entry value in s_entries.Values) { value.Asset?.Destroy(); value.Previous?.Destroy(); } s_entries.Clear(); s_dir = null; s_dirFailed = false; } private static bool EnsureDir() { if (s_dir != null) { return true; } if (s_dirFailed) { return false; } World world = ZNet.World; if (world == null || string.IsNullOrEmpty(world.m_name)) { return false; } string text = Cfg.StorageFolder.Value; if (string.IsNullOrWhiteSpace(text)) { text = Path.Combine(Utils.GetSaveDataPath((FileSource)2), "portalpeek"); } string text2 = Path.Combine(text, Sanitize(world.m_name) + "_" + world.m_uid); try { Directory.CreateDirectory(text2); } catch (Exception ex) { s_dirFailed = true; PortalPeekPlugin.Log.LogError((object)("Cannot use snapshot folder '" + text2 + "': " + ex.Message + ". Peeks disabled this session.")); return false; } s_dir = text2; Housekeeping(text2); PortalPeekPlugin.Log.LogInfo((object)("Portal snapshots for this world are kept in " + text2)); return true; } private static void Housekeeping(string dir) { Task.Run(delegate { try { DateTime dateTime = DateTime.UtcNow.AddDays(-60.0); string[] files = Directory.GetFiles(dir, "*.ppk.tmp"); for (int i = 0; i < files.Length; i++) { File.Delete(files[i]); } files = Directory.GetFiles(dir, "*.ppk"); foreach (string path in files) { if (File.GetLastWriteTimeUtc(path) < dateTime || !Snapshot.TryReadHeader(path, out var _, out var _)) { File.Delete(path); } } } catch { } }); } private static string Sanitize(string name) { char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { name = name.Replace(oldChar, '_'); } return name; } private static string PathFor(string key) { return Path.Combine(s_dir, key + ".ppk"); } public static void Update() { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { if (s_dir != null || s_entries.Count > 0) { Reset(); } } else { LoadAssets(); Evict(); } } public static PeekAsset Get(string key, float distance) { if (string.IsNullOrEmpty(key) || !EnsureDir()) { return null; } Entry entry = EntryFor(key); entry.LastShown = (entry.LastTouched = Time.time); if (entry.DistanceFrame != Time.frameCount) { entry.DistanceFrame = Time.frameCount; entry.Distance = distance; } else if (distance < entry.Distance) { entry.Distance = distance; } AdvanceDiskRead(entry); if (entry.Snap != null && entry.Asset == null) { entry.Asset = new PeekAsset(entry.Snap); } if (entry.Asset != null && entry.Asset.Ready) { if (entry.Previous != null) { entry.Previous.Destroy(); entry.Previous = null; } return entry.Asset; } if (entry.Previous == null || !entry.Previous.Ready) { return null; } return entry.Previous; } private static void AdvanceDiskRead(Entry e) { if (!e.DiskChecked) { e.DiskChecked = true; string path = PathFor(e.Key); if (File.Exists(path)) { e.DiskTask = Task.Run(() => Snapshot.FromFile(path)); } } if (e.DiskTask == null || !e.DiskTask.IsCompleted) { return; } if (e.DiskTask.Status == TaskStatus.RanToCompletion) { Snapshot result = e.DiskTask.Result; if (e.Snap == null && result.Key == e.Key) { Adopt(e, result); } } else if (IsCorrupt(e.DiskTask.Exception)) { TryDelete(PathFor(e.Key)); } e.DiskTask = null; } public static bool NeedsCapture(string key, bool force) { if (!EnsureDir()) { return false; } Entry entry = EntryFor(key); entry.LastTouched = Time.time; AdvanceDiskRead(entry); if (entry.DiskTask != null) { return false; } if (force) { return true; } if (entry.Version == 0L) { return true; } if (entry.Asset != null && entry.Asset.Failed) { return true; } return IsStale(entry.Version); } public static bool NeedsCaptureNow(string key) { if (!EnsureDir()) { return false; } Entry entry = EntryFor(key); entry.LastTouched = Time.time; AdvanceDiskRead(entry); if (entry.DiskTask == null) { return NeedsCapture(key, force: false); } string path = PathFor(key); if (!File.Exists(path)) { return true; } if (Snapshot.TryReadHeader(path, out var version, out var _)) { return IsStale(version); } return false; } public static long StaleVersion() { return DateTime.UtcNow.Ticks - TimeSpan.FromMinutes(Cfg.RefreshMinutes.Value + 1f).Ticks; } private static bool IsStale(long version) { return (double)(DateTime.UtcNow.Ticks - version) / 10000000.0 > (double)Cfg.RefreshMinutes.Value * 60.0; } public static bool IsLoading(string key) { if (s_entries.TryGetValue(key, out var value)) { if (value.DiskChecked) { return value.DiskTask != null; } return true; } return false; } private static Entry EntryFor(string key) { if (!s_entries.TryGetValue(key, out var value)) { value = new Entry { Key = key, LastTouched = Time.time }; s_entries[key] = value; } return value; } private static bool IsCorrupt(AggregateException ex) { Exception ex2 = ex?.GetBaseException(); if (!(ex2 is InvalidDataException)) { return ex2 is EndOfStreamException; } return true; } private static void Adopt(Entry e, Snapshot snap) { if (e.Asset != null && e.Asset.Ready) { e.Previous?.Destroy(); e.Previous = e.Asset; } else { e.Asset?.Destroy(); } e.Asset = null; e.Snap = snap; e.Version = snap.Version; } private static void TryDelete(string path) { try { File.Delete(path); } catch { } } public static void Submit(Snapshot snap) { if (!EnsureDir()) { return; } Entry entry = EntryFor(snap.Key); entry.DiskChecked = true; entry.DiskTask = null; Adopt(entry, snap); string path = PathFor(snap.Key); byte[] prefix = snap.Prefix(); byte[] sides = snap.Sides; byte[][] frames = (byte[][])snap.Frames.Clone(); snap.FilePath = path; snap.ImagesStart = prefix.Length; Task.Run(delegate { try { FileUtil.WriteAtomic(path, delegate(Stream s) { s.Write(prefix, 0, prefix.Length); s.Write(sides, 0, sides.Length); byte[][] array = frames; foreach (byte[] array2 in array) { s.Write(array2, 0, array2.Length); } }); snap.ReleaseToFile(); } catch (Exception ex) { PortalPeekPlugin.Log.LogWarning((object)("Could not save snapshot " + snap.Key + ": " + ex.Message)); } }); } private static void LoadAssets() { s_loadCredit = Mathf.Min(6f, s_loadCredit + 2f); if (s_loadCredit <= 0f) { return; } float time = Time.time; s_tmp.Clear(); foreach (Entry value2 in s_entries.Values) { if (time - value2.LastShown < 2f && value2.Asset != null && !value2.Asset.Failed && !value2.Asset.FullyDecoded) { s_tmp.Add(value2); } } if (s_tmp.Count == 0) { return; } s_tmp.Sort((Entry a, Entry b) => a.Distance.CompareTo(b.Distance)); foreach (Entry item in s_tmp) { while (!item.Asset.Ready && !item.Asset.Failed && Step(item)) { if (s_loadCredit <= 0f) { return; } } } if (!Cfg.Animate.Value) { return; } float value = Cfg.AnimationDistance.Value; int num = Cfg.MaxLoadingClips.Value; foreach (Entry item2 in s_tmp) { if (item2.Distance > value || num <= 0) { break; } if (!item2.Asset.Ready || item2.Asset.Failed) { continue; } num--; while (!item2.Asset.FullyDecoded && Step(item2)) { if (s_loadCredit <= 0f) { return; } } } } private static bool Step(Entry e) { s_watch.Restart(); bool result; try { result = e.Asset.LoadStep(); } catch (Exception e2) { Safe.Report("Load " + e.Key, e2); e.Asset.Abandon(); result = false; } s_loadCredit -= (float)s_watch.Elapsed.TotalMilliseconds; return result; } private static long Cost(PeekAsset a) { if (a != null) { return a.GpuBytes + a.Snap.HeldBytes(); } return 0L; } private static void Evict() { float time = Time.time; long num = 0L; s_tmp.Clear(); foreach (Entry value in s_entries.Values) { value.Bytes = Cost(value.Asset) + Cost(value.Previous) + ((value.Asset == null && value.Snap != null) ? value.Snap.HeldBytes() : 0); num += value.Bytes; if (value.DiskTask == null && !(time - value.LastShown < 5f) && ((value.Asset == null && value.Previous == null && time - value.LastTouched > 300f) || value.Bytes > 0)) { s_tmp.Add(value); } } long num2 = (long)Cfg.MemoryBudgetMB.Value * 1024L * 1024; s_tmp.Sort((Entry a, Entry b) => (a.LastShown == b.LastShown) ? a.LastTouched.CompareTo(b.LastTouched) : a.LastShown.CompareTo(b.LastShown)); foreach (Entry item in s_tmp) { if ((item.Asset == null && item.Previous == null && time - item.LastTouched > 300f) || num > num2) { item.Asset?.Destroy(); item.Previous?.Destroy(); s_entries.Remove(item.Key); num -= item.Bytes; } } } } internal sealed class PeekAsset { private sealed class MeshData { public Vector3[] Vertices; public Vector2[] Uvs; public Color32[] Colors; public List<int> Triangles; public long Bytes => (long)Vertices.Length * 24L + (long)Triangles.Count * 4L; public Mesh ToMesh(string name) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown Mesh val = new Mesh { name = name }; if (Vertices.Length > 65000) { val.indexFormat = (IndexFormat)1; } val.vertices = Vertices; val.uv = Uvs; val.colors32 = Colors; val.SetTriangles(Triangles, 0); val.RecalculateBounds(); val.UploadMeshData(true); return val; } } public const int MaxLayers = 4; private const float JumpRatio = 1.25f; private const float JumpMeters = 0.5f; private const float BackgroundPush = 1.015f; private const int SidesImage = -1; public readonly Snapshot Snap; public readonly Texture2D[] Frames; private Task<MeshData[]> m_meshTask; private long m_meshBytes; private Task<byte[]> m_read; private int m_readIndex; private int m_limit = int.MaxValue; private float m_playStart = -1f; private bool m_destroyed; public Mesh ForwardMesh { get; private set; } public Mesh SidesMesh { get; private set; } public Texture2D Sides { get; private set; } public int Decoded { get; private set; } public bool Ready { get { if (Object.op_Implicit((Object)(object)ForwardMesh) && Object.op_Implicit((Object)(object)Sides)) { return Decoded > 0; } return false; } } public bool FullyDecoded { get { if (Object.op_Implicit((Object)(object)Sides)) { return Decoded >= Mathf.Min(Frames.Length, m_limit); } return false; } } public bool Failed { get; private set; } public long GpuBytes => m_meshBytes + (long)((Object.op_Implicit((Object)(object)Sides) ? 1 : 0) + Decoded) * (long)Snap.ImageBytes; public PeekAsset(Snapshot snap) { Snap = snap; Frames = (Texture2D[])(object)new Texture2D[snap.FrameCount]; m_meshTask = Task.Run(() => BuildMeshData(snap)); } public bool LoadStep() { if (m_destroyed || Failed) { return false; } if (!Object.op_Implicit((Object)(object)ForwardMesh)) { if (m_meshTask == null || !m_meshTask.IsCompleted) { return false; } if (m_meshTask.Status != TaskStatus.RanToCompletion) { Failed = true; PortalPeekPlugin.Log.LogWarning((object)("Snapshot " + Snap.Key + ": mesh build failed: " + m_meshTask.Exception?.GetBaseException().Message)); return false; } MeshData[] result = m_meshTask.Result; ForwardMesh = result[0].ToMesh("PortalPeek_Fwd_" + Snap.Key); SidesMesh = result[1].ToMesh("PortalPeek_Sides_" + Snap.Key); m_meshBytes = result[0].Bytes + result[1].Bytes; m_meshTask = null; return true; } if (!Object.op_Implicit((Object)(object)Sides)) { if (!TakeImage(-1, out var bytes)) { return false; } Sides = ((bytes != null) ? Upload(bytes, "sides") : null); if (!Object.op_Implicit((Object)(object)Sides)) { Failed = true; } return true; } if (FullyDecoded) { return false; } int decoded = Decoded; if (!TakeImage(decoded, out var bytes2)) { return false; } Texture2D val = ((bytes2 != null) ? Upload(bytes2, "frame " + decoded) : null); if (!Object.op_Implicit((Object)(object)val)) { m_limit = decoded; if (decoded == 0) { Failed = true; } return true; } Frames[decoded] = val; Decoded++; return true; } private bool TakeImage(int index, out byte[] bytes) { bytes = ((index == -1) ? Snap.Sides : Snap.Frames[index]); if (bytes != null) { if (index == -1) { Snap.Sides = null; } else { Snap.Frames[index] = null; } return true; } if (m_read != null && m_readIndex == index) { if (!m_read.IsCompleted) { return false; } Task<byte[]> read = m_read; m_read = null; if (read.Status == TaskStatus.RanToCompletion) { bytes = read.Result; } else { PortalPeekPlugin.Log.LogWarning((object)$"Snapshot {Snap.Key}: could not read image {index}: {read.Exception?.GetBaseException().Message}"); } return true; } if (Snap.FilePath == null) { return true; } Snapshot snap = Snap; m_readIndex = index; m_read = Task.Run(() => snap.ReadImage(index)); return false; } private Texture2D Upload(byte[] bc1, string what) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown int textureSize = Snap.TextureSize; if (bc1.Length != Snap.ImageBytes) { PortalPeekPlugin.Log.LogWarning((object)("Snapshot " + Snap.Key + ": " + what + " has the wrong size.")); return null; } Texture2D val = new Texture2D(textureSize, textureSize, (TextureFormat)10, Snap.MipCount, false) { name = "PortalPeek_" + Snap.Key + "_" + what, wrapMode = (TextureWrapMode)1, filterMode = (FilterMode)2, anisoLevel = 4 }; try { val.LoadRawTextureData(bc1); val.Apply(false, true); return val; } catch (Exception ex) { PortalPeekPlugin.Log.LogWarning((object)("Snapshot " + Snap.Key + ": " + what + " could not be uploaded: " + ex.Message)); Object.Destroy((Object)(object)val); return null; } } public void Abandon() { m_limit = Decoded; } public void Destroy() { m_destroyed = true; Texture2D[] frames = Frames; foreach (Texture2D val in frames) { if (Object.op_Implicit((Object)(object)val)) { Object.Destroy((Object)(object)val); } } if (Object.op_Implicit((Object)(object)Sides)) { Object.Destroy((Object)(object)Sides); } if (Object.op_Implicit((Object)(object)ForwardMesh)) { Object.Destroy((Object)(object)ForwardMesh); } if (Object.op_Implicit((Object)(object)SidesMesh)) { Object.Destroy((Object)(object)SidesMesh); } } public int Sample(float now, Texture2D[] tex, float[] weight) { tex[0] = Frames[0]; weight[0] = 1f; int num = ((!Cfg.Animate.Value || !FullyDecoded) ? 1 : Decoded); if (num <= 1) { m_playStart = -1f; return 1; } if (m_playStart < 0f) { m_playStart = now; } float num2 = now - m_playStart; float num3 = TimeOf(num - 1); if (num3 <= 0f) { return 1; } if (Cfg.Loop.Value == LoopMode.Crossfade && num >= 4) { float num4 = num3 * 0.25f; float num5 = num3 - num4; if (num2 < num4) { return AddAt(num2, 1f, num, tex, weight, 0); } float num6 = Mathf.Repeat(num2, num5); if (num6 < num4) { float num7 = Mathf.SmoothStep(0f, 1f, num6 / num4); int count = AddAt(num6 + num5, 1f - num7, num, tex, weight, 0); return AddAt(num6, num7, num, tex, weight, count); } return AddAt(num6, 1f, num, tex, weight, 0); } return AddAt(Mathf.PingPong(num2, num3), 1f, num, tex, weight, 0); } private float TimeOf(int i) { float[] frameTimes = Snap.FrameTimes; if (i < frameTimes.Length) { return frameTimes[i]; } return (float)i / Mathf.Max(1f, Snap.Fps); } private int AddAt(float t, float w, int n, Texture2D[] tex, float[] weight, int count) { int i; for (i = 0; i < n - 2 && TimeOf(i + 1) <= t; i++) { } float num = TimeOf(i); float num2 = TimeOf(i + 1); float num3 = ((num2 > num) ? Mathf.Clamp01((t - num) / (num2 - num)) : 0f); count = Add(Frames[i], w * (1f - num3), tex, weight, count); return Add(Frames[i + 1], w * num3, tex, weight, count); } private static int Add(Texture2D t, float w, Texture2D[] tex, float[] weight, int count) { if (w <= 0.001f) { return count; } for (int i = 0; i < count; i++) { if (!((Object)(object)tex[i] != (Object)(object)t)) { weight[i] += w; return count; } } if (count >= 4) { return count; } tex[count] = t; weight[count] = w; return count + 1; } private static MeshData[] BuildMeshData(Snapshot s) { //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) int depthRes = s.DepthRes; int num = depthRes + 1; int num2 = num * num; float[] array = (float[])s.Depth.Clone(); float[] array2 = new float[array.Length]; for (int i = 0; i < 5; i++) { int num3 = i * num2; for (int j = 0; j <= depthRes; j++) { for (int k = 0; k <= depthRes; k++) { float num4 = 0f; for (int l = -1; l <= 1; l++) { for (int m = -1; m <= 1; m++) { int num5 = Mathf.Clamp(j + l, 0, depthRes); int num6 = Mathf.Clamp(k + m, 0, depthRes); num4 = Mathf.Max(num4, s.Depth[num3 + num5 * num + num6]); } } array2[num3 + j * num + k] = num4; } } } Dictionary<long, List<int>> dictionary = new Dictionary<long, List<int>>(); for (int n = 0; n < 5; n++) { for (int num7 = 0; num7 <= depthRes; num7++) { for (int num8 = 0; num8 <= depthRes; num8++) { if (num8 == 0 || num8 == depthRes || num7 == 0 || num7 == depthRes) { Vector3 val = CubeFaces.Dir(n, (float)num8 * 2f / (float)depthRes - 1f, (float)num7 * 2f / (float)depthRes - 1f); Vector3 normalized = ((Vector3)(ref val)).normalized; long key = ((long)Mathf.RoundToInt(normalized.x * 20000f) + 32768L << 32) | ((long)Mathf.RoundToInt(normalized.y * 20000f) + 32768L << 16) | ((long)Mathf.RoundToInt(normalized.z * 20000f) + 32768L); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List<int>(3)); } value.Add(n * num2 + num7 * num + num8); } } } } foreach (List<int> value2 in dictionary.Values) { if (value2.Count < 2) { continue; } float num9 = float.MaxValue; float num10 = 0f; foreach (int item in value2) { num9 = Mathf.Min(num9, array[item]); num10 = Mathf.Max(num10, array2[item]); } foreach (int item2 in value2) { array[item2] = num9; array2[item2] = num10; } } return new MeshData[2] { BuildGroup(s, array, array2, 0, 1), BuildGroup(s, array, array2, 1, 5) }; } private static MeshData BuildGroup(Snapshot s, float[] near, float[] far, int firstFace, int endFace) { //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018d: 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_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01da: 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_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) int depthRes = s.DepthRes; int num = depthRes + 1; int num2 = num * num; int num3 = endFace - firstFace; int num4 = num3 * num2; MeshData meshData = new MeshData { Vertices = (Vector3[])(object)new Vector3[num4 * 2], Uvs = (Vector2[])(object)new Vector2[num4 * 2], Colors = (Color32[])(object)new Color32[num4 * 2], Triangles = new List<int>(num3 * depthRes * depthRes * 12) }; float num5 = s.TextureSize; Color32 val = default(Color32); ((Color32)(ref val))..ctor(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); Vector2 val3 = default(Vector2); for (int i = firstFace; i < endFace; i++) { Snapshot.FaceRect(s.FaceSize, i, out var x, out var y, out var size); int num6 = Snapshot.Gutter(size); float num7 = size - 2 * num6; int num8 = i * num2; int num9 = (i - firstFace) * num2; for (int j = 0; j <= depthRes; j++) { for (int k = 0; k <= depthRes; k++) { float num10 = (float)k / (float)depthRes; float num11 = (float)j / (float)depthRes; Vector3 val2 = CubeFaces.Dir(i, num10 * 2f - 1f, num11 * 2f - 1f); Vector3 normalized = ((Vector3)(ref val2)).normalized; int num12 = num8 + j * num + k; int num13 = num9 + j * num + k; ((Vector2)(ref val3))..ctor(((float)(x + num6) + num10 * num7) / num5, ((float)(y + num6) + num11 * num7) / num5); meshData.Vertices[num13] = s.Origin + normalized * near[num12]; meshData.Vertices[num4 + num13] = s.Origin + normalized * (far[num12] * 1.015f); meshData.Uvs[num13] = (meshData.Uvs[num4 + num13] = val3); meshData.Colors[num13] = (meshData.Colors[num4 + num13] = val); } } for (int l = 0; l < depthRes; l++) { for (int m = 0; m < depthRes; m++) { int num14 = num9 + l * num + m; int c = num14 + 1; int num15 = num14 + num; int num16 = num15 + 1; int num17 = num8 + l * num + m; int num18 = num17 + 1; int num19 = num17 + num; int num20 = num19 + 1; if (!IsJump(near[num17], near[num19], near[num20])) { Tri(meshData.Triangles, num14, num15, num16, 0); } if (!IsJump(near[num17], near[num20], near[num18])) { Tri(meshData.Triangles, num14, num16, c, 0); } Tri(meshData.Triangles, num14, num15, num16, num4); Tri(meshData.Triangles, num14, num16, c, num4); } } } return meshData; } private static bool IsJump(float a, float b, float c) { float num = Mathf.Min(a, Mathf.Min(b, c)); float num2 = Mathf.Max(a, Mathf.Max(b, c)); if (num2 > num * 1.25f) { return num2 - num > 0.5f; } return false; } private static void Tri(List<int> tris, int a, int b, int c, int offset) { tris.Add(a + offset); tris.Add(b + offset); tris.Add(c + offset); } } internal struct Opening { public float CenterX; public float CenterY; public float RadiusX; public float RadiusY; public const float Floor = 0.03f; public Rect Rect => Rect.MinMaxRect(CenterX - RadiusX, Mathf.Max(0.03f, CenterY - RadiusY), CenterX + RadiusX, CenterY + RadiusY); public static Opening For(TeleportWorld portal) { //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be du