Decompiled source of SpeedrunTimer v1.8.1
Patch1_MelonLoader0.5/Mods/SpeedrunTimer.P1.ML5.dll
Decompiled 4 months agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using SLZ.Data; using SLZ.Interaction; using SLZ.Marrow.Input; using SLZ.Marrow.SceneStreaming; using SLZ.Marrow.Utilities; using SLZ.Marrow.Warehouse; using SLZ.Rig; using Sst.SpeedrunTimer; using Sst.Utilities; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("SpeedrunTimer")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany(null)] [assembly: AssemblyProduct("SpeedrunTimer")] [assembly: AssemblyCopyright("Created by jakzo")] [assembly: AssemblyTrademark(null)] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("1.8.1")] [assembly: NeutralResourcesLanguage("en")] [assembly: MelonInfo(typeof(Mod), "SpeedrunTimer", "1.8.1", "jakzo", "https://bonelab.thunderstore.io/package/jakzo/SpeedrunTimer/")] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.8.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Sst { public class Metadata { public const string AUTHOR = "jakzo"; public const string COMPANY = null; public const string DEVELOPER = "Stress Level Zero"; public const string GAME = "BONELAB"; public const string GAME_BONEWORKS = "BONEWORKS"; } public class Dbg { private static MelonPreferences_Entry<bool> _prefPrintDebugLogs; public static void Init(string prefCategoryId) { _prefPrintDebugLogs = MelonPreferences.CreateCategory(prefCategoryId).CreateEntry<bool>("printDebugLogs", false, "Print debug logs", "Print debug logs to console", false, true, (ValueValidator)null, (string)null); } public static void Log(string msg, params object[] data) { if (_prefPrintDebugLogs.Value) { MelonLogger.Msg("dbg: " + msg); } } } } namespace Sst.Utilities { internal static class LevelHooks { [HarmonyPatch(typeof(BasicTrackingRig), "Awake")] private class BasicTrackingRig_Awake_Patch { [CompilerGenerated] private static class <>O { public static LemonAction <0>__WaitForLoadFinished; } [HarmonyPrefix] internal static void Prefix(BasicTrackingRig __instance) { //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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown Dbg.Log("BasicTrackingRig_Awake_Patch"); _loadingScene = ((Component)__instance).gameObject.scene; if (Object.op_Implicit((Object)(object)CurrentLevel)) { PrevLevel = CurrentLevel; } CurrentLevel = null; NextLevel = SceneStreamer.Session.Level; RigManager = null; BasicTrackingRig = __instance; MelonEvent onUpdate = MelonEvents.OnUpdate; object obj = <>O.<0>__WaitForLoadFinished; if (obj == null) { LemonAction val = WaitForLoadFinished; <>O.<0>__WaitForLoadFinished = val; obj = (object)val; } ((MelonEventBase<LemonAction>)(object)onUpdate).Subscribe((LemonAction)obj, 0, false); LevelCrate nextLevel = NextLevel; Dbg.Log("OnLoad " + ((nextLevel != null) ? ((Scannable)nextLevel).Title : null)); SafeInvoke("OnLoad", LevelHooks.OnLoad, NextLevel); } } [HarmonyPatch(typeof(RigManager), "Awake")] private class RigManager_Awake_Patch { [HarmonyPrefix] internal static void Prefix(RigManager __instance) { Dbg.Log("RigManager_Awake_Patch"); RigManager = __instance; BasicTrackingRig = null; } } [CompilerGenerated] private static class <>O { public static LemonAction <0>__WaitForLoadFinished; } public static LevelCrate PrevLevel; public static LevelCrate CurrentLevel; public static LevelCrate NextLevel; public static RigManager RigManager; public static BasicTrackingRig BasicTrackingRig; private static Scene _loadingScene; public static bool IsLoading => !Object.op_Implicit((Object)(object)CurrentLevel); public static event Action<LevelCrate> OnLoad; public static event Action<LevelCrate> OnLevelStart; private static void SafeInvoke(string name, Action<LevelCrate> action, LevelCrate level) { try { action?.Invoke(level); } catch (Exception ex) { MelonLogger.Error("Failed to execute " + name + " event: " + ex.ToString()); } } private static void WaitForLoadFinished() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown if (!((Scene)(ref _loadingScene)).isLoaded) { MelonEvent onUpdate = MelonEvents.OnUpdate; object obj = <>O.<0>__WaitForLoadFinished; if (obj == null) { LemonAction val = WaitForLoadFinished; <>O.<0>__WaitForLoadFinished = val; obj = (object)val; } ((MelonEventBase<LemonAction>)(object)onUpdate).Unsubscribe((LemonAction)obj); CurrentLevel = NextLevel ?? SceneStreamer.Session.Level ?? CurrentLevel; NextLevel = null; LevelCrate currentLevel = CurrentLevel; Dbg.Log("OnLevelStart " + ((currentLevel != null) ? ((Scannable)currentLevel).Title : null)); SafeInvoke("OnLevelStart", LevelHooks.OnLevelStart, CurrentLevel); } } } public class Bonelab { public static void DockToWrist(GameObject gameObject, bool rightHand = false) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) PhysicsRig physicsRig = LevelHooks.RigManager.physicsRig; Hand val = (rightHand ? physicsRig.rightHand : physicsRig.leftHand); gameObject.transform.SetParent(((Component)val).transform); gameObject.transform.localPosition = new Vector3(-0.31f, 0.3f, 0f); gameObject.transform.localRotation = Quaternion.Euler(32f, 4f, 3f); } public static TextMeshPro CreateTextOnWrist(string name, bool rightHand = false) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); TextMeshPro obj = val.AddComponent<TextMeshPro>(); ((TMP_Text)obj).alignment = (TextAlignmentOptions)1028; ((TMP_Text)obj).fontSize = 0.5f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); DockToWrist(val, rightHand); return obj; } } public class Levels { public class Barcodes { public const string DESCENT = "c2534c5a-4197-4879-8cd3-4a695363656e"; public const string HUB = "c2534c5a-6b79-40ec-8e98-e58c5363656e"; public const string LONG_RUN = "c2534c5a-56a6-40ab-a8ce-23074c657665"; public const string MINE_DIVE = "c2534c5a-54df-470b-baaf-741f4c657665"; public const string BIG_ANOMALY_A = "c2534c5a-7601-4443-bdfe-7f235363656e"; public const string STREET_PUNCHER = "SLZ.BONELAB.Content.Level.LevelStreetPunch"; public const string SPRINT_BRIDGE = "SLZ.BONELAB.Content.Level.SprintBridge04"; public const string MAGMA_GATE = "SLZ.BONELAB.Content.Level.SceneMagmaGate"; public const string MOON_BASE = "SLZ.BONELAB.Content.Level.MoonBase"; public const string MONOGON_MOTORWAY = "SLZ.BONELAB.Content.Level.LevelKartRace"; public const string PILLAR = "c2534c5a-c056-4883-ac79-e051426f6964"; public const string BIG_ANOMALY_B = "SLZ.BONELAB.Content.Level.LevelBigAnomalyB"; public const string ASCENT = "c2534c5a-db71-49cf-b694-24584c657665"; public const string OUTRO = "SLZ.BONELAB.Content.Level.LevelOutro"; public const string ROOFTOPS = "c2534c5a-c6ac-48b4-9c5f-b5cd5363656e"; public const string TUNNEL_TIPPER = "c2534c5a-c180-40e0-b2b7-325c5363656e"; public const string DISTRICT_TAC_TRIAL = "c2534c5a-4f3b-480e-ad2f-69175363656e"; public const string DROP_PIT = "c2534c5a-de61-4df9-8f6c-416954726547"; public const string TUSCANY = "c2534c5a-2c4c-4b44-b076-203b5363656e"; public const string MAIN_MENU = "c2534c5a-80e1-4a29-93ca-f3254d656e75"; public const string VOID_G114 = "fa534c5a868247138f50c62e424c4144.Level.VoidG114"; public const string FANTASY_ARENA = "fa534c5a868247138f50c62e424c4144.Level.LevelArenaMin"; } public class LabworksBarcodes { public const string BREAKROOM = "volx4.LabWorksBoneworksPort.Level.Boneworks01Breakroom"; public const string THRONE_ROOM = "volx4.LabWorksBoneworksPort.Level.Boneworks12ThroneRoom"; public const string MAIN_MENU = "volx4.LabWorksBoneworksPort.Level.BoneworksMainMenu"; } public const string TITLE_DESCENT = "01 - Descent"; public const string TITLE_MONOGON_MOTORWAY = "10 - Monogon Motorway"; public static Dictionary<string, byte> TitleToIndex = new Dictionary<string, byte> { ["01 - Descent"] = 1, ["02 - BONELAB Hub"] = 2, ["03 - LongRun"] = 3, ["04 - Mine Dive"] = 4, ["05 - Big Anomaly"] = 5, ["06 - Street Puncher"] = 6, ["07 - Sprint Bridge 04"] = 7, ["08 - Magma Gate"] = 8, ["09- MoonBase"] = 9, ["10 - Monogon Motorway"] = 10, ["11 - Pillar Climb"] = 11, ["12 - Big Anomaly B"] = 12, ["13 - Ascent"] = 13, ["14 - Home"] = 14, ["15 - Void G114"] = 15, ["Big Bone Bowling"] = 16, ["Container Yard"] = 17, ["Neon District Parkour"] = 18, ["Neon District Tac Trial"] = 19, ["Drop Pit"] = 20, ["Dungeon Warrior"] = 21, ["Fantasy Arena"] = 22, ["Gun Range"] = 23, ["Halfway Park"] = 24, ["HoloChamber"] = 25, ["Mirror"] = 26, ["Museum Basement"] = 27, ["Rooftops"] = 28, ["Tunnel Tipper"] = 29, ["Tuscany"] = 30, ["00 - Main Menu"] = 31, ["Baseline"] = 32, ["Load Default"] = 33, ["Load Mod"] = 34, ["Boneworks Main Menu"] = 100 }; public static string[] CAMPAIGN_LEVEL_BARCODES = new string[14] { "c2534c5a-4197-4879-8cd3-4a695363656e", "c2534c5a-6b79-40ec-8e98-e58c5363656e", "c2534c5a-56a6-40ab-a8ce-23074c657665", "c2534c5a-54df-470b-baaf-741f4c657665", "c2534c5a-7601-4443-bdfe-7f235363656e", "SLZ.BONELAB.Content.Level.LevelStreetPunch", "SLZ.BONELAB.Content.Level.SprintBridge04", "SLZ.BONELAB.Content.Level.SceneMagmaGate", "SLZ.BONELAB.Content.Level.MoonBase", "SLZ.BONELAB.Content.Level.LevelKartRace", "c2534c5a-c056-4883-ac79-e051426f6964", "SLZ.BONELAB.Content.Level.LevelBigAnomalyB", "c2534c5a-db71-49cf-b694-24584c657665", "SLZ.BONELAB.Content.Level.LevelOutro" }; public static HashSet<string> CAMPAIGN_LEVEL_BARCODES_SET = CAMPAIGN_LEVEL_BARCODES.ToHashSet(); public static byte GetIndex(string title) { if (TitleToIndex.TryGetValue(title, out var value)) { return value; } Match match = Regex.Match(title, "^Boneworks_(\\d+) "); if (match.Success) { return (byte)(100 + int.Parse(match.Groups[1].Value)); } return 0; } public static bool IsMenu(string barcode) { if (!(barcode == "c2534c5a-80e1-4a29-93ca-f3254d656e75")) { return barcode == "fa534c5a868247138f50c62e424c4144.Level.VoidG114"; } return true; } } } namespace Sst.SpeedrunTimer { internal static class AppVersion { public const string Value = "1.8.1"; } public static class BuildInfo { public const string Name = "SpeedrunTimer"; } public class InputServer { private const float DEGREES_TO_RADIANS = (float)Math.PI / 180f; private WebsocketServer websocketServer; public InputServer(int port = 6161, string ip = null) { websocketServer = new WebsocketServer { OnConnect = delegate(WebsocketServer.Client client) { client.Send("{\"inputVersion\":1}"); } }; websocketServer.Start(port, ip); MelonLogger.Msg("Input viewer server started at:\n" + string.Join("\n", ((ip == null) ? Network.GetAllAddresses() : new string[1] { ip }).Select((string address) => $"ws://{address}:{port}"))); } public void SendInputState() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) XRApi xr = MarrowGame.xr; if (((xr != null) ? xr.HMD : null) == null) { return; } byte item = 1; List<byte> list = new List<byte>(128) { item }; AddToData(list, Time.unscaledTime); AddToData(list, (XRDevice)(object)MarrowGame.xr.HMD); XRController[] array = (XRController[])(object)new XRController[2] { MarrowGame.xr.LeftController, MarrowGame.xr.RightController }; foreach (XRController val in array) { if (val != null) { AddToData(list, (XRDevice)(object)val); } else { AddToData(list, default(Vector3)); AddToData(list, default(Quaternion)); } bool[] obj = new bool[15] { val.IsConnected, val.TriggerButton, val.TriggerTouched, val.GripButton, false, val.TouchpadButton, val.TouchpadTouch, val.JoystickButton, val.JoystickTouch, val.AButton, val.ATouch, val.BButton, val.BTouch, val.MenuButton, false }; int num = 0; int num2 = 0; bool[] array2 = obj; for (int j = 0; j < array2.Length; j++) { if (array2[j]) { num |= 1 << num2; } num2++; } list.AddRange(BitConverter.GetBytes(num)); AddToData(list, val.Touchpad2DAxis); AddToData(list, val.Joystick2DAxis); AddToData(list, val.Trigger); AddToData(list, val.Grip); } websocketServer.Send(list.ToArray()); } private void AddToData(List<byte> data, int value) { data.AddRange(BitConverter.GetBytes(value)); } private void AddToData(List<byte> data, float value) { data.AddRange(BitConverter.GetBytes(value)); } private void AddToData(List<byte> data, Vector2 value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.x); AddToData(data, value.y); } private void AddToData(List<byte> data, Vector3 value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.x); AddToData(data, value.y); AddToData(data, value.z); } private void AddToData(List<byte> data, Quaternion value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) AddToData(data, value.x); AddToData(data, value.y); AddToData(data, value.z); AddToData(data, value.w); } private void AddToData(List<byte> data, XRDevice value) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.Position); AddToData(data, value.Rotation); } private void AddToData(List<byte> data, Transform value) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.localPosition); AddToData(data, value.localRotation); } } public class Mod : MelonMod { public const string PREF_CATEGORY_ID = "SpeedrunTimer"; private static SplitsTimer _timer = new SplitsTimer(); private InputServer _inputServer; public MelonPreferences_Category PrefCategory; public SplitsServer SplitsServer; public static Mod Instance; public Mod() { Instance = this; } public override void OnInitializeMelon() { Dbg.Init("SpeedrunTimer"); PrefCategory = MelonPreferences.CreateCategory("SpeedrunTimer"); SplitsTimer.OnInitialize(); SaveDeleteImprovements.OnInitialize(); LevelHooks.OnLoad += _timer.OnLoadingScreen; LevelHooks.OnLevelStart += _timer.OnLevelStart; SplitsServer = new SplitsServer(); _inputServer = new InputServer(); } public override void OnUpdate() { _timer.OnUpdate(); _inputServer?.SendInputState(); } } public static class SaveDeleteImprovements { [HarmonyPatch(typeof(DataManager), "_MSAFAIGE")] private class DataManager_MSAFAIGE_Patch { [HarmonyPrefix] internal static void Prefix() { Dbg.Log("DataManager_MSAFAIGE_Prefix"); if (!_prefDeleteModsOnWipe.Value) { _modsBackupPath = Path.Combine(Application.persistentDataPath, "Mods.backup"); if (Directory.Exists(_modsBackupPath)) { Directory.Delete(_modsBackupPath, recursive: true); } Directory.Move(GetModsPath(), _modsBackupPath); } } [HarmonyPostfix] internal static void Postfix() { Dbg.Log("DataManager_MSAFAIGE_Postfix"); if (_modsBackupPath != null) { if (Directory.Exists(_modsBackupPath)) { MelonLogger.Warning("Failed to restore mods folder on data wipe! Old mods are in Mods.backup now."); } _modsBackupPath = null; } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] private class DataManager_SavePath_Patch { [HarmonyPrefix] internal static void Prefix() { Dbg.Log("DataManager_SavePath_Patch"); RestoreModsBackup(); } } private static MelonPreferences_Entry<bool> _prefDeleteModsOnWipe; private static string _modsBackupPath; private static string GetModsPath() { return Path.Combine(Application.persistentDataPath, "Mods"); } public static void OnInitialize() { _prefDeleteModsOnWipe = Mod.Instance.PrefCategory.CreateEntry<bool>("deleteModsOnWipe", false, "Let mods be deleted when wiping all data", "Normally when resetting your save state through the main menu the game will delete all mods including the SpeedrunTimer. For convenience the timer will keep your mods folder. This option reverts to the original behavior of deleting mods.", false, false, (ValueValidator)null, (string)null); } private static void RestoreModsBackup() { if (_modsBackupPath != null && Directory.Exists(_modsBackupPath) && !Directory.Exists(GetModsPath())) { Directory.Move(_modsBackupPath, GetModsPath()); _modsBackupPath = null; } } } public class SplitsServer { private WebsocketServer _ws; public SplitsServer(int port = 6162, string ip = null) { SplitsServer splitsServer = this; _ws = new WebsocketServer { OnConnect = delegate { splitsServer.InitGameTime(); } }; _ws.Start(port, ip); MelonLogger.Msg("Splits server started at:\n" + string.Join("\n", ((ip == null) ? Network.GetAllAddresses() : new string[1] { ip }).Select((string address) => $"ws://{address}:{port}"))); } public void Start() { _ws.Send("start"); } public void Split() { _ws.Send("split"); } public void SplitOrStart() { _ws.Send("splitorstart"); } public void Reset() { _ws.Send("reset"); } public void TogglePause() { _ws.Send("togglepause"); } public void Undo() { _ws.Send("undo"); } public void Skip() { _ws.Send("skip"); } public void InitGameTime() { _ws.Send("initgametime"); } public void SetGameTime(TimeSpan time) { _ws.Send($"setgametime {time.TotalSeconds}"); } public void SetLoadingTimes(TimeSpan times) { _ws.Send($"setloadingtimes {times.TotalSeconds}"); } public void PauseGameTime() { _ws.Send("pausegametime"); } public void ResumeGameTime() { _ws.Send("resumegametime"); } } internal class SplitsTimer { [HarmonyPatch(typeof(TaxiController), "Start")] private class TaxiController_Start_Patch { [HarmonyPrefix] internal static void Prefix(TaxiController __instance) { Dbg.Log("TaxiController_Start_Patch"); __instance.OnPlayerSeated.AddListener(UnityAction.op_Implicit((Action)Instance.Finish)); } } private static Color FINISH_COLOR = new Color(0.2f, 0.8f, 0.2f); private static SplitsTimer Instance; private TextMeshPro _tmp; private TextMeshPro _tmpIl; private Splits _splits = new Splits(); private bool _isFinished; private static MelonPreferences_Entry<bool> _prefHide; private static MelonPreferences_Entry<bool> _prefHideIl; private static MelonPreferences_Entry<bool> _prefHideSplits; public SplitsTimer() { Instance = this; Livesplit.SetState(isLoading: true, isSittingInTaxi: false); } public static void OnInitialize() { _prefHide = Mod.Instance.PrefCategory.CreateEntry<bool>("hide", false, "Hide in-game timer", "Stops the timer from displaying on your wrist. Does not hide loading screen timer.", false, false, (ValueValidator)null, (string)null); _prefHideIl = Mod.Instance.PrefCategory.CreateEntry<bool>("hideIl", false, "Hide level timer", "Stops the individual level timer from displaying on your wrist.", false, false, (ValueValidator)null, (string)null); _prefHideSplits = Mod.Instance.PrefCategory.CreateEntry<bool>("hideSplits", false, "Hides split display in loading screen", "Stops the individual level times from displaying in the loading screen.", false, false, (ValueValidator)null, (string)null); } public void Reset() { _isFinished = false; _splits.Reset(); Mod.Instance.SplitsServer?.Reset(); Livesplit.SetState(isLoading: false, isSittingInTaxi: false); } public void OnLoadingScreen(LevelCrate nextLevel) { if ((Object)(object)nextLevel == (Object)null) { return; } if (_isFinished) { _splits.Reset(); _isFinished = false; } if (Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.BoneworksMainMenu" && Barcode.op_Implicit(((Scannable)LevelHooks.PrevLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.Boneworks12ThroneRoom") { _splits.Split(null); Finish(); } else { Livesplit.SetState(isLoading: true, isSittingInTaxi: false, ((Scannable)nextLevel).Title); if (Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "c2534c5a-4197-4879-8cd3-4a695363656e" || Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.Boneworks01Breakroom") { Dbg.Log("Attempting to start timer"); Mod.Instance.SplitsServer?.Start(); _splits.ResetAndPause(nextLevel); } else if (_splits.TimeStart.HasValue) { Dbg.Log("Splitting timer"); Mod.Instance.SplitsServer?.Split(); _splits.Pause(); _splits.Split(nextLevel); } } TimeSpan? time = _splits.GetTime(); if (time.HasValue) { Mod.Instance.SplitsServer?.PauseGameTime(); Mod.Instance.SplitsServer?.SetGameTime(time.Value); SplitsRenderer.RenderLoadingWatermark(time.Value); if (!_prefHideSplits.Value) { SplitsRenderer.RenderSplits(_splits); } } } public void OnLevelStart(LevelCrate level) { Livesplit.SetState(isLoading: false, isSittingInTaxi: false, ((Scannable)level).Title); Mod.Instance.SplitsServer?.ResumeGameTime(); if (!_isFinished) { _splits.ResumeIfStarted(); } AddTimerToWrist(); } private void AddTimerToWrist() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) if (!_prefHide.Value) { GameObject val = new GameObject("SpeedrunTimer_Wrist_Text"); _tmp = val.AddComponent<TextMeshPro>(); ((TMP_Text)_tmp).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_tmp).fontSize = 0.5f; ((TMP_Text)_tmp).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); if (_isFinished) { ((TMP_Text)_tmp).color = FINISH_COLOR; } Bonelab.DockToWrist(val); if (!_prefHideIl.Value) { GameObject val2 = new GameObject("SpeedrunTimer_Wrist_IL"); GameObject val3 = new GameObject("SpeedrunTimer_Wrist_IL_Offset"); val3.transform.parent = val2.transform; _tmpIl = val3.AddComponent<TextMeshPro>(); ((TMP_Text)_tmpIl).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_tmpIl).fontSize = 0.3f; ((TMP_Text)_tmpIl).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); ((Transform)((TMP_Text)_tmpIl).rectTransform).localPosition = new Vector3(-0.005f, -0.03f, 0.005f); ((TMP_Text)_tmpIl).color = new Color(0.2f, 0.2f, 0.2f); Bonelab.DockToWrist(val2); } } } public void OnUpdate() { if ((Object)(object)_tmp != (Object)null) { TimeSpan? time = _splits.GetTime(); if (!time.HasValue) { return; } ((Component)_tmp).gameObject.active = true; ((TMP_Text)_tmp).SetText(SplitsRenderer.DurationToString(time.Value), true); } if ((Object)(object)_tmpIl != (Object)null) { TimeSpan? currentSplitTime = _splits.GetCurrentSplitTime(); if (currentSplitTime.HasValue) { ((Component)_tmpIl).gameObject.active = true; ((TMP_Text)_tmpIl).SetText(SplitsRenderer.DurationToString(currentSplitTime.Value), true); } } } public void Finish() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) LevelCrate currentLevel = LevelHooks.CurrentLevel; Livesplit.SetState(isLoading: false, isSittingInTaxi: true, ((currentLevel != null) ? ((Scannable)currentLevel).Title : null) ?? ""); _splits.Pause(); Mod.Instance.SplitsServer?.Split(); Mod.Instance.SplitsServer?.SetGameTime(_splits.GetTime().Value); _isFinished = true; MelonLogger.Msg($"Stopping timer at: {_splits.GetTime()}"); if ((Object)(object)_tmp != (Object)null) { ((TMP_Text)_tmp).color = FINISH_COLOR; } } } internal class TimeTrialDisplayFix { [HarmonyPatch(typeof(BoneLeaderManager), "SubmitLeaderboardScore")] private class BoneLeaderManager_SubmitLeaderboardScore_Patch { [HarmonyPostfix] internal static void Postfix(BoneLeaderManager __instance, uint score) { DisplayTime(__instance, score); } } public const string LABEL_NAME = "TimeTrialDisplayFix_Label"; public static void DisplayTime(BoneLeaderManager leaderManager, uint milliseconds) { TMP_Text text_TitleBoard = leaderManager.text_TitleBoard; Transform parent = text_TitleBoard.transform.parent; Transform obj = parent.Find("TimeTrialDisplayFix_Label"); TextMeshPro obj2 = ((obj != null) ? ((Component)obj).GetComponent<TextMeshPro>() : null) ?? CreateTimeDisplay(parent, text_TitleBoard); string text = SplitsRenderer.DurationToString(TimeSpan.FromMilliseconds(milliseconds)); ((TMP_Text)obj2).SetText("Time: " + text, true); } private static TextMeshPro CreateTimeDisplay(Transform canvas, TMP_Text title) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_002c: 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) GameObject val = new GameObject("TimeTrialDisplayFix_Label"); val.transform.SetParent(canvas, false); val.transform.localPosition = new Vector3(0f, 1.2f, 3f); TextMeshPro obj = val.AddComponent<TextMeshPro>(); ((TMP_Text)obj).alignment = (TextAlignmentOptions)514; ((TMP_Text)obj).font = title.font; ((TMP_Text)obj).fontSize = 4f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(10f, 1f); return obj; } } internal class Livesplit { private static byte[] State = new byte[12] { 0, 226, 3, 52, 194, 223, 99, 36, 0, 0, 0, 0 }; public static void SetState(bool isLoading, bool isSittingInTaxi, string levelTitle = "") { State[0] = 212; State[8] = (byte)((isLoading ? 1u : 0u) | ((isSittingInTaxi ? 1u : 0u) << 1)); State[9] = Levels.GetIndex(levelTitle); } } public static class Network { public static string[] GetAllAddresses() { return new string[1] { "127.0.0.1" }.Concat(from address in Dns.GetHostEntry(Dns.GetHostName()).AddressList where address.AddressFamily == AddressFamily.InterNetwork select address.ToString()).ToArray(); } } public class Splits { private static Regex _levelPrefixPattern = new Regex("^\\s*\\d+\\s*-\\s*"); public List<Split> Items = new List<Split>(); public DateTime? TimeStart; public DateTime? TimeEnd; public DateTime? TimeStartRelative; public DateTime? TimePause; public DateTime TimeLastSplitStartRelative = DateTime.Now; private static string LevelSplitName(LevelCrate level) { return _levelPrefixPattern.Replace(((Scannable)level).Title, ""); } public void Reset() { TimeStart = null; TimeEnd = null; TimeStartRelative = null; TimePause = null; } public void ResetAndPause(LevelCrate firstLevel) { ResetAndStart(firstLevel); TimePause = TimeStart; } public void ResetAndStart(LevelCrate firstLevel) { DateTime now = DateTime.Now; TimeEnd = (TimePause = null); TimeStart = (TimeStartRelative = (TimeLastSplitStartRelative = now)); Items = new List<Split> { new Split { Level = firstLevel, Name = LevelSplitName(firstLevel), TimeStart = now } }; } public void Pause() { TimePause = DateTime.Now; } public void ResumeIfStarted() { if (TimePause.HasValue) { TimeSpan timeSpan = DateTime.Now - TimePause.Value; TimeStartRelative += timeSpan; TimeLastSplitStartRelative += timeSpan; TimePause = null; } } public TimeSpan? GetTime() { DateTime value = TimeEnd ?? TimePause ?? DateTime.Now; DateTime? timeStartRelative = TimeStartRelative; return value - timeStartRelative; } public TimeSpan? GetCurrentSplitTime() { return (TimeEnd ?? TimePause ?? DateTime.Now) - TimeLastSplitStartRelative; } public void Split(LevelCrate nextLevel) { Split split = Items[Items.Count - 1]; DateTime now = DateTime.Now; split.TimeEnd = now; DateTime valueOrDefault = TimePause.GetValueOrDefault(now); split.Duration = valueOrDefault - TimeLastSplitStartRelative; TimeLastSplitStartRelative = valueOrDefault; if (Object.op_Implicit((Object)(object)nextLevel)) { Items.Add(new Split { Level = nextLevel, Name = LevelSplitName(nextLevel), TimeStart = now }); } } } public class Split { public LevelCrate Level; public string Name; public DateTime? TimeStart; public DateTime? TimeEnd; public TimeSpan? Duration; } internal class SplitsRenderer { private static float SPLITS_WIDTH = 0.3f; private static float SPLITS_LINE_HEIGHT = 0.05f; private static float SPLITS_LEFT = -0.1f; private static float SPLITS_FONT_SIZE = 0.3f; public static void RenderLoadingWatermark(TimeSpan time) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) Dbg.Log("RenderLoadingWatermark"); BasicTrackingRig basicTrackingRig = LevelHooks.BasicTrackingRig; Transform val = ((basicTrackingRig != null) ? basicTrackingRig.head : null); if (!Object.op_Implicit((Object)(object)val)) { MelonLogger.Warning("Failed to render watermark in loading screen because could not find head position"); return; } TextMeshPro obj = CreateText(val, "Watermark"); ((TMP_Text)obj).alignment = (TextAlignmentOptions)260; ((TMP_Text)obj).fontSize = 0.5f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(0.8f, 0.8f); ((Transform)((TMP_Text)obj).rectTransform).localPosition = new Vector3(0f, 0f, 1f); ((TMP_Text)obj).color = new Color(0.4f, 0.4f, 0.6f); string text = ""; string text2 = DurationToString(time); string text3 = string.Join("\n", MelonTypeBase<MelonMod>.RegisteredMelons.Select((MelonMod mod) => ((MelonBase)mod).Info.Name)); ((TMP_Text)obj).SetText("SpeedrunTimer v1.8.1" + text + "\n" + text2 + "\n\nMods:\n" + text3, true); } public static void RenderSplits(Splits splits) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) BasicTrackingRig basicTrackingRig = LevelHooks.BasicTrackingRig; Transform val = ((basicTrackingRig != null) ? basicTrackingRig.head : null); if (!Object.op_Implicit((Object)(object)val)) { return; } GameObject val2 = new GameObject("SpeedrunTimer_Splits"); val2.transform.SetParent(((Component)val).transform); val2.transform.localPosition = new Vector3(-0.4f, 0.25f, 1f); Vector2 val4 = default(Vector2); for (int i = 0; i < splits.Items.Count; i++) { Split split = splits.Items[i]; float num = (float)i * (0f - SPLITS_LINE_HEIGHT); float num2 = 0f; if (split.Duration.HasValue) { TextMeshPro val3 = CreateText(val2.transform, "Splits_Time_" + split.Name); string text = ((split.Duration.Value >= TimeSpan.FromHours(1.0)) ? "h\\:mm\\:ss" : "m\\:ss\\.f"); ((TMP_Text)val3).SetText(split.Duration.Value.ToString(text), true); ((TMP_Text)val3).alignment = (TextAlignmentOptions)516; ((TMP_Text)val3).overflowMode = (TextOverflowModes)3; ((Transform)((TMP_Text)val3).rectTransform).localPosition = new Vector3(0f, 0f, 0f); RectTransform rectTransform = ((TMP_Text)val3).rectTransform; RectTransform rectTransform2 = ((TMP_Text)val3).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform2.anchorMax = val4; rectTransform.anchorMin = val4; ((TMP_Text)val3).rectTransform.offsetMin = new Vector2(SPLITS_LEFT, num); ((TMP_Text)val3).rectTransform.offsetMax = new Vector2(SPLITS_WIDTH + SPLITS_LEFT, num + SPLITS_LINE_HEIGHT); ((TMP_Text)val3).fontSize = SPLITS_FONT_SIZE; val3.ForceMeshUpdate(false, false); num2 = ((TMP_Text)val3).preferredWidth; } TextMeshPro val5 = CreateText(val2.transform, "Splits_Name_" + split.Name); string text2 = Regex.Replace(split.Name, "^boneworks(?:_\\d+)?\\s+", "", RegexOptions.IgnoreCase); ((TMP_Text)val5).SetText(text2, true); ((TMP_Text)val5).alignment = (TextAlignmentOptions)513; ((TMP_Text)val5).overflowMode = (TextOverflowModes)3; ((Transform)((TMP_Text)val5).rectTransform).localPosition = new Vector3(0f, 0f, 0f); RectTransform rectTransform3 = ((TMP_Text)val5).rectTransform; RectTransform rectTransform4 = ((TMP_Text)val5).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform4.anchorMax = val4; rectTransform3.anchorMin = val4; ((TMP_Text)val5).rectTransform.offsetMin = new Vector2(SPLITS_LEFT, num); ((TMP_Text)val5).rectTransform.offsetMax = new Vector2(SPLITS_WIDTH + SPLITS_LEFT - num2, num + SPLITS_LINE_HEIGHT); ((TMP_Text)val5).fontSize = SPLITS_FONT_SIZE; } } public static string DurationToString(TimeSpan duration) { return duration.ToString(((duration.Hours >= 1) ? "h\\:m" : "") + "m\\:ss\\.ff"); } private static TextMeshPro CreateText(Transform parent, string name) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) TextMeshPro obj = new GameObject("SpeedrunTimer_" + name) { layer = LayerMask.NameToLayer("Background") }.AddComponent<TextMeshPro>(); obj.transform.SetParent(parent); obj.sortingOrder = 100; ((TMP_Text)obj).color = new Color(0.5f, 0.5f, 0.5f); return obj; } } public class WebsocketServer { public class Client { public TcpClient TcpClient; public List<byte> MessageBuffer = new List<byte>(); public BlockingCollection<byte[]> SendQueue = new BlockingCollection<byte[]>(); public void Send(string data) { Send(Encoding.UTF8.GetBytes(data), isString: true); } public void Send(byte[] data, bool isString = false) { SendQueue.Add(CreateMessageFrame(data, isString)); } } public TcpListener listener; private List<Client> connectedClients = new List<Client>(); public Action<Client> OnConnect; public Action<string, Client> OnMessage; public void Start(int port = 6161, string ip = null) { listener = new TcpListener((ip != null) ? IPAddress.Parse(ip) : IPAddress.Any, port); listener.Start(); Task.Run((Action)ListenForConnections); } public void Send(string data) { Send(Encoding.UTF8.GetBytes(data), isString: true); } public void Send(byte[] data, bool isString = false) { byte[] item = CreateMessageFrame(data, isString); Client[] array = connectedClients.ToArray(); foreach (Client client in array) { if (client.TcpClient.Connected) { client.SendQueue.Add(item); } else { connectedClients.Remove(client); } } } private void ListenForConnections() { while (true) { try { TcpClient tcpClient = listener.AcceptTcpClient(); MelonLogger.Msg("Websocket client connected"); Client client = new Client { TcpClient = tcpClient }; connectedClients.Add(client); Task.Run(delegate { HandleClient(client); }); Thread thread = new Thread((ThreadStart)delegate { ListenToSendQueue(client); }); thread.IsBackground = true; thread.Start(); } catch (Exception ex) { MelonLogger.Error("Error listening for Websocket connections:"); MelonLogger.Error((object)ex); Thread.Sleep(5000); } } } private void HandleClient(Client client) { byte[] array = new byte[1024]; try { NetworkStream stream = client.TcpClient.GetStream(); while (client.TcpClient.Connected) { int num = stream.Read(array, 0, array.Length); if (num == 0) { break; } byte[] array2 = new byte[num]; Array.Copy(array, array2, num); if (IsClientHandshake(array2)) { RespondToClientHandshake(array2, client); continue; } byte[] array3 = ReadMessage(array2, client); if (array3 != null) { string @string = Encoding.UTF8.GetString(array3); if (OnMessage != null) { OnMessage(@string, client); } client.MessageBuffer.Clear(); } } } catch (Exception ex) { MelonLogger.Warning("Error with websocket: {0}", new object[1] { ex }); } finally { CloseConnection(client); } } private void CloseConnection(Client client) { if (connectedClients.Contains(client)) { MelonLogger.Msg("Websocket disconnected"); client.SendQueue.CompleteAdding(); connectedClients.Remove(client); } else { client.TcpClient.Dispose(); } } private void ListenToSendQueue(Client client) { try { NetworkStream stream = client.TcpClient.GetStream(); foreach (byte[] item in client.SendQueue.GetConsumingEnumerable()) { if (client.TcpClient.Connected) { stream.Write(item, 0, item.Length); continue; } break; } } catch (Exception ex) { MelonLogger.Warning("Websocket error:"); MelonLogger.Warning((object)ex); } finally { CloseConnection(client); } } private bool IsClientHandshake(byte[] request) { return Regex.IsMatch(Encoding.UTF8.GetString(request, 0, 4), "^GET\\s", RegexOptions.IgnoreCase); } private void RespondToClientHandshake(byte[] request, Client client) { NetworkStream stream = client.TcpClient.GetStream(); string @string = Encoding.UTF8.GetString(request); byte[] bytes = Encoding.UTF8.GetBytes(string.Join("\r\n", "HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", "Upgrade: websocket", "Sec-WebSocket-Accept: " + GenerateWebsocketAcceptToken(@string), "", "")); stream.Write(bytes, 0, bytes.Length); OnConnect(client); } private string GenerateWebsocketAcceptToken(string request) { string s = Regex.Match(request, "\\n\\s*Sec-WebSocket-Key\\s*:(.*)", RegexOptions.IgnoreCase).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; return Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(s))); } private byte[] ReadMessage(byte[] request, Client client) { bool flag = (request[0] & 0x80) != 0; if ((request[1] & 0x80) == 0) { return null; } int num = 2; ulong num2 = (ulong)(int)(request[1] & 0x7Fu); switch (num2) { case 126uL: num2 = BitConverter.ToUInt16(new byte[2] { request[3], request[2] }, 0); num = 4; break; case 127uL: num2 = BitConverter.ToUInt64(new byte[8] { request[9], request[8], request[7], request[6], request[5], request[4], request[3], request[2] }, 0); num = 10; break; } byte[] array = new byte[num2]; int num3 = num; num += 4; for (ulong num4 = 0uL; num4 < num2; num4++) { array[num4] = (byte)(request[num + (int)num4] ^ request[num3 + ((int)num4 & 3)]); } client.MessageBuffer.AddRange(array); if (!flag) { return null; } return client.MessageBuffer.ToArray(); } private static byte[] CreateMessageFrame(byte[] data, bool isString) { byte[] array; if (data.Length < 126) { array = new byte[data.Length + 2]; array[1] = (byte)data.Length; } else if (data.Length <= 65535) { array = new byte[data.Length + 4]; array[1] = 126; byte[] bytes = BitConverter.GetBytes((ushort)data.Length); array[2] = bytes[1]; array[3] = bytes[0]; } else { array = new byte[data.Length + 10]; array[1] = 127; byte[] bytes2 = BitConverter.GetBytes((ulong)data.Length); for (int i = 0; i < 8; i++) { array[9 - i] = bytes2[i]; } } array[0] = (byte)(isString ? 129u : 130u); Array.Copy(data, 0, array, array.Length - data.Length, data.Length); return array; } } }
Patch2_MelonLoader0.5/Mods/SpeedrunTimer.P2.ML5.dll
Decompiled 4 months agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using SLZ.Bonelab; using SLZ.Interaction; using SLZ.Marrow.Input; using SLZ.Marrow.SceneStreaming; using SLZ.Marrow.Utilities; using SLZ.Marrow.Warehouse; using SLZ.Rig; using SLZ.SaveData; using Sst.SpeedrunTimer; using Sst.Utilities; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("SpeedrunTimer")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany(null)] [assembly: AssemblyProduct("SpeedrunTimer")] [assembly: AssemblyCopyright("Created by jakzo")] [assembly: AssemblyTrademark(null)] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("1.8.1")] [assembly: NeutralResourcesLanguage("en")] [assembly: MelonInfo(typeof(Mod), "SpeedrunTimer", "1.8.1", "jakzo", "https://bonelab.thunderstore.io/package/jakzo/SpeedrunTimer/")] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.8.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Sst { public class Metadata { public const string AUTHOR = "jakzo"; public const string COMPANY = null; public const string DEVELOPER = "Stress Level Zero"; public const string GAME = "BONELAB"; public const string GAME_BONEWORKS = "BONEWORKS"; } public class Dbg { private static MelonPreferences_Entry<bool> _prefPrintDebugLogs; public static void Init(string prefCategoryId) { _prefPrintDebugLogs = MelonPreferences.CreateCategory(prefCategoryId).CreateEntry<bool>("printDebugLogs", false, "Print debug logs", "Print debug logs to console", false, true, (ValueValidator)null, (string)null); } public static void Log(string msg, params object[] data) { if (_prefPrintDebugLogs.Value) { MelonLogger.Msg("dbg: " + msg); } } } } namespace Sst.Utilities { internal static class LevelHooks { [HarmonyPatch(typeof(BasicTrackingRig), "Awake")] private class BasicTrackingRig_Awake_Patch { [CompilerGenerated] private static class <>O { public static LemonAction <0>__WaitForLoadFinished; } [HarmonyPrefix] internal static void Prefix(BasicTrackingRig __instance) { //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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown Dbg.Log("BasicTrackingRig_Awake_Patch"); _loadingScene = ((Component)__instance).gameObject.scene; if (Object.op_Implicit((Object)(object)CurrentLevel)) { PrevLevel = CurrentLevel; } CurrentLevel = null; NextLevel = SceneStreamer.Session.Level; RigManager = null; BasicTrackingRig = __instance; MelonEvent onUpdate = MelonEvents.OnUpdate; object obj = <>O.<0>__WaitForLoadFinished; if (obj == null) { LemonAction val = WaitForLoadFinished; <>O.<0>__WaitForLoadFinished = val; obj = (object)val; } ((MelonEventBase<LemonAction>)(object)onUpdate).Subscribe((LemonAction)obj, 0, false); LevelCrate nextLevel = NextLevel; Dbg.Log("OnLoad " + ((nextLevel != null) ? ((Scannable)nextLevel).Title : null)); SafeInvoke("OnLoad", LevelHooks.OnLoad, NextLevel); } } [HarmonyPatch(typeof(RigManager), "Awake")] private class RigManager_Awake_Patch { [HarmonyPrefix] internal static void Prefix(RigManager __instance) { Dbg.Log("RigManager_Awake_Patch"); RigManager = __instance; BasicTrackingRig = null; } } [CompilerGenerated] private static class <>O { public static LemonAction <0>__WaitForLoadFinished; } public static LevelCrate PrevLevel; public static LevelCrate CurrentLevel; public static LevelCrate NextLevel; public static RigManager RigManager; public static BasicTrackingRig BasicTrackingRig; private static Scene _loadingScene; public static bool IsLoading => !Object.op_Implicit((Object)(object)CurrentLevel); public static event Action<LevelCrate> OnLoad; public static event Action<LevelCrate> OnLevelStart; private static void SafeInvoke(string name, Action<LevelCrate> action, LevelCrate level) { try { action?.Invoke(level); } catch (Exception ex) { MelonLogger.Error("Failed to execute " + name + " event: " + ex.ToString()); } } private static void WaitForLoadFinished() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown if (!((Scene)(ref _loadingScene)).isLoaded) { MelonEvent onUpdate = MelonEvents.OnUpdate; object obj = <>O.<0>__WaitForLoadFinished; if (obj == null) { LemonAction val = WaitForLoadFinished; <>O.<0>__WaitForLoadFinished = val; obj = (object)val; } ((MelonEventBase<LemonAction>)(object)onUpdate).Unsubscribe((LemonAction)obj); CurrentLevel = NextLevel ?? SceneStreamer.Session.Level ?? CurrentLevel; NextLevel = null; LevelCrate currentLevel = CurrentLevel; Dbg.Log("OnLevelStart " + ((currentLevel != null) ? ((Scannable)currentLevel).Title : null)); SafeInvoke("OnLevelStart", LevelHooks.OnLevelStart, CurrentLevel); } } } public class Bonelab { public static void DockToWrist(GameObject gameObject, bool rightHand = false) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) PhysicsRig physicsRig = LevelHooks.RigManager.physicsRig; Hand val = (rightHand ? physicsRig.rightHand : physicsRig.leftHand); gameObject.transform.SetParent(((Component)val).transform); gameObject.transform.localPosition = new Vector3(-0.31f, 0.3f, 0f); gameObject.transform.localRotation = Quaternion.Euler(32f, 4f, 3f); } public static TextMeshPro CreateTextOnWrist(string name, bool rightHand = false) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); TextMeshPro obj = val.AddComponent<TextMeshPro>(); ((TMP_Text)obj).alignment = (TextAlignmentOptions)1028; ((TMP_Text)obj).fontSize = 0.5f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); DockToWrist(val, rightHand); return obj; } } public class Levels { public class Barcodes { public const string DESCENT = "c2534c5a-4197-4879-8cd3-4a695363656e"; public const string HUB = "c2534c5a-6b79-40ec-8e98-e58c5363656e"; public const string LONG_RUN = "c2534c5a-56a6-40ab-a8ce-23074c657665"; public const string MINE_DIVE = "c2534c5a-54df-470b-baaf-741f4c657665"; public const string BIG_ANOMALY_A = "c2534c5a-7601-4443-bdfe-7f235363656e"; public const string STREET_PUNCHER = "SLZ.BONELAB.Content.Level.LevelStreetPunch"; public const string SPRINT_BRIDGE = "SLZ.BONELAB.Content.Level.SprintBridge04"; public const string MAGMA_GATE = "SLZ.BONELAB.Content.Level.SceneMagmaGate"; public const string MOON_BASE = "SLZ.BONELAB.Content.Level.MoonBase"; public const string MONOGON_MOTORWAY = "SLZ.BONELAB.Content.Level.LevelKartRace"; public const string PILLAR = "c2534c5a-c056-4883-ac79-e051426f6964"; public const string BIG_ANOMALY_B = "SLZ.BONELAB.Content.Level.LevelBigAnomalyB"; public const string ASCENT = "c2534c5a-db71-49cf-b694-24584c657665"; public const string OUTRO = "SLZ.BONELAB.Content.Level.LevelOutro"; public const string ROOFTOPS = "c2534c5a-c6ac-48b4-9c5f-b5cd5363656e"; public const string TUNNEL_TIPPER = "c2534c5a-c180-40e0-b2b7-325c5363656e"; public const string DISTRICT_TAC_TRIAL = "c2534c5a-4f3b-480e-ad2f-69175363656e"; public const string DROP_PIT = "c2534c5a-de61-4df9-8f6c-416954726547"; public const string TUSCANY = "c2534c5a-2c4c-4b44-b076-203b5363656e"; public const string MAIN_MENU = "c2534c5a-80e1-4a29-93ca-f3254d656e75"; public const string VOID_G114 = "fa534c5a868247138f50c62e424c4144.Level.VoidG114"; public const string FANTASY_ARENA = "fa534c5a868247138f50c62e424c4144.Level.LevelArenaMin"; } public class LabworksBarcodes { public const string BREAKROOM = "volx4.LabWorksBoneworksPort.Level.Boneworks01Breakroom"; public const string THRONE_ROOM = "volx4.LabWorksBoneworksPort.Level.Boneworks12ThroneRoom"; public const string MAIN_MENU = "volx4.LabWorksBoneworksPort.Level.BoneworksMainMenu"; } public const string TITLE_DESCENT = "01 - Descent"; public const string TITLE_MONOGON_MOTORWAY = "10 - Monogon Motorway"; public static Dictionary<string, byte> TitleToIndex = new Dictionary<string, byte> { ["01 - Descent"] = 1, ["02 - BONELAB Hub"] = 2, ["03 - LongRun"] = 3, ["04 - Mine Dive"] = 4, ["05 - Big Anomaly"] = 5, ["06 - Street Puncher"] = 6, ["07 - Sprint Bridge 04"] = 7, ["08 - Magma Gate"] = 8, ["09- MoonBase"] = 9, ["10 - Monogon Motorway"] = 10, ["11 - Pillar Climb"] = 11, ["12 - Big Anomaly B"] = 12, ["13 - Ascent"] = 13, ["14 - Home"] = 14, ["15 - Void G114"] = 15, ["Big Bone Bowling"] = 16, ["Container Yard"] = 17, ["Neon District Parkour"] = 18, ["Neon District Tac Trial"] = 19, ["Drop Pit"] = 20, ["Dungeon Warrior"] = 21, ["Fantasy Arena"] = 22, ["Gun Range"] = 23, ["Halfway Park"] = 24, ["HoloChamber"] = 25, ["Mirror"] = 26, ["Museum Basement"] = 27, ["Rooftops"] = 28, ["Tunnel Tipper"] = 29, ["Tuscany"] = 30, ["00 - Main Menu"] = 31, ["Baseline"] = 32, ["Load Default"] = 33, ["Load Mod"] = 34, ["Boneworks Main Menu"] = 100 }; public static string[] CAMPAIGN_LEVEL_BARCODES = new string[14] { "c2534c5a-4197-4879-8cd3-4a695363656e", "c2534c5a-6b79-40ec-8e98-e58c5363656e", "c2534c5a-56a6-40ab-a8ce-23074c657665", "c2534c5a-54df-470b-baaf-741f4c657665", "c2534c5a-7601-4443-bdfe-7f235363656e", "SLZ.BONELAB.Content.Level.LevelStreetPunch", "SLZ.BONELAB.Content.Level.SprintBridge04", "SLZ.BONELAB.Content.Level.SceneMagmaGate", "SLZ.BONELAB.Content.Level.MoonBase", "SLZ.BONELAB.Content.Level.LevelKartRace", "c2534c5a-c056-4883-ac79-e051426f6964", "SLZ.BONELAB.Content.Level.LevelBigAnomalyB", "c2534c5a-db71-49cf-b694-24584c657665", "SLZ.BONELAB.Content.Level.LevelOutro" }; public static HashSet<string> CAMPAIGN_LEVEL_BARCODES_SET = CAMPAIGN_LEVEL_BARCODES.ToHashSet(); public static byte GetIndex(string title) { if (TitleToIndex.TryGetValue(title, out var value)) { return value; } Match match = Regex.Match(title, "^Boneworks_(\\d+) "); if (match.Success) { return (byte)(100 + int.Parse(match.Groups[1].Value)); } return 0; } public static bool IsMenu(string barcode) { if (!(barcode == "c2534c5a-80e1-4a29-93ca-f3254d656e75")) { return barcode == "fa534c5a868247138f50c62e424c4144.Level.VoidG114"; } return true; } } } namespace Sst.SpeedrunTimer { internal static class AppVersion { public const string Value = "1.8.1"; } public static class BuildInfo { public const string Name = "SpeedrunTimer"; } public class InputServer { private const float DEGREES_TO_RADIANS = (float)Math.PI / 180f; private WebsocketServer websocketServer; public InputServer(int port = 6161, string ip = null) { websocketServer = new WebsocketServer { OnConnect = delegate(WebsocketServer.Client client) { client.Send("{\"inputVersion\":1}"); } }; websocketServer.Start(port, ip); MelonLogger.Msg("Input viewer server started at:\n" + string.Join("\n", ((ip == null) ? Network.GetAllAddresses() : new string[1] { ip }).Select((string address) => $"ws://{address}:{port}"))); } public void SendInputState() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) XRApi xr = MarrowGame.xr; if (((xr != null) ? xr.HMD : null) == null) { return; } byte item = 1; List<byte> list = new List<byte>(128) { item }; AddToData(list, Time.unscaledTime); AddToData(list, (XRDevice)(object)MarrowGame.xr.HMD); XRController[] array = (XRController[])(object)new XRController[2] { MarrowGame.xr.LeftController, MarrowGame.xr.RightController }; foreach (XRController val in array) { if (val != null) { AddToData(list, (XRDevice)(object)val); } else { AddToData(list, default(Vector3)); AddToData(list, default(Quaternion)); } bool[] obj = new bool[15] { val.IsConnected, val.TriggerButton, val.TriggerTouched, val.GripButton, false, val.TouchpadButton, val.TouchpadTouch, val.JoystickButton, val.JoystickTouch, val.AButton, val.ATouch, val.BButton, val.BTouch, val.MenuButton, false }; int num = 0; int num2 = 0; bool[] array2 = obj; for (int j = 0; j < array2.Length; j++) { if (array2[j]) { num |= 1 << num2; } num2++; } list.AddRange(BitConverter.GetBytes(num)); AddToData(list, val.Touchpad2DAxis); AddToData(list, val.Joystick2DAxis); AddToData(list, val.Trigger); AddToData(list, val.Grip); } websocketServer.Send(list.ToArray()); } private void AddToData(List<byte> data, int value) { data.AddRange(BitConverter.GetBytes(value)); } private void AddToData(List<byte> data, float value) { data.AddRange(BitConverter.GetBytes(value)); } private void AddToData(List<byte> data, Vector2 value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.x); AddToData(data, value.y); } private void AddToData(List<byte> data, Vector3 value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.x); AddToData(data, value.y); AddToData(data, value.z); } private void AddToData(List<byte> data, Quaternion value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) AddToData(data, value.x); AddToData(data, value.y); AddToData(data, value.z); AddToData(data, value.w); } private void AddToData(List<byte> data, XRDevice value) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.Position); AddToData(data, value.Rotation); } private void AddToData(List<byte> data, Transform value) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.localPosition); AddToData(data, value.localRotation); } } public class Mod : MelonMod { public const string PREF_CATEGORY_ID = "SpeedrunTimer"; private static SplitsTimer _timer = new SplitsTimer(); private InputServer _inputServer; public MelonPreferences_Category PrefCategory; public SplitsServer SplitsServer; public static Mod Instance; public Mod() { Instance = this; } public override void OnInitializeMelon() { Dbg.Init("SpeedrunTimer"); PrefCategory = MelonPreferences.CreateCategory("SpeedrunTimer"); SplitsTimer.OnInitialize(); SaveDeleteImprovements.OnInitialize(); LevelHooks.OnLoad += _timer.OnLoadingScreen; LevelHooks.OnLevelStart += _timer.OnLevelStart; SplitsServer = new SplitsServer(); _inputServer = new InputServer(); } public override void OnUpdate() { _timer.OnUpdate(); _inputServer?.SendInputState(); } } public static class SaveDeleteImprovements { [HarmonyPatch(typeof(DataManager), "_MSAFAIGE")] private class DataManager_MSAFAIGE_Patch { [HarmonyPrefix] internal static void Prefix() { Dbg.Log("DataManager_MSAFAIGE_Prefix"); if (!_prefDeleteModsOnWipe.Value) { _modsBackupPath = Path.Combine(Application.persistentDataPath, "Mods.backup"); if (Directory.Exists(_modsBackupPath)) { Directory.Delete(_modsBackupPath, recursive: true); } Directory.Move(GetModsPath(), _modsBackupPath); } } [HarmonyPostfix] internal static void Postfix() { Dbg.Log("DataManager_MSAFAIGE_Postfix"); if (_modsBackupPath != null) { if (Directory.Exists(_modsBackupPath)) { MelonLogger.Warning("Failed to restore mods folder on data wipe! Old mods are in Mods.backup now."); } _modsBackupPath = null; } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] private class DataManager_SavePath_Patch { [HarmonyPrefix] internal static void Prefix() { Dbg.Log("DataManager_SavePath_Patch"); RestoreModsBackup(); } } private static MelonPreferences_Entry<bool> _prefDeleteModsOnWipe; private static string _modsBackupPath; private static string GetModsPath() { return Path.Combine(Application.persistentDataPath, "Mods"); } public static void OnInitialize() { _prefDeleteModsOnWipe = Mod.Instance.PrefCategory.CreateEntry<bool>("deleteModsOnWipe", false, "Let mods be deleted when wiping all data", "Normally when resetting your save state through the main menu the game will delete all mods including the SpeedrunTimer. For convenience the timer will keep your mods folder. This option reverts to the original behavior of deleting mods.", false, false, (ValueValidator)null, (string)null); } private static void RestoreModsBackup() { if (_modsBackupPath != null && Directory.Exists(_modsBackupPath) && !Directory.Exists(GetModsPath())) { Directory.Move(_modsBackupPath, GetModsPath()); _modsBackupPath = null; } } } public class SplitsServer { private WebsocketServer _ws; public SplitsServer(int port = 6162, string ip = null) { SplitsServer splitsServer = this; _ws = new WebsocketServer { OnConnect = delegate { splitsServer.InitGameTime(); } }; _ws.Start(port, ip); MelonLogger.Msg("Splits server started at:\n" + string.Join("\n", ((ip == null) ? Network.GetAllAddresses() : new string[1] { ip }).Select((string address) => $"ws://{address}:{port}"))); } public void Start() { _ws.Send("start"); } public void Split() { _ws.Send("split"); } public void SplitOrStart() { _ws.Send("splitorstart"); } public void Reset() { _ws.Send("reset"); } public void TogglePause() { _ws.Send("togglepause"); } public void Undo() { _ws.Send("undo"); } public void Skip() { _ws.Send("skip"); } public void InitGameTime() { _ws.Send("initgametime"); } public void SetGameTime(TimeSpan time) { _ws.Send($"setgametime {time.TotalSeconds}"); } public void SetLoadingTimes(TimeSpan times) { _ws.Send($"setloadingtimes {times.TotalSeconds}"); } public void PauseGameTime() { _ws.Send("pausegametime"); } public void ResumeGameTime() { _ws.Send("resumegametime"); } } internal class SplitsTimer { [HarmonyPatch(typeof(TaxiController), "Start")] private class TaxiController_Start_Patch { [HarmonyPrefix] internal static void Prefix(TaxiController __instance) { Dbg.Log("TaxiController_Start_Patch"); __instance.OnPlayerSeated.AddListener(UnityAction.op_Implicit((Action)Instance.Finish)); } } private static Color FINISH_COLOR = new Color(0.2f, 0.8f, 0.2f); private static SplitsTimer Instance; private TextMeshPro _tmp; private TextMeshPro _tmpIl; private Splits _splits = new Splits(); private bool _isFinished; private static MelonPreferences_Entry<bool> _prefHide; private static MelonPreferences_Entry<bool> _prefHideIl; private static MelonPreferences_Entry<bool> _prefHideSplits; public SplitsTimer() { Instance = this; Livesplit.SetState(isLoading: true, isSittingInTaxi: false); } public static void OnInitialize() { _prefHide = Mod.Instance.PrefCategory.CreateEntry<bool>("hide", false, "Hide in-game timer", "Stops the timer from displaying on your wrist. Does not hide loading screen timer.", false, false, (ValueValidator)null, (string)null); _prefHideIl = Mod.Instance.PrefCategory.CreateEntry<bool>("hideIl", false, "Hide level timer", "Stops the individual level timer from displaying on your wrist.", false, false, (ValueValidator)null, (string)null); _prefHideSplits = Mod.Instance.PrefCategory.CreateEntry<bool>("hideSplits", false, "Hides split display in loading screen", "Stops the individual level times from displaying in the loading screen.", false, false, (ValueValidator)null, (string)null); } public void Reset() { _isFinished = false; _splits.Reset(); Mod.Instance.SplitsServer?.Reset(); Livesplit.SetState(isLoading: false, isSittingInTaxi: false); } public void OnLoadingScreen(LevelCrate nextLevel) { if ((Object)(object)nextLevel == (Object)null) { return; } if (_isFinished) { _splits.Reset(); _isFinished = false; } if (Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.BoneworksMainMenu" && Barcode.op_Implicit(((Scannable)LevelHooks.PrevLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.Boneworks12ThroneRoom") { _splits.Split(null); Finish(); } else { Livesplit.SetState(isLoading: true, isSittingInTaxi: false, ((Scannable)nextLevel).Title); if (Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "c2534c5a-4197-4879-8cd3-4a695363656e" || Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.Boneworks01Breakroom") { Dbg.Log("Attempting to start timer"); Mod.Instance.SplitsServer?.Start(); _splits.ResetAndPause(nextLevel); } else if (_splits.TimeStart.HasValue) { Dbg.Log("Splitting timer"); Mod.Instance.SplitsServer?.Split(); _splits.Pause(); _splits.Split(nextLevel); } } TimeSpan? time = _splits.GetTime(); if (time.HasValue) { Mod.Instance.SplitsServer?.PauseGameTime(); Mod.Instance.SplitsServer?.SetGameTime(time.Value); SplitsRenderer.RenderLoadingWatermark(time.Value); if (!_prefHideSplits.Value) { SplitsRenderer.RenderSplits(_splits); } } } public void OnLevelStart(LevelCrate level) { Livesplit.SetState(isLoading: false, isSittingInTaxi: false, ((Scannable)level).Title); Mod.Instance.SplitsServer?.ResumeGameTime(); if (!_isFinished) { _splits.ResumeIfStarted(); } AddTimerToWrist(); } private void AddTimerToWrist() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) if (!_prefHide.Value) { GameObject val = new GameObject("SpeedrunTimer_Wrist_Text"); _tmp = val.AddComponent<TextMeshPro>(); ((TMP_Text)_tmp).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_tmp).fontSize = 0.5f; ((TMP_Text)_tmp).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); if (_isFinished) { ((TMP_Text)_tmp).color = FINISH_COLOR; } Bonelab.DockToWrist(val); if (!_prefHideIl.Value) { GameObject val2 = new GameObject("SpeedrunTimer_Wrist_IL"); GameObject val3 = new GameObject("SpeedrunTimer_Wrist_IL_Offset"); val3.transform.parent = val2.transform; _tmpIl = val3.AddComponent<TextMeshPro>(); ((TMP_Text)_tmpIl).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_tmpIl).fontSize = 0.3f; ((TMP_Text)_tmpIl).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); ((Transform)((TMP_Text)_tmpIl).rectTransform).localPosition = new Vector3(-0.005f, -0.03f, 0.005f); ((TMP_Text)_tmpIl).color = new Color(0.2f, 0.2f, 0.2f); Bonelab.DockToWrist(val2); } } } public void OnUpdate() { if ((Object)(object)_tmp != (Object)null) { TimeSpan? time = _splits.GetTime(); if (!time.HasValue) { return; } ((Component)_tmp).gameObject.active = true; ((TMP_Text)_tmp).SetText(SplitsRenderer.DurationToString(time.Value), true); } if ((Object)(object)_tmpIl != (Object)null) { TimeSpan? currentSplitTime = _splits.GetCurrentSplitTime(); if (currentSplitTime.HasValue) { ((Component)_tmpIl).gameObject.active = true; ((TMP_Text)_tmpIl).SetText(SplitsRenderer.DurationToString(currentSplitTime.Value), true); } } } public void Finish() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) LevelCrate currentLevel = LevelHooks.CurrentLevel; Livesplit.SetState(isLoading: false, isSittingInTaxi: true, ((currentLevel != null) ? ((Scannable)currentLevel).Title : null) ?? ""); _splits.Pause(); Mod.Instance.SplitsServer?.Split(); Mod.Instance.SplitsServer?.SetGameTime(_splits.GetTime().Value); _isFinished = true; MelonLogger.Msg($"Stopping timer at: {_splits.GetTime()}"); if ((Object)(object)_tmp != (Object)null) { ((TMP_Text)_tmp).color = FINISH_COLOR; } } } internal class TimeTrialDisplayFix { [HarmonyPatch(typeof(BoneLeaderManager), "SubmitLeaderboardScore")] private class BoneLeaderManager_SubmitLeaderboardScore_Patch { [HarmonyPostfix] internal static void Postfix(BoneLeaderManager __instance, uint score) { DisplayTime(__instance, score); } } public const string LABEL_NAME = "TimeTrialDisplayFix_Label"; public static void DisplayTime(BoneLeaderManager leaderManager, uint milliseconds) { TMP_Text text_TitleBoard = leaderManager.text_TitleBoard; Transform parent = text_TitleBoard.transform.parent; Transform obj = parent.Find("TimeTrialDisplayFix_Label"); TextMeshPro obj2 = ((obj != null) ? ((Component)obj).GetComponent<TextMeshPro>() : null) ?? CreateTimeDisplay(parent, text_TitleBoard); string text = SplitsRenderer.DurationToString(TimeSpan.FromMilliseconds(milliseconds)); ((TMP_Text)obj2).SetText("Time: " + text, true); } private static TextMeshPro CreateTimeDisplay(Transform canvas, TMP_Text title) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_002c: 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) GameObject val = new GameObject("TimeTrialDisplayFix_Label"); val.transform.SetParent(canvas, false); val.transform.localPosition = new Vector3(0f, 1.2f, 3f); TextMeshPro obj = val.AddComponent<TextMeshPro>(); ((TMP_Text)obj).alignment = (TextAlignmentOptions)514; ((TMP_Text)obj).font = title.font; ((TMP_Text)obj).fontSize = 4f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(10f, 1f); return obj; } } internal class Livesplit { private static byte[] State = new byte[12] { 0, 226, 3, 52, 194, 223, 99, 36, 0, 0, 0, 0 }; public static void SetState(bool isLoading, bool isSittingInTaxi, string levelTitle = "") { State[0] = 212; State[8] = (byte)((isLoading ? 1u : 0u) | ((isSittingInTaxi ? 1u : 0u) << 1)); State[9] = Levels.GetIndex(levelTitle); } } public static class Network { public static string[] GetAllAddresses() { return new string[1] { "127.0.0.1" }.Concat(from address in Dns.GetHostEntry(Dns.GetHostName()).AddressList where address.AddressFamily == AddressFamily.InterNetwork select address.ToString()).ToArray(); } } public class Splits { private static Regex _levelPrefixPattern = new Regex("^\\s*\\d+\\s*-\\s*"); public List<Split> Items = new List<Split>(); public DateTime? TimeStart; public DateTime? TimeEnd; public DateTime? TimeStartRelative; public DateTime? TimePause; public DateTime TimeLastSplitStartRelative = DateTime.Now; private static string LevelSplitName(LevelCrate level) { return _levelPrefixPattern.Replace(((Scannable)level).Title, ""); } public void Reset() { TimeStart = null; TimeEnd = null; TimeStartRelative = null; TimePause = null; } public void ResetAndPause(LevelCrate firstLevel) { ResetAndStart(firstLevel); TimePause = TimeStart; } public void ResetAndStart(LevelCrate firstLevel) { DateTime now = DateTime.Now; TimeEnd = (TimePause = null); TimeStart = (TimeStartRelative = (TimeLastSplitStartRelative = now)); Items = new List<Split> { new Split { Level = firstLevel, Name = LevelSplitName(firstLevel), TimeStart = now } }; } public void Pause() { TimePause = DateTime.Now; } public void ResumeIfStarted() { if (TimePause.HasValue) { TimeSpan timeSpan = DateTime.Now - TimePause.Value; TimeStartRelative += timeSpan; TimeLastSplitStartRelative += timeSpan; TimePause = null; } } public TimeSpan? GetTime() { DateTime value = TimeEnd ?? TimePause ?? DateTime.Now; DateTime? timeStartRelative = TimeStartRelative; return value - timeStartRelative; } public TimeSpan? GetCurrentSplitTime() { return (TimeEnd ?? TimePause ?? DateTime.Now) - TimeLastSplitStartRelative; } public void Split(LevelCrate nextLevel) { Split split = Items[Items.Count - 1]; DateTime now = DateTime.Now; split.TimeEnd = now; DateTime valueOrDefault = TimePause.GetValueOrDefault(now); split.Duration = valueOrDefault - TimeLastSplitStartRelative; TimeLastSplitStartRelative = valueOrDefault; if (Object.op_Implicit((Object)(object)nextLevel)) { Items.Add(new Split { Level = nextLevel, Name = LevelSplitName(nextLevel), TimeStart = now }); } } } public class Split { public LevelCrate Level; public string Name; public DateTime? TimeStart; public DateTime? TimeEnd; public TimeSpan? Duration; } internal class SplitsRenderer { private static float SPLITS_WIDTH = 0.3f; private static float SPLITS_LINE_HEIGHT = 0.05f; private static float SPLITS_LEFT = -0.1f; private static float SPLITS_FONT_SIZE = 0.3f; public static void RenderLoadingWatermark(TimeSpan time) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) Dbg.Log("RenderLoadingWatermark"); BasicTrackingRig basicTrackingRig = LevelHooks.BasicTrackingRig; Transform val = ((basicTrackingRig != null) ? basicTrackingRig.head : null); if (!Object.op_Implicit((Object)(object)val)) { MelonLogger.Warning("Failed to render watermark in loading screen because could not find head position"); return; } TextMeshPro obj = CreateText(val, "Watermark"); ((TMP_Text)obj).alignment = (TextAlignmentOptions)260; ((TMP_Text)obj).fontSize = 0.5f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(0.8f, 0.8f); ((Transform)((TMP_Text)obj).rectTransform).localPosition = new Vector3(0f, 0f, 1f); ((TMP_Text)obj).color = new Color(0.4f, 0.4f, 0.6f); string text = ""; string text2 = DurationToString(time); string text3 = string.Join("\n", MelonTypeBase<MelonMod>.RegisteredMelons.Select((MelonMod mod) => ((MelonBase)mod).Info.Name)); ((TMP_Text)obj).SetText("SpeedrunTimer v1.8.1" + text + "\n" + text2 + "\n\nMods:\n" + text3, true); } public static void RenderSplits(Splits splits) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) BasicTrackingRig basicTrackingRig = LevelHooks.BasicTrackingRig; Transform val = ((basicTrackingRig != null) ? basicTrackingRig.head : null); if (!Object.op_Implicit((Object)(object)val)) { return; } GameObject val2 = new GameObject("SpeedrunTimer_Splits"); val2.transform.SetParent(((Component)val).transform); val2.transform.localPosition = new Vector3(-0.4f, 0.25f, 1f); Vector2 val4 = default(Vector2); for (int i = 0; i < splits.Items.Count; i++) { Split split = splits.Items[i]; float num = (float)i * (0f - SPLITS_LINE_HEIGHT); float num2 = 0f; if (split.Duration.HasValue) { TextMeshPro val3 = CreateText(val2.transform, "Splits_Time_" + split.Name); string text = ((split.Duration.Value >= TimeSpan.FromHours(1.0)) ? "h\\:mm\\:ss" : "m\\:ss\\.f"); ((TMP_Text)val3).SetText(split.Duration.Value.ToString(text), true); ((TMP_Text)val3).alignment = (TextAlignmentOptions)516; ((TMP_Text)val3).overflowMode = (TextOverflowModes)3; ((Transform)((TMP_Text)val3).rectTransform).localPosition = new Vector3(0f, 0f, 0f); RectTransform rectTransform = ((TMP_Text)val3).rectTransform; RectTransform rectTransform2 = ((TMP_Text)val3).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform2.anchorMax = val4; rectTransform.anchorMin = val4; ((TMP_Text)val3).rectTransform.offsetMin = new Vector2(SPLITS_LEFT, num); ((TMP_Text)val3).rectTransform.offsetMax = new Vector2(SPLITS_WIDTH + SPLITS_LEFT, num + SPLITS_LINE_HEIGHT); ((TMP_Text)val3).fontSize = SPLITS_FONT_SIZE; val3.ForceMeshUpdate(false, false); num2 = ((TMP_Text)val3).preferredWidth; } TextMeshPro val5 = CreateText(val2.transform, "Splits_Name_" + split.Name); string text2 = Regex.Replace(split.Name, "^boneworks(?:_\\d+)?\\s+", "", RegexOptions.IgnoreCase); ((TMP_Text)val5).SetText(text2, true); ((TMP_Text)val5).alignment = (TextAlignmentOptions)513; ((TMP_Text)val5).overflowMode = (TextOverflowModes)3; ((Transform)((TMP_Text)val5).rectTransform).localPosition = new Vector3(0f, 0f, 0f); RectTransform rectTransform3 = ((TMP_Text)val5).rectTransform; RectTransform rectTransform4 = ((TMP_Text)val5).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform4.anchorMax = val4; rectTransform3.anchorMin = val4; ((TMP_Text)val5).rectTransform.offsetMin = new Vector2(SPLITS_LEFT, num); ((TMP_Text)val5).rectTransform.offsetMax = new Vector2(SPLITS_WIDTH + SPLITS_LEFT - num2, num + SPLITS_LINE_HEIGHT); ((TMP_Text)val5).fontSize = SPLITS_FONT_SIZE; } } public static string DurationToString(TimeSpan duration) { return duration.ToString(((duration.Hours >= 1) ? "h\\:m" : "") + "m\\:ss\\.ff"); } private static TextMeshPro CreateText(Transform parent, string name) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) TextMeshPro obj = new GameObject("SpeedrunTimer_" + name) { layer = LayerMask.NameToLayer("Background") }.AddComponent<TextMeshPro>(); obj.transform.SetParent(parent); obj.sortingOrder = 100; ((TMP_Text)obj).color = new Color(0.5f, 0.5f, 0.5f); return obj; } } public class WebsocketServer { public class Client { public TcpClient TcpClient; public List<byte> MessageBuffer = new List<byte>(); public BlockingCollection<byte[]> SendQueue = new BlockingCollection<byte[]>(); public void Send(string data) { Send(Encoding.UTF8.GetBytes(data), isString: true); } public void Send(byte[] data, bool isString = false) { SendQueue.Add(CreateMessageFrame(data, isString)); } } public TcpListener listener; private List<Client> connectedClients = new List<Client>(); public Action<Client> OnConnect; public Action<string, Client> OnMessage; public void Start(int port = 6161, string ip = null) { listener = new TcpListener((ip != null) ? IPAddress.Parse(ip) : IPAddress.Any, port); listener.Start(); Task.Run((Action)ListenForConnections); } public void Send(string data) { Send(Encoding.UTF8.GetBytes(data), isString: true); } public void Send(byte[] data, bool isString = false) { byte[] item = CreateMessageFrame(data, isString); Client[] array = connectedClients.ToArray(); foreach (Client client in array) { if (client.TcpClient.Connected) { client.SendQueue.Add(item); } else { connectedClients.Remove(client); } } } private void ListenForConnections() { while (true) { try { TcpClient tcpClient = listener.AcceptTcpClient(); MelonLogger.Msg("Websocket client connected"); Client client = new Client { TcpClient = tcpClient }; connectedClients.Add(client); Task.Run(delegate { HandleClient(client); }); Thread thread = new Thread((ThreadStart)delegate { ListenToSendQueue(client); }); thread.IsBackground = true; thread.Start(); } catch (Exception ex) { MelonLogger.Error("Error listening for Websocket connections:"); MelonLogger.Error((object)ex); Thread.Sleep(5000); } } } private void HandleClient(Client client) { byte[] array = new byte[1024]; try { NetworkStream stream = client.TcpClient.GetStream(); while (client.TcpClient.Connected) { int num = stream.Read(array, 0, array.Length); if (num == 0) { break; } byte[] array2 = new byte[num]; Array.Copy(array, array2, num); if (IsClientHandshake(array2)) { RespondToClientHandshake(array2, client); continue; } byte[] array3 = ReadMessage(array2, client); if (array3 != null) { string @string = Encoding.UTF8.GetString(array3); if (OnMessage != null) { OnMessage(@string, client); } client.MessageBuffer.Clear(); } } } catch (Exception ex) { MelonLogger.Warning("Error with websocket: {0}", new object[1] { ex }); } finally { CloseConnection(client); } } private void CloseConnection(Client client) { if (connectedClients.Contains(client)) { MelonLogger.Msg("Websocket disconnected"); client.SendQueue.CompleteAdding(); connectedClients.Remove(client); } else { client.TcpClient.Dispose(); } } private void ListenToSendQueue(Client client) { try { NetworkStream stream = client.TcpClient.GetStream(); foreach (byte[] item in client.SendQueue.GetConsumingEnumerable()) { if (client.TcpClient.Connected) { stream.Write(item, 0, item.Length); continue; } break; } } catch (Exception ex) { MelonLogger.Warning("Websocket error:"); MelonLogger.Warning((object)ex); } finally { CloseConnection(client); } } private bool IsClientHandshake(byte[] request) { return Regex.IsMatch(Encoding.UTF8.GetString(request, 0, 4), "^GET\\s", RegexOptions.IgnoreCase); } private void RespondToClientHandshake(byte[] request, Client client) { NetworkStream stream = client.TcpClient.GetStream(); string @string = Encoding.UTF8.GetString(request); byte[] bytes = Encoding.UTF8.GetBytes(string.Join("\r\n", "HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", "Upgrade: websocket", "Sec-WebSocket-Accept: " + GenerateWebsocketAcceptToken(@string), "", "")); stream.Write(bytes, 0, bytes.Length); OnConnect(client); } private string GenerateWebsocketAcceptToken(string request) { string s = Regex.Match(request, "\\n\\s*Sec-WebSocket-Key\\s*:(.*)", RegexOptions.IgnoreCase).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; return Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(s))); } private byte[] ReadMessage(byte[] request, Client client) { bool flag = (request[0] & 0x80) != 0; if ((request[1] & 0x80) == 0) { return null; } int num = 2; ulong num2 = (ulong)(int)(request[1] & 0x7Fu); switch (num2) { case 126uL: num2 = BitConverter.ToUInt16(new byte[2] { request[3], request[2] }, 0); num = 4; break; case 127uL: num2 = BitConverter.ToUInt64(new byte[8] { request[9], request[8], request[7], request[6], request[5], request[4], request[3], request[2] }, 0); num = 10; break; } byte[] array = new byte[num2]; int num3 = num; num += 4; for (ulong num4 = 0uL; num4 < num2; num4++) { array[num4] = (byte)(request[num + (int)num4] ^ request[num3 + ((int)num4 & 3)]); } client.MessageBuffer.AddRange(array); if (!flag) { return null; } return client.MessageBuffer.ToArray(); } private static byte[] CreateMessageFrame(byte[] data, bool isString) { byte[] array; if (data.Length < 126) { array = new byte[data.Length + 2]; array[1] = (byte)data.Length; } else if (data.Length <= 65535) { array = new byte[data.Length + 4]; array[1] = 126; byte[] bytes = BitConverter.GetBytes((ushort)data.Length); array[2] = bytes[1]; array[3] = bytes[0]; } else { array = new byte[data.Length + 10]; array[1] = 127; byte[] bytes2 = BitConverter.GetBytes((ulong)data.Length); for (int i = 0; i < 8; i++) { array[9 - i] = bytes2[i]; } } array[0] = (byte)(isString ? 129u : 130u); Array.Copy(data, 0, array, array.Length - data.Length, data.Length); return array; } } }
Patch3_MelonLoader0.5/Mods/SpeedrunTimer.P3.ML5.dll
Decompiled 4 months agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using SLZ.Bonelab; using SLZ.Interaction; using SLZ.Marrow.Input; using SLZ.Marrow.SceneStreaming; using SLZ.Marrow.Utilities; using SLZ.Marrow.Warehouse; using SLZ.Rig; using SLZ.SaveData; using Sst.SpeedrunTimer; using Sst.Utilities; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("SpeedrunTimer")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany(null)] [assembly: AssemblyProduct("SpeedrunTimer")] [assembly: AssemblyCopyright("Created by jakzo")] [assembly: AssemblyTrademark(null)] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("1.8.1")] [assembly: NeutralResourcesLanguage("en")] [assembly: MelonInfo(typeof(Mod), "SpeedrunTimer", "1.8.1", "jakzo", "https://bonelab.thunderstore.io/package/jakzo/SpeedrunTimer/")] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.8.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Sst { public class Metadata { public const string AUTHOR = "jakzo"; public const string COMPANY = null; public const string DEVELOPER = "Stress Level Zero"; public const string GAME = "BONELAB"; public const string GAME_BONEWORKS = "BONEWORKS"; } public class Dbg { private static MelonPreferences_Entry<bool> _prefPrintDebugLogs; public static void Init(string prefCategoryId) { _prefPrintDebugLogs = MelonPreferences.CreateCategory(prefCategoryId).CreateEntry<bool>("printDebugLogs", false, "Print debug logs", "Print debug logs to console", false, true, (ValueValidator)null, (string)null); } public static void Log(string msg, params object[] data) { if (_prefPrintDebugLogs.Value) { MelonLogger.Msg("dbg: " + msg); } } } } namespace Sst.Utilities { internal static class LevelHooks { [HarmonyPatch(typeof(BasicTrackingRig), "Awake")] private class BasicTrackingRig_Awake_Patch { [CompilerGenerated] private static class <>O { public static LemonAction <0>__WaitForLoadFinished; } [HarmonyPrefix] internal static void Prefix(BasicTrackingRig __instance) { //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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown Dbg.Log("BasicTrackingRig_Awake_Patch"); _loadingScene = ((Component)__instance).gameObject.scene; if (Object.op_Implicit((Object)(object)CurrentLevel)) { PrevLevel = CurrentLevel; } CurrentLevel = null; NextLevel = SceneStreamer.Session.Level; RigManager = null; BasicTrackingRig = __instance; MelonEvent onUpdate = MelonEvents.OnUpdate; object obj = <>O.<0>__WaitForLoadFinished; if (obj == null) { LemonAction val = WaitForLoadFinished; <>O.<0>__WaitForLoadFinished = val; obj = (object)val; } ((MelonEventBase<LemonAction>)(object)onUpdate).Subscribe((LemonAction)obj, 0, false); LevelCrate nextLevel = NextLevel; Dbg.Log("OnLoad " + ((nextLevel != null) ? ((Scannable)nextLevel).Title : null)); SafeInvoke("OnLoad", LevelHooks.OnLoad, NextLevel); } } [HarmonyPatch(typeof(RigManager), "Awake")] private class RigManager_Awake_Patch { [HarmonyPrefix] internal static void Prefix(RigManager __instance) { Dbg.Log("RigManager_Awake_Patch"); RigManager = __instance; BasicTrackingRig = null; } } [CompilerGenerated] private static class <>O { public static LemonAction <0>__WaitForLoadFinished; } public static LevelCrate PrevLevel; public static LevelCrate CurrentLevel; public static LevelCrate NextLevel; public static RigManager RigManager; public static BasicTrackingRig BasicTrackingRig; private static Scene _loadingScene; public static bool IsLoading => !Object.op_Implicit((Object)(object)CurrentLevel); public static event Action<LevelCrate> OnLoad; public static event Action<LevelCrate> OnLevelStart; private static void SafeInvoke(string name, Action<LevelCrate> action, LevelCrate level) { try { action?.Invoke(level); } catch (Exception ex) { MelonLogger.Error("Failed to execute " + name + " event: " + ex.ToString()); } } private static void WaitForLoadFinished() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown if (!((Scene)(ref _loadingScene)).isLoaded) { MelonEvent onUpdate = MelonEvents.OnUpdate; object obj = <>O.<0>__WaitForLoadFinished; if (obj == null) { LemonAction val = WaitForLoadFinished; <>O.<0>__WaitForLoadFinished = val; obj = (object)val; } ((MelonEventBase<LemonAction>)(object)onUpdate).Unsubscribe((LemonAction)obj); CurrentLevel = NextLevel ?? SceneStreamer.Session.Level ?? CurrentLevel; NextLevel = null; LevelCrate currentLevel = CurrentLevel; Dbg.Log("OnLevelStart " + ((currentLevel != null) ? ((Scannable)currentLevel).Title : null)); SafeInvoke("OnLevelStart", LevelHooks.OnLevelStart, CurrentLevel); } } } public class Bonelab { public static void DockToWrist(GameObject gameObject, bool rightHand = false) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) PhysicsRig physicsRig = LevelHooks.RigManager.physicsRig; Hand val = (rightHand ? physicsRig.rightHand : physicsRig.leftHand); gameObject.transform.SetParent(((Component)val).transform); gameObject.transform.localPosition = new Vector3(-0.31f, 0.3f, 0f); gameObject.transform.localRotation = Quaternion.Euler(32f, 4f, 3f); } public static TextMeshPro CreateTextOnWrist(string name, bool rightHand = false) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); TextMeshPro obj = val.AddComponent<TextMeshPro>(); ((TMP_Text)obj).alignment = (TextAlignmentOptions)1028; ((TMP_Text)obj).fontSize = 0.5f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); DockToWrist(val, rightHand); return obj; } } public class Levels { public class Barcodes { public const string DESCENT = "c2534c5a-4197-4879-8cd3-4a695363656e"; public const string HUB = "c2534c5a-6b79-40ec-8e98-e58c5363656e"; public const string LONG_RUN = "c2534c5a-56a6-40ab-a8ce-23074c657665"; public const string MINE_DIVE = "c2534c5a-54df-470b-baaf-741f4c657665"; public const string BIG_ANOMALY_A = "c2534c5a-7601-4443-bdfe-7f235363656e"; public const string STREET_PUNCHER = "SLZ.BONELAB.Content.Level.LevelStreetPunch"; public const string SPRINT_BRIDGE = "SLZ.BONELAB.Content.Level.SprintBridge04"; public const string MAGMA_GATE = "SLZ.BONELAB.Content.Level.SceneMagmaGate"; public const string MOON_BASE = "SLZ.BONELAB.Content.Level.MoonBase"; public const string MONOGON_MOTORWAY = "SLZ.BONELAB.Content.Level.LevelKartRace"; public const string PILLAR = "c2534c5a-c056-4883-ac79-e051426f6964"; public const string BIG_ANOMALY_B = "SLZ.BONELAB.Content.Level.LevelBigAnomalyB"; public const string ASCENT = "c2534c5a-db71-49cf-b694-24584c657665"; public const string OUTRO = "SLZ.BONELAB.Content.Level.LevelOutro"; public const string ROOFTOPS = "c2534c5a-c6ac-48b4-9c5f-b5cd5363656e"; public const string TUNNEL_TIPPER = "c2534c5a-c180-40e0-b2b7-325c5363656e"; public const string DISTRICT_TAC_TRIAL = "c2534c5a-4f3b-480e-ad2f-69175363656e"; public const string DROP_PIT = "c2534c5a-de61-4df9-8f6c-416954726547"; public const string TUSCANY = "c2534c5a-2c4c-4b44-b076-203b5363656e"; public const string MAIN_MENU = "c2534c5a-80e1-4a29-93ca-f3254d656e75"; public const string VOID_G114 = "fa534c5a868247138f50c62e424c4144.Level.VoidG114"; public const string FANTASY_ARENA = "fa534c5a868247138f50c62e424c4144.Level.LevelArenaMin"; } public class LabworksBarcodes { public const string BREAKROOM = "volx4.LabWorksBoneworksPort.Level.Boneworks01Breakroom"; public const string THRONE_ROOM = "volx4.LabWorksBoneworksPort.Level.Boneworks12ThroneRoom"; public const string MAIN_MENU = "volx4.LabWorksBoneworksPort.Level.BoneworksMainMenu"; } public const string TITLE_DESCENT = "01 - Descent"; public const string TITLE_MONOGON_MOTORWAY = "10 - Monogon Motorway"; public static Dictionary<string, byte> TitleToIndex = new Dictionary<string, byte> { ["01 - Descent"] = 1, ["02 - BONELAB Hub"] = 2, ["03 - LongRun"] = 3, ["04 - Mine Dive"] = 4, ["05 - Big Anomaly"] = 5, ["06 - Street Puncher"] = 6, ["07 - Sprint Bridge 04"] = 7, ["08 - Magma Gate"] = 8, ["09- MoonBase"] = 9, ["10 - Monogon Motorway"] = 10, ["11 - Pillar Climb"] = 11, ["12 - Big Anomaly B"] = 12, ["13 - Ascent"] = 13, ["14 - Home"] = 14, ["15 - Void G114"] = 15, ["Big Bone Bowling"] = 16, ["Container Yard"] = 17, ["Neon District Parkour"] = 18, ["Neon District Tac Trial"] = 19, ["Drop Pit"] = 20, ["Dungeon Warrior"] = 21, ["Fantasy Arena"] = 22, ["Gun Range"] = 23, ["Halfway Park"] = 24, ["HoloChamber"] = 25, ["Mirror"] = 26, ["Museum Basement"] = 27, ["Rooftops"] = 28, ["Tunnel Tipper"] = 29, ["Tuscany"] = 30, ["00 - Main Menu"] = 31, ["Baseline"] = 32, ["Load Default"] = 33, ["Load Mod"] = 34, ["Boneworks Main Menu"] = 100 }; public static string[] CAMPAIGN_LEVEL_BARCODES = new string[14] { "c2534c5a-4197-4879-8cd3-4a695363656e", "c2534c5a-6b79-40ec-8e98-e58c5363656e", "c2534c5a-56a6-40ab-a8ce-23074c657665", "c2534c5a-54df-470b-baaf-741f4c657665", "c2534c5a-7601-4443-bdfe-7f235363656e", "SLZ.BONELAB.Content.Level.LevelStreetPunch", "SLZ.BONELAB.Content.Level.SprintBridge04", "SLZ.BONELAB.Content.Level.SceneMagmaGate", "SLZ.BONELAB.Content.Level.MoonBase", "SLZ.BONELAB.Content.Level.LevelKartRace", "c2534c5a-c056-4883-ac79-e051426f6964", "SLZ.BONELAB.Content.Level.LevelBigAnomalyB", "c2534c5a-db71-49cf-b694-24584c657665", "SLZ.BONELAB.Content.Level.LevelOutro" }; public static HashSet<string> CAMPAIGN_LEVEL_BARCODES_SET = CAMPAIGN_LEVEL_BARCODES.ToHashSet(); public static byte GetIndex(string title) { if (TitleToIndex.TryGetValue(title, out var value)) { return value; } Match match = Regex.Match(title, "^Boneworks_(\\d+) "); if (match.Success) { return (byte)(100 + int.Parse(match.Groups[1].Value)); } return 0; } public static bool IsMenu(string barcode) { if (!(barcode == "c2534c5a-80e1-4a29-93ca-f3254d656e75")) { return barcode == "fa534c5a868247138f50c62e424c4144.Level.VoidG114"; } return true; } } } namespace Sst.SpeedrunTimer { internal static class AppVersion { public const string Value = "1.8.1"; } public static class BuildInfo { public const string Name = "SpeedrunTimer"; } public class InputServer { private const float DEGREES_TO_RADIANS = (float)Math.PI / 180f; private WebsocketServer websocketServer; public InputServer(int port = 6161, string ip = null) { websocketServer = new WebsocketServer { OnConnect = delegate(WebsocketServer.Client client) { client.Send("{\"inputVersion\":1}"); } }; websocketServer.Start(port, ip); MelonLogger.Msg("Input viewer server started at:\n" + string.Join("\n", ((ip == null) ? Network.GetAllAddresses() : new string[1] { ip }).Select((string address) => $"ws://{address}:{port}"))); } public void SendInputState() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) XRApi xr = MarrowGame.xr; if (((xr != null) ? xr.HMD : null) == null) { return; } byte item = 1; List<byte> list = new List<byte>(128) { item }; AddToData(list, Time.unscaledTime); AddToData(list, (XRDevice)(object)MarrowGame.xr.HMD); XRController[] array = (XRController[])(object)new XRController[2] { MarrowGame.xr.LeftController, MarrowGame.xr.RightController }; foreach (XRController val in array) { if (val != null) { AddToData(list, (XRDevice)(object)val); } else { AddToData(list, default(Vector3)); AddToData(list, default(Quaternion)); } bool[] obj = new bool[15] { val.IsConnected, val.TriggerButton, val.TriggerTouched, val.GripButton, false, val.TouchpadButton, val.TouchpadTouch, val.JoystickButton, val.JoystickTouch, val.AButton, val.ATouch, val.BButton, val.BTouch, val.MenuButton, false }; int num = 0; int num2 = 0; bool[] array2 = obj; for (int j = 0; j < array2.Length; j++) { if (array2[j]) { num |= 1 << num2; } num2++; } list.AddRange(BitConverter.GetBytes(num)); AddToData(list, val.Touchpad2DAxis); AddToData(list, val.Joystick2DAxis); AddToData(list, val.Trigger); AddToData(list, val.Grip); } websocketServer.Send(list.ToArray()); } private void AddToData(List<byte> data, int value) { data.AddRange(BitConverter.GetBytes(value)); } private void AddToData(List<byte> data, float value) { data.AddRange(BitConverter.GetBytes(value)); } private void AddToData(List<byte> data, Vector2 value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.x); AddToData(data, value.y); } private void AddToData(List<byte> data, Vector3 value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.x); AddToData(data, value.y); AddToData(data, value.z); } private void AddToData(List<byte> data, Quaternion value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) AddToData(data, value.x); AddToData(data, value.y); AddToData(data, value.z); AddToData(data, value.w); } private void AddToData(List<byte> data, XRDevice value) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.Position); AddToData(data, value.Rotation); } private void AddToData(List<byte> data, Transform value) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.localPosition); AddToData(data, value.localRotation); } } public class Mod : MelonMod { public const string PREF_CATEGORY_ID = "SpeedrunTimer"; private static SplitsTimer _timer = new SplitsTimer(); private InputServer _inputServer; public MelonPreferences_Category PrefCategory; public SplitsServer SplitsServer; public static Mod Instance; public Mod() { Instance = this; } public override void OnInitializeMelon() { Dbg.Init("SpeedrunTimer"); PrefCategory = MelonPreferences.CreateCategory("SpeedrunTimer"); SplitsTimer.OnInitialize(); SaveDeleteImprovements.OnInitialize(); LevelHooks.OnLoad += _timer.OnLoadingScreen; LevelHooks.OnLevelStart += _timer.OnLevelStart; SplitsServer = new SplitsServer(); _inputServer = new InputServer(); } public override void OnUpdate() { _timer.OnUpdate(); _inputServer?.SendInputState(); } } public static class SaveDeleteImprovements { [HarmonyPatch(typeof(DataManager), "_MSAFAIGE")] private class DataManager_MSAFAIGE_Patch { [HarmonyPrefix] internal static void Prefix() { Dbg.Log("DataManager_MSAFAIGE_Prefix"); if (!_prefDeleteModsOnWipe.Value) { _modsBackupPath = Path.Combine(Application.persistentDataPath, "Mods.backup"); if (Directory.Exists(_modsBackupPath)) { Directory.Delete(_modsBackupPath, recursive: true); } Directory.Move(GetModsPath(), _modsBackupPath); } } [HarmonyPostfix] internal static void Postfix() { Dbg.Log("DataManager_MSAFAIGE_Postfix"); if (_modsBackupPath != null) { if (Directory.Exists(_modsBackupPath)) { MelonLogger.Warning("Failed to restore mods folder on data wipe! Old mods are in Mods.backup now."); } _modsBackupPath = null; } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] private class DataManager_SavePath_Patch { [HarmonyPrefix] internal static void Prefix() { Dbg.Log("DataManager_SavePath_Patch"); RestoreModsBackup(); } } private static MelonPreferences_Entry<bool> _prefDeleteModsOnWipe; private static string _modsBackupPath; private static string GetModsPath() { return Path.Combine(Application.persistentDataPath, "Mods"); } public static void OnInitialize() { _prefDeleteModsOnWipe = Mod.Instance.PrefCategory.CreateEntry<bool>("deleteModsOnWipe", false, "Let mods be deleted when wiping all data", "Normally when resetting your save state through the main menu the game will delete all mods including the SpeedrunTimer. For convenience the timer will keep your mods folder. This option reverts to the original behavior of deleting mods.", false, false, (ValueValidator)null, (string)null); } private static void RestoreModsBackup() { if (_modsBackupPath != null && Directory.Exists(_modsBackupPath) && !Directory.Exists(GetModsPath())) { Directory.Move(_modsBackupPath, GetModsPath()); _modsBackupPath = null; } } } public class SplitsServer { private WebsocketServer _ws; public SplitsServer(int port = 6162, string ip = null) { SplitsServer splitsServer = this; _ws = new WebsocketServer { OnConnect = delegate { splitsServer.InitGameTime(); } }; _ws.Start(port, ip); MelonLogger.Msg("Splits server started at:\n" + string.Join("\n", ((ip == null) ? Network.GetAllAddresses() : new string[1] { ip }).Select((string address) => $"ws://{address}:{port}"))); } public void Start() { _ws.Send("start"); } public void Split() { _ws.Send("split"); } public void SplitOrStart() { _ws.Send("splitorstart"); } public void Reset() { _ws.Send("reset"); } public void TogglePause() { _ws.Send("togglepause"); } public void Undo() { _ws.Send("undo"); } public void Skip() { _ws.Send("skip"); } public void InitGameTime() { _ws.Send("initgametime"); } public void SetGameTime(TimeSpan time) { _ws.Send($"setgametime {time.TotalSeconds}"); } public void SetLoadingTimes(TimeSpan times) { _ws.Send($"setloadingtimes {times.TotalSeconds}"); } public void PauseGameTime() { _ws.Send("pausegametime"); } public void ResumeGameTime() { _ws.Send("resumegametime"); } } internal class SplitsTimer { [HarmonyPatch(typeof(TaxiController), "Start")] private class TaxiController_Start_Patch { [HarmonyPrefix] internal static void Prefix(TaxiController __instance) { Dbg.Log("TaxiController_Start_Patch"); __instance.OnPlayerSeated.AddListener(UnityAction.op_Implicit((Action)Instance.Finish)); } } private static Color FINISH_COLOR = new Color(0.2f, 0.8f, 0.2f); private static SplitsTimer Instance; private TextMeshPro _tmp; private TextMeshPro _tmpIl; private Splits _splits = new Splits(); private bool _isFinished; private static MelonPreferences_Entry<bool> _prefHide; private static MelonPreferences_Entry<bool> _prefHideIl; private static MelonPreferences_Entry<bool> _prefHideSplits; public SplitsTimer() { Instance = this; Livesplit.SetState(isLoading: true, isSittingInTaxi: false); } public static void OnInitialize() { _prefHide = Mod.Instance.PrefCategory.CreateEntry<bool>("hide", false, "Hide in-game timer", "Stops the timer from displaying on your wrist. Does not hide loading screen timer.", false, false, (ValueValidator)null, (string)null); _prefHideIl = Mod.Instance.PrefCategory.CreateEntry<bool>("hideIl", false, "Hide level timer", "Stops the individual level timer from displaying on your wrist.", false, false, (ValueValidator)null, (string)null); _prefHideSplits = Mod.Instance.PrefCategory.CreateEntry<bool>("hideSplits", false, "Hides split display in loading screen", "Stops the individual level times from displaying in the loading screen.", false, false, (ValueValidator)null, (string)null); } public void Reset() { _isFinished = false; _splits.Reset(); Mod.Instance.SplitsServer?.Reset(); Livesplit.SetState(isLoading: false, isSittingInTaxi: false); } public void OnLoadingScreen(LevelCrate nextLevel) { if ((Object)(object)nextLevel == (Object)null) { return; } if (_isFinished) { _splits.Reset(); _isFinished = false; } if (Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.BoneworksMainMenu" && Barcode.op_Implicit(((Scannable)LevelHooks.PrevLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.Boneworks12ThroneRoom") { _splits.Split(null); Finish(); } else { Livesplit.SetState(isLoading: true, isSittingInTaxi: false, ((Scannable)nextLevel).Title); if (Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "c2534c5a-4197-4879-8cd3-4a695363656e" || Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.Boneworks01Breakroom") { Dbg.Log("Attempting to start timer"); Mod.Instance.SplitsServer?.Start(); _splits.ResetAndPause(nextLevel); } else if (_splits.TimeStart.HasValue) { Dbg.Log("Splitting timer"); Mod.Instance.SplitsServer?.Split(); _splits.Pause(); _splits.Split(nextLevel); } } TimeSpan? time = _splits.GetTime(); if (time.HasValue) { Mod.Instance.SplitsServer?.PauseGameTime(); Mod.Instance.SplitsServer?.SetGameTime(time.Value); SplitsRenderer.RenderLoadingWatermark(time.Value); if (!_prefHideSplits.Value) { SplitsRenderer.RenderSplits(_splits); } } } public void OnLevelStart(LevelCrate level) { Livesplit.SetState(isLoading: false, isSittingInTaxi: false, ((Scannable)level).Title); Mod.Instance.SplitsServer?.ResumeGameTime(); if (!_isFinished) { _splits.ResumeIfStarted(); } AddTimerToWrist(); } private void AddTimerToWrist() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) if (!_prefHide.Value) { GameObject val = new GameObject("SpeedrunTimer_Wrist_Text"); _tmp = val.AddComponent<TextMeshPro>(); ((TMP_Text)_tmp).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_tmp).fontSize = 0.5f; ((TMP_Text)_tmp).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); if (_isFinished) { ((TMP_Text)_tmp).color = FINISH_COLOR; } Bonelab.DockToWrist(val); if (!_prefHideIl.Value) { GameObject val2 = new GameObject("SpeedrunTimer_Wrist_IL"); GameObject val3 = new GameObject("SpeedrunTimer_Wrist_IL_Offset"); val3.transform.parent = val2.transform; _tmpIl = val3.AddComponent<TextMeshPro>(); ((TMP_Text)_tmpIl).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_tmpIl).fontSize = 0.3f; ((TMP_Text)_tmpIl).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); ((Transform)((TMP_Text)_tmpIl).rectTransform).localPosition = new Vector3(-0.005f, -0.03f, 0.005f); ((TMP_Text)_tmpIl).color = new Color(0.2f, 0.2f, 0.2f); Bonelab.DockToWrist(val2); } } } public void OnUpdate() { if ((Object)(object)_tmp != (Object)null) { TimeSpan? time = _splits.GetTime(); if (!time.HasValue) { return; } ((Component)_tmp).gameObject.active = true; ((TMP_Text)_tmp).SetText(SplitsRenderer.DurationToString(time.Value), true); } if ((Object)(object)_tmpIl != (Object)null) { TimeSpan? currentSplitTime = _splits.GetCurrentSplitTime(); if (currentSplitTime.HasValue) { ((Component)_tmpIl).gameObject.active = true; ((TMP_Text)_tmpIl).SetText(SplitsRenderer.DurationToString(currentSplitTime.Value), true); } } } public void Finish() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) LevelCrate currentLevel = LevelHooks.CurrentLevel; Livesplit.SetState(isLoading: false, isSittingInTaxi: true, ((currentLevel != null) ? ((Scannable)currentLevel).Title : null) ?? ""); _splits.Pause(); Mod.Instance.SplitsServer?.Split(); Mod.Instance.SplitsServer?.SetGameTime(_splits.GetTime().Value); _isFinished = true; MelonLogger.Msg($"Stopping timer at: {_splits.GetTime()}"); if ((Object)(object)_tmp != (Object)null) { ((TMP_Text)_tmp).color = FINISH_COLOR; } } } internal class TimeTrialDisplayFix { [HarmonyPatch(typeof(BoneLeaderManager), "SubmitLeaderboardScore")] private class BoneLeaderManager_SubmitLeaderboardScore_Patch { [HarmonyPostfix] internal static void Postfix(BoneLeaderManager __instance, uint score) { DisplayTime(__instance, score); } } public const string LABEL_NAME = "TimeTrialDisplayFix_Label"; public static void DisplayTime(BoneLeaderManager leaderManager, uint milliseconds) { TMP_Text text_TitleBoard = leaderManager.text_TitleBoard; Transform parent = text_TitleBoard.transform.parent; Transform obj = parent.Find("TimeTrialDisplayFix_Label"); TextMeshPro obj2 = ((obj != null) ? ((Component)obj).GetComponent<TextMeshPro>() : null) ?? CreateTimeDisplay(parent, text_TitleBoard); string text = SplitsRenderer.DurationToString(TimeSpan.FromMilliseconds(milliseconds)); ((TMP_Text)obj2).SetText("Time: " + text, true); } private static TextMeshPro CreateTimeDisplay(Transform canvas, TMP_Text title) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_002c: 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) GameObject val = new GameObject("TimeTrialDisplayFix_Label"); val.transform.SetParent(canvas, false); val.transform.localPosition = new Vector3(0f, 1.2f, 3f); TextMeshPro obj = val.AddComponent<TextMeshPro>(); ((TMP_Text)obj).alignment = (TextAlignmentOptions)514; ((TMP_Text)obj).font = title.font; ((TMP_Text)obj).fontSize = 4f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(10f, 1f); return obj; } } internal class Livesplit { private static byte[] State = new byte[12] { 0, 226, 3, 52, 194, 223, 99, 36, 0, 0, 0, 0 }; public static void SetState(bool isLoading, bool isSittingInTaxi, string levelTitle = "") { State[0] = 212; State[8] = (byte)((isLoading ? 1u : 0u) | ((isSittingInTaxi ? 1u : 0u) << 1)); State[9] = Levels.GetIndex(levelTitle); } } public static class Network { public static string[] GetAllAddresses() { return new string[1] { "127.0.0.1" }.Concat(from address in Dns.GetHostEntry(Dns.GetHostName()).AddressList where address.AddressFamily == AddressFamily.InterNetwork select address.ToString()).ToArray(); } } public class Splits { private static Regex _levelPrefixPattern = new Regex("^\\s*\\d+\\s*-\\s*"); public List<Split> Items = new List<Split>(); public DateTime? TimeStart; public DateTime? TimeEnd; public DateTime? TimeStartRelative; public DateTime? TimePause; public DateTime TimeLastSplitStartRelative = DateTime.Now; private static string LevelSplitName(LevelCrate level) { return _levelPrefixPattern.Replace(((Scannable)level).Title, ""); } public void Reset() { TimeStart = null; TimeEnd = null; TimeStartRelative = null; TimePause = null; } public void ResetAndPause(LevelCrate firstLevel) { ResetAndStart(firstLevel); TimePause = TimeStart; } public void ResetAndStart(LevelCrate firstLevel) { DateTime now = DateTime.Now; TimeEnd = (TimePause = null); TimeStart = (TimeStartRelative = (TimeLastSplitStartRelative = now)); Items = new List<Split> { new Split { Level = firstLevel, Name = LevelSplitName(firstLevel), TimeStart = now } }; } public void Pause() { TimePause = DateTime.Now; } public void ResumeIfStarted() { if (TimePause.HasValue) { TimeSpan timeSpan = DateTime.Now - TimePause.Value; TimeStartRelative += timeSpan; TimeLastSplitStartRelative += timeSpan; TimePause = null; } } public TimeSpan? GetTime() { DateTime value = TimeEnd ?? TimePause ?? DateTime.Now; DateTime? timeStartRelative = TimeStartRelative; return value - timeStartRelative; } public TimeSpan? GetCurrentSplitTime() { return (TimeEnd ?? TimePause ?? DateTime.Now) - TimeLastSplitStartRelative; } public void Split(LevelCrate nextLevel) { Split split = Items[Items.Count - 1]; DateTime now = DateTime.Now; split.TimeEnd = now; DateTime valueOrDefault = TimePause.GetValueOrDefault(now); split.Duration = valueOrDefault - TimeLastSplitStartRelative; TimeLastSplitStartRelative = valueOrDefault; if (Object.op_Implicit((Object)(object)nextLevel)) { Items.Add(new Split { Level = nextLevel, Name = LevelSplitName(nextLevel), TimeStart = now }); } } } public class Split { public LevelCrate Level; public string Name; public DateTime? TimeStart; public DateTime? TimeEnd; public TimeSpan? Duration; } internal class SplitsRenderer { private static float SPLITS_WIDTH = 0.3f; private static float SPLITS_LINE_HEIGHT = 0.05f; private static float SPLITS_LEFT = -0.1f; private static float SPLITS_FONT_SIZE = 0.3f; public static void RenderLoadingWatermark(TimeSpan time) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) Dbg.Log("RenderLoadingWatermark"); BasicTrackingRig basicTrackingRig = LevelHooks.BasicTrackingRig; Transform val = ((basicTrackingRig != null) ? basicTrackingRig.head : null); if (!Object.op_Implicit((Object)(object)val)) { MelonLogger.Warning("Failed to render watermark in loading screen because could not find head position"); return; } TextMeshPro obj = CreateText(val, "Watermark"); ((TMP_Text)obj).alignment = (TextAlignmentOptions)260; ((TMP_Text)obj).fontSize = 0.5f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(0.8f, 0.8f); ((Transform)((TMP_Text)obj).rectTransform).localPosition = new Vector3(0f, 0f, 1f); ((TMP_Text)obj).color = new Color(0.4f, 0.4f, 0.6f); string text = ""; string text2 = DurationToString(time); string text3 = string.Join("\n", MelonTypeBase<MelonMod>.RegisteredMelons.Select((MelonMod mod) => ((MelonBase)mod).Info.Name)); ((TMP_Text)obj).SetText("SpeedrunTimer v1.8.1" + text + "\n" + text2 + "\n\nMods:\n" + text3, true); } public static void RenderSplits(Splits splits) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) BasicTrackingRig basicTrackingRig = LevelHooks.BasicTrackingRig; Transform val = ((basicTrackingRig != null) ? basicTrackingRig.head : null); if (!Object.op_Implicit((Object)(object)val)) { return; } GameObject val2 = new GameObject("SpeedrunTimer_Splits"); val2.transform.SetParent(((Component)val).transform); val2.transform.localPosition = new Vector3(-0.4f, 0.25f, 1f); Vector2 val4 = default(Vector2); for (int i = 0; i < splits.Items.Count; i++) { Split split = splits.Items[i]; float num = (float)i * (0f - SPLITS_LINE_HEIGHT); float num2 = 0f; if (split.Duration.HasValue) { TextMeshPro val3 = CreateText(val2.transform, "Splits_Time_" + split.Name); string text = ((split.Duration.Value >= TimeSpan.FromHours(1.0)) ? "h\\:mm\\:ss" : "m\\:ss\\.f"); ((TMP_Text)val3).SetText(split.Duration.Value.ToString(text), true); ((TMP_Text)val3).alignment = (TextAlignmentOptions)516; ((TMP_Text)val3).overflowMode = (TextOverflowModes)3; ((Transform)((TMP_Text)val3).rectTransform).localPosition = new Vector3(0f, 0f, 0f); RectTransform rectTransform = ((TMP_Text)val3).rectTransform; RectTransform rectTransform2 = ((TMP_Text)val3).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform2.anchorMax = val4; rectTransform.anchorMin = val4; ((TMP_Text)val3).rectTransform.offsetMin = new Vector2(SPLITS_LEFT, num); ((TMP_Text)val3).rectTransform.offsetMax = new Vector2(SPLITS_WIDTH + SPLITS_LEFT, num + SPLITS_LINE_HEIGHT); ((TMP_Text)val3).fontSize = SPLITS_FONT_SIZE; val3.ForceMeshUpdate(false, false); num2 = ((TMP_Text)val3).preferredWidth; } TextMeshPro val5 = CreateText(val2.transform, "Splits_Name_" + split.Name); string text2 = Regex.Replace(split.Name, "^boneworks(?:_\\d+)?\\s+", "", RegexOptions.IgnoreCase); ((TMP_Text)val5).SetText(text2, true); ((TMP_Text)val5).alignment = (TextAlignmentOptions)513; ((TMP_Text)val5).overflowMode = (TextOverflowModes)3; ((Transform)((TMP_Text)val5).rectTransform).localPosition = new Vector3(0f, 0f, 0f); RectTransform rectTransform3 = ((TMP_Text)val5).rectTransform; RectTransform rectTransform4 = ((TMP_Text)val5).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform4.anchorMax = val4; rectTransform3.anchorMin = val4; ((TMP_Text)val5).rectTransform.offsetMin = new Vector2(SPLITS_LEFT, num); ((TMP_Text)val5).rectTransform.offsetMax = new Vector2(SPLITS_WIDTH + SPLITS_LEFT - num2, num + SPLITS_LINE_HEIGHT); ((TMP_Text)val5).fontSize = SPLITS_FONT_SIZE; } } public static string DurationToString(TimeSpan duration) { return duration.ToString(((duration.Hours >= 1) ? "h\\:m" : "") + "m\\:ss\\.ff"); } private static TextMeshPro CreateText(Transform parent, string name) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) TextMeshPro obj = new GameObject("SpeedrunTimer_" + name) { layer = LayerMask.NameToLayer("Background") }.AddComponent<TextMeshPro>(); obj.transform.SetParent(parent); obj.sortingOrder = 100; ((TMP_Text)obj).color = new Color(0.5f, 0.5f, 0.5f); return obj; } } public class WebsocketServer { public class Client { public TcpClient TcpClient; public List<byte> MessageBuffer = new List<byte>(); public BlockingCollection<byte[]> SendQueue = new BlockingCollection<byte[]>(); public void Send(string data) { Send(Encoding.UTF8.GetBytes(data), isString: true); } public void Send(byte[] data, bool isString = false) { SendQueue.Add(CreateMessageFrame(data, isString)); } } public TcpListener listener; private List<Client> connectedClients = new List<Client>(); public Action<Client> OnConnect; public Action<string, Client> OnMessage; public void Start(int port = 6161, string ip = null) { listener = new TcpListener((ip != null) ? IPAddress.Parse(ip) : IPAddress.Any, port); listener.Start(); Task.Run((Action)ListenForConnections); } public void Send(string data) { Send(Encoding.UTF8.GetBytes(data), isString: true); } public void Send(byte[] data, bool isString = false) { byte[] item = CreateMessageFrame(data, isString); Client[] array = connectedClients.ToArray(); foreach (Client client in array) { if (client.TcpClient.Connected) { client.SendQueue.Add(item); } else { connectedClients.Remove(client); } } } private void ListenForConnections() { while (true) { try { TcpClient tcpClient = listener.AcceptTcpClient(); MelonLogger.Msg("Websocket client connected"); Client client = new Client { TcpClient = tcpClient }; connectedClients.Add(client); Task.Run(delegate { HandleClient(client); }); Thread thread = new Thread((ThreadStart)delegate { ListenToSendQueue(client); }); thread.IsBackground = true; thread.Start(); } catch (Exception ex) { MelonLogger.Error("Error listening for Websocket connections:"); MelonLogger.Error((object)ex); Thread.Sleep(5000); } } } private void HandleClient(Client client) { byte[] array = new byte[1024]; try { NetworkStream stream = client.TcpClient.GetStream(); while (client.TcpClient.Connected) { int num = stream.Read(array, 0, array.Length); if (num == 0) { break; } byte[] array2 = new byte[num]; Array.Copy(array, array2, num); if (IsClientHandshake(array2)) { RespondToClientHandshake(array2, client); continue; } byte[] array3 = ReadMessage(array2, client); if (array3 != null) { string @string = Encoding.UTF8.GetString(array3); if (OnMessage != null) { OnMessage(@string, client); } client.MessageBuffer.Clear(); } } } catch (Exception ex) { MelonLogger.Warning("Error with websocket: {0}", new object[1] { ex }); } finally { CloseConnection(client); } } private void CloseConnection(Client client) { if (connectedClients.Contains(client)) { MelonLogger.Msg("Websocket disconnected"); client.SendQueue.CompleteAdding(); connectedClients.Remove(client); } else { client.TcpClient.Dispose(); } } private void ListenToSendQueue(Client client) { try { NetworkStream stream = client.TcpClient.GetStream(); foreach (byte[] item in client.SendQueue.GetConsumingEnumerable()) { if (client.TcpClient.Connected) { stream.Write(item, 0, item.Length); continue; } break; } } catch (Exception ex) { MelonLogger.Warning("Websocket error:"); MelonLogger.Warning((object)ex); } finally { CloseConnection(client); } } private bool IsClientHandshake(byte[] request) { return Regex.IsMatch(Encoding.UTF8.GetString(request, 0, 4), "^GET\\s", RegexOptions.IgnoreCase); } private void RespondToClientHandshake(byte[] request, Client client) { NetworkStream stream = client.TcpClient.GetStream(); string @string = Encoding.UTF8.GetString(request); byte[] bytes = Encoding.UTF8.GetBytes(string.Join("\r\n", "HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", "Upgrade: websocket", "Sec-WebSocket-Accept: " + GenerateWebsocketAcceptToken(@string), "", "")); stream.Write(bytes, 0, bytes.Length); OnConnect(client); } private string GenerateWebsocketAcceptToken(string request) { string s = Regex.Match(request, "\\n\\s*Sec-WebSocket-Key\\s*:(.*)", RegexOptions.IgnoreCase).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; return Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(s))); } private byte[] ReadMessage(byte[] request, Client client) { bool flag = (request[0] & 0x80) != 0; if ((request[1] & 0x80) == 0) { return null; } int num = 2; ulong num2 = (ulong)(int)(request[1] & 0x7Fu); switch (num2) { case 126uL: num2 = BitConverter.ToUInt16(new byte[2] { request[3], request[2] }, 0); num = 4; break; case 127uL: num2 = BitConverter.ToUInt64(new byte[8] { request[9], request[8], request[7], request[6], request[5], request[4], request[3], request[2] }, 0); num = 10; break; } byte[] array = new byte[num2]; int num3 = num; num += 4; for (ulong num4 = 0uL; num4 < num2; num4++) { array[num4] = (byte)(request[num + (int)num4] ^ request[num3 + ((int)num4 & 3)]); } client.MessageBuffer.AddRange(array); if (!flag) { return null; } return client.MessageBuffer.ToArray(); } private static byte[] CreateMessageFrame(byte[] data, bool isString) { byte[] array; if (data.Length < 126) { array = new byte[data.Length + 2]; array[1] = (byte)data.Length; } else if (data.Length <= 65535) { array = new byte[data.Length + 4]; array[1] = 126; byte[] bytes = BitConverter.GetBytes((ushort)data.Length); array[2] = bytes[1]; array[3] = bytes[0]; } else { array = new byte[data.Length + 10]; array[1] = 127; byte[] bytes2 = BitConverter.GetBytes((ulong)data.Length); for (int i = 0; i < 8; i++) { array[9 - i] = bytes2[i]; } } array[0] = (byte)(isString ? 129u : 130u); Array.Copy(data, 0, array, array.Length - data.Length, data.Length); return array; } } }
Patch4_MelonLoader0.5/Mods/SpeedrunTimer.P4.ML5.dll
Decompiled 4 months agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using SLZ.Bonelab; using SLZ.Bonelab.SaveData; using SLZ.Interaction; using SLZ.Marrow.Input; using SLZ.Marrow.SceneStreaming; using SLZ.Marrow.Utilities; using SLZ.Marrow.Warehouse; using SLZ.Rig; using Sst.SpeedrunTimer; using Sst.Utilities; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("SpeedrunTimer")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany(null)] [assembly: AssemblyProduct("SpeedrunTimer")] [assembly: AssemblyCopyright("Created by jakzo")] [assembly: AssemblyTrademark(null)] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("1.8.1")] [assembly: NeutralResourcesLanguage("en")] [assembly: MelonInfo(typeof(Mod), "SpeedrunTimer", "1.8.1", "jakzo", "https://bonelab.thunderstore.io/package/jakzo/SpeedrunTimer/")] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.8.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Sst { public class Metadata { public const string AUTHOR = "jakzo"; public const string COMPANY = null; public const string DEVELOPER = "Stress Level Zero"; public const string GAME = "BONELAB"; public const string GAME_BONEWORKS = "BONEWORKS"; } public class Dbg { private static MelonPreferences_Entry<bool> _prefPrintDebugLogs; public static void Init(string prefCategoryId) { _prefPrintDebugLogs = MelonPreferences.CreateCategory(prefCategoryId).CreateEntry<bool>("printDebugLogs", false, "Print debug logs", "Print debug logs to console", false, true, (ValueValidator)null, (string)null); } public static void Log(string msg, params object[] data) { if (_prefPrintDebugLogs.Value) { MelonLogger.Msg("dbg: " + msg); } } } } namespace Sst.Utilities { internal static class LevelHooks { [HarmonyPatch(typeof(BasicTrackingRig), "Awake")] private class BasicTrackingRig_Awake_Patch { [CompilerGenerated] private static class <>O { public static LemonAction <0>__WaitForLoadFinished; } [HarmonyPrefix] internal static void Prefix(BasicTrackingRig __instance) { //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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown Dbg.Log("BasicTrackingRig_Awake_Patch"); _loadingScene = ((Component)__instance).gameObject.scene; if (Object.op_Implicit((Object)(object)CurrentLevel)) { PrevLevel = CurrentLevel; } CurrentLevel = null; NextLevel = SceneStreamer.Session.Level; RigManager = null; BasicTrackingRig = __instance; MelonEvent onUpdate = MelonEvents.OnUpdate; object obj = <>O.<0>__WaitForLoadFinished; if (obj == null) { LemonAction val = WaitForLoadFinished; <>O.<0>__WaitForLoadFinished = val; obj = (object)val; } ((MelonEventBase<LemonAction>)(object)onUpdate).Subscribe((LemonAction)obj, 0, false); LevelCrate nextLevel = NextLevel; Dbg.Log("OnLoad " + ((nextLevel != null) ? ((Scannable)nextLevel).Title : null)); SafeInvoke("OnLoad", LevelHooks.OnLoad, NextLevel); } } [HarmonyPatch(typeof(RigManager), "Awake")] private class RigManager_Awake_Patch { [HarmonyPrefix] internal static void Prefix(RigManager __instance) { Dbg.Log("RigManager_Awake_Patch"); RigManager = __instance; BasicTrackingRig = null; } } [CompilerGenerated] private static class <>O { public static LemonAction <0>__WaitForLoadFinished; } public static LevelCrate PrevLevel; public static LevelCrate CurrentLevel; public static LevelCrate NextLevel; public static RigManager RigManager; public static BasicTrackingRig BasicTrackingRig; private static Scene _loadingScene; public static bool IsLoading => !Object.op_Implicit((Object)(object)CurrentLevel); public static event Action<LevelCrate> OnLoad; public static event Action<LevelCrate> OnLevelStart; private static void SafeInvoke(string name, Action<LevelCrate> action, LevelCrate level) { try { action?.Invoke(level); } catch (Exception ex) { MelonLogger.Error("Failed to execute " + name + " event: " + ex.ToString()); } } private static void WaitForLoadFinished() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown if (!((Scene)(ref _loadingScene)).isLoaded) { MelonEvent onUpdate = MelonEvents.OnUpdate; object obj = <>O.<0>__WaitForLoadFinished; if (obj == null) { LemonAction val = WaitForLoadFinished; <>O.<0>__WaitForLoadFinished = val; obj = (object)val; } ((MelonEventBase<LemonAction>)(object)onUpdate).Unsubscribe((LemonAction)obj); CurrentLevel = NextLevel ?? SceneStreamer.Session.Level ?? CurrentLevel; NextLevel = null; LevelCrate currentLevel = CurrentLevel; Dbg.Log("OnLevelStart " + ((currentLevel != null) ? ((Scannable)currentLevel).Title : null)); SafeInvoke("OnLevelStart", LevelHooks.OnLevelStart, CurrentLevel); } } } public class Bonelab { public static void DockToWrist(GameObject gameObject, bool rightHand = false) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) PhysicsRig physicsRig = LevelHooks.RigManager.physicsRig; Hand val = (rightHand ? physicsRig.rightHand : physicsRig.leftHand); gameObject.transform.SetParent(((Component)val).transform); gameObject.transform.localPosition = new Vector3(-0.31f, 0.3f, 0f); gameObject.transform.localRotation = Quaternion.Euler(32f, 4f, 3f); } public static TextMeshPro CreateTextOnWrist(string name, bool rightHand = false) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); TextMeshPro obj = val.AddComponent<TextMeshPro>(); ((TMP_Text)obj).alignment = (TextAlignmentOptions)1028; ((TMP_Text)obj).fontSize = 0.5f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); DockToWrist(val, rightHand); return obj; } } public class Levels { public class Barcodes { public const string DESCENT = "c2534c5a-4197-4879-8cd3-4a695363656e"; public const string HUB = "c2534c5a-6b79-40ec-8e98-e58c5363656e"; public const string LONG_RUN = "c2534c5a-56a6-40ab-a8ce-23074c657665"; public const string MINE_DIVE = "c2534c5a-54df-470b-baaf-741f4c657665"; public const string BIG_ANOMALY_A = "c2534c5a-7601-4443-bdfe-7f235363656e"; public const string STREET_PUNCHER = "SLZ.BONELAB.Content.Level.LevelStreetPunch"; public const string SPRINT_BRIDGE = "SLZ.BONELAB.Content.Level.SprintBridge04"; public const string MAGMA_GATE = "SLZ.BONELAB.Content.Level.SceneMagmaGate"; public const string MOON_BASE = "SLZ.BONELAB.Content.Level.MoonBase"; public const string MONOGON_MOTORWAY = "SLZ.BONELAB.Content.Level.LevelKartRace"; public const string PILLAR = "c2534c5a-c056-4883-ac79-e051426f6964"; public const string BIG_ANOMALY_B = "SLZ.BONELAB.Content.Level.LevelBigAnomalyB"; public const string ASCENT = "c2534c5a-db71-49cf-b694-24584c657665"; public const string OUTRO = "SLZ.BONELAB.Content.Level.LevelOutro"; public const string ROOFTOPS = "c2534c5a-c6ac-48b4-9c5f-b5cd5363656e"; public const string TUNNEL_TIPPER = "c2534c5a-c180-40e0-b2b7-325c5363656e"; public const string DISTRICT_TAC_TRIAL = "c2534c5a-4f3b-480e-ad2f-69175363656e"; public const string DROP_PIT = "c2534c5a-de61-4df9-8f6c-416954726547"; public const string TUSCANY = "c2534c5a-2c4c-4b44-b076-203b5363656e"; public const string MAIN_MENU = "c2534c5a-80e1-4a29-93ca-f3254d656e75"; public const string VOID_G114 = "fa534c5a868247138f50c62e424c4144.Level.VoidG114"; public const string FANTASY_ARENA = "fa534c5a868247138f50c62e424c4144.Level.LevelArenaMin"; } public class LabworksBarcodes { public const string BREAKROOM = "volx4.LabWorksBoneworksPort.Level.Boneworks01Breakroom"; public const string THRONE_ROOM = "volx4.LabWorksBoneworksPort.Level.Boneworks12ThroneRoom"; public const string MAIN_MENU = "volx4.LabWorksBoneworksPort.Level.BoneworksMainMenu"; } public const string TITLE_DESCENT = "01 - Descent"; public const string TITLE_MONOGON_MOTORWAY = "10 - Monogon Motorway"; public static Dictionary<string, byte> TitleToIndex = new Dictionary<string, byte> { ["01 - Descent"] = 1, ["02 - BONELAB Hub"] = 2, ["03 - LongRun"] = 3, ["04 - Mine Dive"] = 4, ["05 - Big Anomaly"] = 5, ["06 - Street Puncher"] = 6, ["07 - Sprint Bridge 04"] = 7, ["08 - Magma Gate"] = 8, ["09- MoonBase"] = 9, ["10 - Monogon Motorway"] = 10, ["11 - Pillar Climb"] = 11, ["12 - Big Anomaly B"] = 12, ["13 - Ascent"] = 13, ["14 - Home"] = 14, ["15 - Void G114"] = 15, ["Big Bone Bowling"] = 16, ["Container Yard"] = 17, ["Neon District Parkour"] = 18, ["Neon District Tac Trial"] = 19, ["Drop Pit"] = 20, ["Dungeon Warrior"] = 21, ["Fantasy Arena"] = 22, ["Gun Range"] = 23, ["Halfway Park"] = 24, ["HoloChamber"] = 25, ["Mirror"] = 26, ["Museum Basement"] = 27, ["Rooftops"] = 28, ["Tunnel Tipper"] = 29, ["Tuscany"] = 30, ["00 - Main Menu"] = 31, ["Baseline"] = 32, ["Load Default"] = 33, ["Load Mod"] = 34, ["Boneworks Main Menu"] = 100 }; public static string[] CAMPAIGN_LEVEL_BARCODES = new string[14] { "c2534c5a-4197-4879-8cd3-4a695363656e", "c2534c5a-6b79-40ec-8e98-e58c5363656e", "c2534c5a-56a6-40ab-a8ce-23074c657665", "c2534c5a-54df-470b-baaf-741f4c657665", "c2534c5a-7601-4443-bdfe-7f235363656e", "SLZ.BONELAB.Content.Level.LevelStreetPunch", "SLZ.BONELAB.Content.Level.SprintBridge04", "SLZ.BONELAB.Content.Level.SceneMagmaGate", "SLZ.BONELAB.Content.Level.MoonBase", "SLZ.BONELAB.Content.Level.LevelKartRace", "c2534c5a-c056-4883-ac79-e051426f6964", "SLZ.BONELAB.Content.Level.LevelBigAnomalyB", "c2534c5a-db71-49cf-b694-24584c657665", "SLZ.BONELAB.Content.Level.LevelOutro" }; public static HashSet<string> CAMPAIGN_LEVEL_BARCODES_SET = CAMPAIGN_LEVEL_BARCODES.ToHashSet(); public static byte GetIndex(string title) { if (TitleToIndex.TryGetValue(title, out var value)) { return value; } Match match = Regex.Match(title, "^Boneworks_(\\d+) "); if (match.Success) { return (byte)(100 + int.Parse(match.Groups[1].Value)); } return 0; } public static bool IsMenu(string barcode) { if (!(barcode == "c2534c5a-80e1-4a29-93ca-f3254d656e75")) { return barcode == "fa534c5a868247138f50c62e424c4144.Level.VoidG114"; } return true; } } } namespace Sst.SpeedrunTimer { internal static class AppVersion { public const string Value = "1.8.1"; } public static class BuildInfo { public const string Name = "SpeedrunTimer"; } public class InputServer { private const float DEGREES_TO_RADIANS = (float)Math.PI / 180f; private WebsocketServer websocketServer; public InputServer(int port = 6161, string ip = null) { websocketServer = new WebsocketServer { OnConnect = delegate(WebsocketServer.Client client) { client.Send("{\"inputVersion\":1}"); } }; websocketServer.Start(port, ip); MelonLogger.Msg("Input viewer server started at:\n" + string.Join("\n", ((ip == null) ? Network.GetAllAddresses() : new string[1] { ip }).Select((string address) => $"ws://{address}:{port}"))); } public void SendInputState() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) XRApi xr = MarrowGame.xr; if (((xr != null) ? xr.HMD : null) == null) { return; } byte item = 1; List<byte> list = new List<byte>(128) { item }; AddToData(list, Time.unscaledTime); AddToData(list, (XRDevice)(object)MarrowGame.xr.HMD); XRController[] array = (XRController[])(object)new XRController[2] { MarrowGame.xr.LeftController, MarrowGame.xr.RightController }; foreach (XRController val in array) { if (val != null) { AddToData(list, (XRDevice)(object)val); } else { AddToData(list, default(Vector3)); AddToData(list, default(Quaternion)); } bool[] obj = new bool[15] { val.IsConnected, val.TriggerButton, val.TriggerTouched, val.GripButton, false, val.TouchpadButton, val.TouchpadTouch, val.JoystickButton, val.JoystickTouch, val.AButton, val.ATouch, val.BButton, val.BTouch, val.MenuButton, false }; int num = 0; int num2 = 0; bool[] array2 = obj; for (int j = 0; j < array2.Length; j++) { if (array2[j]) { num |= 1 << num2; } num2++; } list.AddRange(BitConverter.GetBytes(num)); AddToData(list, val.Touchpad2DAxis); AddToData(list, val.Joystick2DAxis); AddToData(list, val.Trigger); AddToData(list, val.Grip); } websocketServer.Send(list.ToArray()); } private void AddToData(List<byte> data, int value) { data.AddRange(BitConverter.GetBytes(value)); } private void AddToData(List<byte> data, float value) { data.AddRange(BitConverter.GetBytes(value)); } private void AddToData(List<byte> data, Vector2 value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.x); AddToData(data, value.y); } private void AddToData(List<byte> data, Vector3 value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.x); AddToData(data, value.y); AddToData(data, value.z); } private void AddToData(List<byte> data, Quaternion value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) AddToData(data, value.x); AddToData(data, value.y); AddToData(data, value.z); AddToData(data, value.w); } private void AddToData(List<byte> data, XRDevice value) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.Position); AddToData(data, value.Rotation); } private void AddToData(List<byte> data, Transform value) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.localPosition); AddToData(data, value.localRotation); } } public class Mod : MelonMod { public const string PREF_CATEGORY_ID = "SpeedrunTimer"; private static SplitsTimer _timer = new SplitsTimer(); private InputServer _inputServer; public MelonPreferences_Category PrefCategory; public SplitsServer SplitsServer; public static Mod Instance; public Mod() { Instance = this; } public override void OnInitializeMelon() { Dbg.Init("SpeedrunTimer"); PrefCategory = MelonPreferences.CreateCategory("SpeedrunTimer"); SplitsTimer.OnInitialize(); SaveDeleteImprovements.OnInitialize(); LevelHooks.OnLoad += _timer.OnLoadingScreen; LevelHooks.OnLevelStart += _timer.OnLevelStart; SplitsServer = new SplitsServer(); _inputServer = new InputServer(); } public override void OnUpdate() { _timer.OnUpdate(); _inputServer?.SendInputState(); } } public static class SaveDeleteImprovements { [HarmonyPatch(typeof(DataManager), "_MSAFAIGE")] private class DataManager_MSAFAIGE_Patch { [HarmonyPrefix] internal static void Prefix() { Dbg.Log("DataManager_MSAFAIGE_Prefix"); if (!_prefDeleteModsOnWipe.Value) { _modsBackupPath = Path.Combine(Application.persistentDataPath, "Mods.backup"); if (Directory.Exists(_modsBackupPath)) { Directory.Delete(_modsBackupPath, recursive: true); } Directory.Move(GetModsPath(), _modsBackupPath); } } [HarmonyPostfix] internal static void Postfix() { Dbg.Log("DataManager_MSAFAIGE_Postfix"); if (_modsBackupPath != null) { if (Directory.Exists(_modsBackupPath)) { MelonLogger.Warning("Failed to restore mods folder on data wipe! Old mods are in Mods.backup now."); } _modsBackupPath = null; } } } private static MelonPreferences_Entry<bool> _prefDeleteModsOnWipe; private static string _modsBackupPath; private static string GetModsPath() { return Path.Combine(Application.persistentDataPath, "Mods"); } public static void OnInitialize() { _prefDeleteModsOnWipe = Mod.Instance.PrefCategory.CreateEntry<bool>("deleteModsOnWipe", false, "Let mods be deleted when wiping all data", "Normally when resetting your save state through the main menu the game will delete all mods including the SpeedrunTimer. For convenience the timer will keep your mods folder. This option reverts to the original behavior of deleting mods.", false, false, (ValueValidator)null, (string)null); } } public class SplitsServer { private WebsocketServer _ws; public SplitsServer(int port = 6162, string ip = null) { SplitsServer splitsServer = this; _ws = new WebsocketServer { OnConnect = delegate { splitsServer.InitGameTime(); } }; _ws.Start(port, ip); MelonLogger.Msg("Splits server started at:\n" + string.Join("\n", ((ip == null) ? Network.GetAllAddresses() : new string[1] { ip }).Select((string address) => $"ws://{address}:{port}"))); } public void Start() { _ws.Send("start"); } public void Split() { _ws.Send("split"); } public void SplitOrStart() { _ws.Send("splitorstart"); } public void Reset() { _ws.Send("reset"); } public void TogglePause() { _ws.Send("togglepause"); } public void Undo() { _ws.Send("undo"); } public void Skip() { _ws.Send("skip"); } public void InitGameTime() { _ws.Send("initgametime"); } public void SetGameTime(TimeSpan time) { _ws.Send($"setgametime {time.TotalSeconds}"); } public void SetLoadingTimes(TimeSpan times) { _ws.Send($"setloadingtimes {times.TotalSeconds}"); } public void PauseGameTime() { _ws.Send("pausegametime"); } public void ResumeGameTime() { _ws.Send("resumegametime"); } } internal class SplitsTimer { [HarmonyPatch(typeof(TaxiController), "Start")] private class TaxiController_Start_Patch { [HarmonyPrefix] internal static void Prefix(TaxiController __instance) { Dbg.Log("TaxiController_Start_Patch"); __instance.OnPlayerSeated.AddListener(UnityAction.op_Implicit((Action)Instance.Finish)); } } private static Color FINISH_COLOR = new Color(0.2f, 0.8f, 0.2f); private static SplitsTimer Instance; private TextMeshPro _tmp; private TextMeshPro _tmpIl; private Splits _splits = new Splits(); private bool _isFinished; private static MelonPreferences_Entry<bool> _prefHide; private static MelonPreferences_Entry<bool> _prefHideIl; private static MelonPreferences_Entry<bool> _prefHideSplits; public SplitsTimer() { Instance = this; Livesplit.SetState(isLoading: true, isSittingInTaxi: false); } public static void OnInitialize() { _prefHide = Mod.Instance.PrefCategory.CreateEntry<bool>("hide", false, "Hide in-game timer", "Stops the timer from displaying on your wrist. Does not hide loading screen timer.", false, false, (ValueValidator)null, (string)null); _prefHideIl = Mod.Instance.PrefCategory.CreateEntry<bool>("hideIl", false, "Hide level timer", "Stops the individual level timer from displaying on your wrist.", false, false, (ValueValidator)null, (string)null); _prefHideSplits = Mod.Instance.PrefCategory.CreateEntry<bool>("hideSplits", false, "Hides split display in loading screen", "Stops the individual level times from displaying in the loading screen.", false, false, (ValueValidator)null, (string)null); } public void Reset() { _isFinished = false; _splits.Reset(); Mod.Instance.SplitsServer?.Reset(); Livesplit.SetState(isLoading: false, isSittingInTaxi: false); } public void OnLoadingScreen(LevelCrate nextLevel) { if ((Object)(object)nextLevel == (Object)null) { return; } if (_isFinished) { _splits.Reset(); _isFinished = false; } if (Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.BoneworksMainMenu" && Barcode.op_Implicit(((Scannable)LevelHooks.PrevLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.Boneworks12ThroneRoom") { _splits.Split(null); Finish(); } else { Livesplit.SetState(isLoading: true, isSittingInTaxi: false, ((Scannable)nextLevel).Title); if (Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "c2534c5a-4197-4879-8cd3-4a695363656e" || Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.Boneworks01Breakroom") { Dbg.Log("Attempting to start timer"); Mod.Instance.SplitsServer?.Start(); _splits.ResetAndPause(nextLevel); } else if (_splits.TimeStart.HasValue) { Dbg.Log("Splitting timer"); Mod.Instance.SplitsServer?.Split(); _splits.Pause(); _splits.Split(nextLevel); } } TimeSpan? time = _splits.GetTime(); if (time.HasValue) { Mod.Instance.SplitsServer?.PauseGameTime(); Mod.Instance.SplitsServer?.SetGameTime(time.Value); SplitsRenderer.RenderLoadingWatermark(time.Value); if (!_prefHideSplits.Value) { SplitsRenderer.RenderSplits(_splits); } } } public void OnLevelStart(LevelCrate level) { Livesplit.SetState(isLoading: false, isSittingInTaxi: false, ((Scannable)level).Title); Mod.Instance.SplitsServer?.ResumeGameTime(); if (!_isFinished) { _splits.ResumeIfStarted(); } AddTimerToWrist(); } private void AddTimerToWrist() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) if (!_prefHide.Value) { GameObject val = new GameObject("SpeedrunTimer_Wrist_Text"); _tmp = val.AddComponent<TextMeshPro>(); ((TMP_Text)_tmp).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_tmp).fontSize = 0.5f; ((TMP_Text)_tmp).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); if (_isFinished) { ((TMP_Text)_tmp).color = FINISH_COLOR; } Bonelab.DockToWrist(val); if (!_prefHideIl.Value) { GameObject val2 = new GameObject("SpeedrunTimer_Wrist_IL"); GameObject val3 = new GameObject("SpeedrunTimer_Wrist_IL_Offset"); val3.transform.parent = val2.transform; _tmpIl = val3.AddComponent<TextMeshPro>(); ((TMP_Text)_tmpIl).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_tmpIl).fontSize = 0.3f; ((TMP_Text)_tmpIl).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); ((Transform)((TMP_Text)_tmpIl).rectTransform).localPosition = new Vector3(-0.005f, -0.03f, 0.005f); ((TMP_Text)_tmpIl).color = new Color(0.2f, 0.2f, 0.2f); Bonelab.DockToWrist(val2); } } } public void OnUpdate() { if ((Object)(object)_tmp != (Object)null) { TimeSpan? time = _splits.GetTime(); if (!time.HasValue) { return; } ((Component)_tmp).gameObject.active = true; ((TMP_Text)_tmp).SetText(SplitsRenderer.DurationToString(time.Value), true); } if ((Object)(object)_tmpIl != (Object)null) { TimeSpan? currentSplitTime = _splits.GetCurrentSplitTime(); if (currentSplitTime.HasValue) { ((Component)_tmpIl).gameObject.active = true; ((TMP_Text)_tmpIl).SetText(SplitsRenderer.DurationToString(currentSplitTime.Value), true); } } } public void Finish() { //IL_00a3: Unknown result type (might be due to invalid IL or missing references) LevelCrate currentLevel = LevelHooks.CurrentLevel; Livesplit.SetState(isLoading: false, isSittingInTaxi: true, ((currentLevel != null) ? ((Scannable)currentLevel).Title : null) ?? ""); _splits.Pause(); Mod.Instance.SplitsServer?.Split(); Mod.Instance.SplitsServer?.SetGameTime(_splits.GetTime().Value); _isFinished = true; MelonLogger.Msg($"Stopping timer at: {_splits.GetTime()}"); if ((Object)(object)_tmp != (Object)null) { ((TMP_Text)_tmp).color = FINISH_COLOR; } } } internal class TimeTrialDisplayFix { [HarmonyPatch(typeof(BoneLeaderManager), "SubmitLeaderboardScore")] private class BoneLeaderManager_SubmitLeaderboardScore_Patch { [HarmonyPostfix] internal static void Postfix(BoneLeaderManager __instance, uint score) { DisplayTime(__instance, score); } } public const string LABEL_NAME = "TimeTrialDisplayFix_Label"; public static void DisplayTime(BoneLeaderManager leaderManager, uint milliseconds) { TMP_Text text_TitleBoard = leaderManager.text_TitleBoard; Transform parent = text_TitleBoard.transform.parent; Transform obj = parent.Find("TimeTrialDisplayFix_Label"); TextMeshPro obj2 = ((obj != null) ? ((Component)obj).GetComponent<TextMeshPro>() : null) ?? CreateTimeDisplay(parent, text_TitleBoard); string text = SplitsRenderer.DurationToString(TimeSpan.FromMilliseconds(milliseconds)); ((TMP_Text)obj2).SetText("Time: " + text, true); } private static TextMeshPro CreateTimeDisplay(Transform canvas, TMP_Text title) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_002c: 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) GameObject val = new GameObject("TimeTrialDisplayFix_Label"); val.transform.SetParent(canvas, false); val.transform.localPosition = new Vector3(0f, 1.2f, 3f); TextMeshPro obj = val.AddComponent<TextMeshPro>(); ((TMP_Text)obj).alignment = (TextAlignmentOptions)514; ((TMP_Text)obj).font = title.font; ((TMP_Text)obj).fontSize = 4f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(10f, 1f); return obj; } } internal class Livesplit { private static byte[] State = new byte[12] { 0, 226, 3, 52, 194, 223, 99, 36, 0, 0, 0, 0 }; public static void SetState(bool isLoading, bool isSittingInTaxi, string levelTitle = "") { State[0] = 212; State[8] = (byte)((isLoading ? 1u : 0u) | ((isSittingInTaxi ? 1u : 0u) << 1)); State[9] = Levels.GetIndex(levelTitle); } } public static class Network { public static string[] GetAllAddresses() { return new string[1] { "127.0.0.1" }.Concat(from address in Dns.GetHostEntry(Dns.GetHostName()).AddressList where address.AddressFamily == AddressFamily.InterNetwork select address.ToString()).ToArray(); } } public class Splits { private static Regex _levelPrefixPattern = new Regex("^\\s*\\d+\\s*-\\s*"); public List<Split> Items = new List<Split>(); public DateTime? TimeStart; public DateTime? TimeEnd; public DateTime? TimeStartRelative; public DateTime? TimePause; public DateTime TimeLastSplitStartRelative = DateTime.Now; private static string LevelSplitName(LevelCrate level) { return _levelPrefixPattern.Replace(((Scannable)level).Title, ""); } public void Reset() { TimeStart = null; TimeEnd = null; TimeStartRelative = null; TimePause = null; } public void ResetAndPause(LevelCrate firstLevel) { ResetAndStart(firstLevel); TimePause = TimeStart; } public void ResetAndStart(LevelCrate firstLevel) { DateTime now = DateTime.Now; TimeEnd = (TimePause = null); TimeStart = (TimeStartRelative = (TimeLastSplitStartRelative = now)); Items = new List<Split> { new Split { Level = firstLevel, Name = LevelSplitName(firstLevel), TimeStart = now } }; } public void Pause() { TimePause = DateTime.Now; } public void ResumeIfStarted() { if (TimePause.HasValue) { TimeSpan timeSpan = DateTime.Now - TimePause.Value; TimeStartRelative += timeSpan; TimeLastSplitStartRelative += timeSpan; TimePause = null; } } public TimeSpan? GetTime() { DateTime value = TimeEnd ?? TimePause ?? DateTime.Now; DateTime? timeStartRelative = TimeStartRelative; return value - timeStartRelative; } public TimeSpan? GetCurrentSplitTime() { return (TimeEnd ?? TimePause ?? DateTime.Now) - TimeLastSplitStartRelative; } public void Split(LevelCrate nextLevel) { Split split = Items[Items.Count - 1]; DateTime now = DateTime.Now; split.TimeEnd = now; DateTime valueOrDefault = TimePause.GetValueOrDefault(now); split.Duration = valueOrDefault - TimeLastSplitStartRelative; TimeLastSplitStartRelative = valueOrDefault; if (Object.op_Implicit((Object)(object)nextLevel)) { Items.Add(new Split { Level = nextLevel, Name = LevelSplitName(nextLevel), TimeStart = now }); } } } public class Split { public LevelCrate Level; public string Name; public DateTime? TimeStart; public DateTime? TimeEnd; public TimeSpan? Duration; } internal class SplitsRenderer { private static float SPLITS_WIDTH = 0.3f; private static float SPLITS_LINE_HEIGHT = 0.05f; private static float SPLITS_LEFT = -0.1f; private static float SPLITS_FONT_SIZE = 0.3f; public static void RenderLoadingWatermark(TimeSpan time) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) Dbg.Log("RenderLoadingWatermark"); BasicTrackingRig basicTrackingRig = LevelHooks.BasicTrackingRig; Transform val = ((basicTrackingRig != null) ? basicTrackingRig.head : null); if (!Object.op_Implicit((Object)(object)val)) { MelonLogger.Warning("Failed to render watermark in loading screen because could not find head position"); return; } TextMeshPro obj = CreateText(val, "Watermark"); ((TMP_Text)obj).alignment = (TextAlignmentOptions)260; ((TMP_Text)obj).fontSize = 0.5f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(0.8f, 0.8f); ((Transform)((TMP_Text)obj).rectTransform).localPosition = new Vector3(0f, 0f, 1f); ((TMP_Text)obj).color = new Color(0.4f, 0.4f, 0.6f); string text = ""; string text2 = DurationToString(time); string text3 = string.Join("\n", MelonTypeBase<MelonMod>.RegisteredMelons.Select((MelonMod mod) => ((MelonBase)mod).Info.Name)); ((TMP_Text)obj).SetText("SpeedrunTimer v1.8.1" + text + "\n" + text2 + "\n\nMods:\n" + text3, true); } public static void RenderSplits(Splits splits) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) BasicTrackingRig basicTrackingRig = LevelHooks.BasicTrackingRig; Transform val = ((basicTrackingRig != null) ? basicTrackingRig.head : null); if (!Object.op_Implicit((Object)(object)val)) { return; } GameObject val2 = new GameObject("SpeedrunTimer_Splits"); val2.transform.SetParent(((Component)val).transform); val2.transform.localPosition = new Vector3(-0.4f, 0.25f, 1f); Vector2 val4 = default(Vector2); for (int i = 0; i < splits.Items.Count; i++) { Split split = splits.Items[i]; float num = (float)i * (0f - SPLITS_LINE_HEIGHT); float num2 = 0f; if (split.Duration.HasValue) { TextMeshPro val3 = CreateText(val2.transform, "Splits_Time_" + split.Name); string text = ((split.Duration.Value >= TimeSpan.FromHours(1.0)) ? "h\\:mm\\:ss" : "m\\:ss\\.f"); ((TMP_Text)val3).SetText(split.Duration.Value.ToString(text), true); ((TMP_Text)val3).alignment = (TextAlignmentOptions)516; ((TMP_Text)val3).overflowMode = (TextOverflowModes)3; ((Transform)((TMP_Text)val3).rectTransform).localPosition = new Vector3(0f, 0f, 0f); RectTransform rectTransform = ((TMP_Text)val3).rectTransform; RectTransform rectTransform2 = ((TMP_Text)val3).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform2.anchorMax = val4; rectTransform.anchorMin = val4; ((TMP_Text)val3).rectTransform.offsetMin = new Vector2(SPLITS_LEFT, num); ((TMP_Text)val3).rectTransform.offsetMax = new Vector2(SPLITS_WIDTH + SPLITS_LEFT, num + SPLITS_LINE_HEIGHT); ((TMP_Text)val3).fontSize = SPLITS_FONT_SIZE; val3.ForceMeshUpdate(false, false); num2 = ((TMP_Text)val3).preferredWidth; } TextMeshPro val5 = CreateText(val2.transform, "Splits_Name_" + split.Name); string text2 = Regex.Replace(split.Name, "^boneworks(?:_\\d+)?\\s+", "", RegexOptions.IgnoreCase); ((TMP_Text)val5).SetText(text2, true); ((TMP_Text)val5).alignment = (TextAlignmentOptions)513; ((TMP_Text)val5).overflowMode = (TextOverflowModes)3; ((Transform)((TMP_Text)val5).rectTransform).localPosition = new Vector3(0f, 0f, 0f); RectTransform rectTransform3 = ((TMP_Text)val5).rectTransform; RectTransform rectTransform4 = ((TMP_Text)val5).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform4.anchorMax = val4; rectTransform3.anchorMin = val4; ((TMP_Text)val5).rectTransform.offsetMin = new Vector2(SPLITS_LEFT, num); ((TMP_Text)val5).rectTransform.offsetMax = new Vector2(SPLITS_WIDTH + SPLITS_LEFT - num2, num + SPLITS_LINE_HEIGHT); ((TMP_Text)val5).fontSize = SPLITS_FONT_SIZE; } } public static string DurationToString(TimeSpan duration) { return duration.ToString(((duration.Hours >= 1) ? "h\\:m" : "") + "m\\:ss\\.ff"); } private static TextMeshPro CreateText(Transform parent, string name) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) TextMeshPro obj = new GameObject("SpeedrunTimer_" + name) { layer = LayerMask.NameToLayer("Background") }.AddComponent<TextMeshPro>(); obj.transform.SetParent(parent); obj.sortingOrder = 100; ((TMP_Text)obj).color = new Color(0.5f, 0.5f, 0.5f); return obj; } } public class WebsocketServer { public class Client { public TcpClient TcpClient; public List<byte> MessageBuffer = new List<byte>(); public BlockingCollection<byte[]> SendQueue = new BlockingCollection<byte[]>(); public void Send(string data) { Send(Encoding.UTF8.GetBytes(data), isString: true); } public void Send(byte[] data, bool isString = false) { SendQueue.Add(CreateMessageFrame(data, isString)); } } public TcpListener listener; private List<Client> connectedClients = new List<Client>(); public Action<Client> OnConnect; public Action<string, Client> OnMessage; public void Start(int port = 6161, string ip = null) { listener = new TcpListener((ip != null) ? IPAddress.Parse(ip) : IPAddress.Any, port); listener.Start(); Task.Run((Action)ListenForConnections); } public void Send(string data) { Send(Encoding.UTF8.GetBytes(data), isString: true); } public void Send(byte[] data, bool isString = false) { byte[] item = CreateMessageFrame(data, isString); Client[] array = connectedClients.ToArray(); foreach (Client client in array) { if (client.TcpClient.Connected) { client.SendQueue.Add(item); } else { connectedClients.Remove(client); } } } private void ListenForConnections() { while (true) { try { TcpClient tcpClient = listener.AcceptTcpClient(); MelonLogger.Msg("Websocket client connected"); Client client = new Client { TcpClient = tcpClient }; connectedClients.Add(client); Task.Run(delegate { HandleClient(client); }); Thread thread = new Thread((ThreadStart)delegate { ListenToSendQueue(client); }); thread.IsBackground = true; thread.Start(); } catch (Exception ex) { MelonLogger.Error("Error listening for Websocket connections:"); MelonLogger.Error((object)ex); Thread.Sleep(5000); } } } private void HandleClient(Client client) { byte[] array = new byte[1024]; try { NetworkStream stream = client.TcpClient.GetStream(); while (client.TcpClient.Connected) { int num = stream.Read(array, 0, array.Length); if (num == 0) { break; } byte[] array2 = new byte[num]; Array.Copy(array, array2, num); if (IsClientHandshake(array2)) { RespondToClientHandshake(array2, client); continue; } byte[] array3 = ReadMessage(array2, client); if (array3 != null) { string @string = Encoding.UTF8.GetString(array3); if (OnMessage != null) { OnMessage(@string, client); } client.MessageBuffer.Clear(); } } } catch (Exception ex) { MelonLogger.Warning("Error with websocket: {0}", new object[1] { ex }); } finally { CloseConnection(client); } } private void CloseConnection(Client client) { if (connectedClients.Contains(client)) { MelonLogger.Msg("Websocket disconnected"); client.SendQueue.CompleteAdding(); connectedClients.Remove(client); } else { client.TcpClient.Dispose(); } } private void ListenToSendQueue(Client client) { try { NetworkStream stream = client.TcpClient.GetStream(); foreach (byte[] item in client.SendQueue.GetConsumingEnumerable()) { if (client.TcpClient.Connected) { stream.Write(item, 0, item.Length); continue; } break; } } catch (Exception ex) { MelonLogger.Warning("Websocket error:"); MelonLogger.Warning((object)ex); } finally { CloseConnection(client); } } private bool IsClientHandshake(byte[] request) { return Regex.IsMatch(Encoding.UTF8.GetString(request, 0, 4), "^GET\\s", RegexOptions.IgnoreCase); } private void RespondToClientHandshake(byte[] request, Client client) { NetworkStream stream = client.TcpClient.GetStream(); string @string = Encoding.UTF8.GetString(request); byte[] bytes = Encoding.UTF8.GetBytes(string.Join("\r\n", "HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", "Upgrade: websocket", "Sec-WebSocket-Accept: " + GenerateWebsocketAcceptToken(@string), "", "")); stream.Write(bytes, 0, bytes.Length); OnConnect(client); } private string GenerateWebsocketAcceptToken(string request) { string s = Regex.Match(request, "\\n\\s*Sec-WebSocket-Key\\s*:(.*)", RegexOptions.IgnoreCase).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; return Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(s))); } private byte[] ReadMessage(byte[] request, Client client) { bool flag = (request[0] & 0x80) != 0; if ((request[1] & 0x80) == 0) { return null; } int num = 2; ulong num2 = (ulong)(int)(request[1] & 0x7Fu); switch (num2) { case 126uL: num2 = BitConverter.ToUInt16(new byte[2] { request[3], request[2] }, 0); num = 4; break; case 127uL: num2 = BitConverter.ToUInt64(new byte[8] { request[9], request[8], request[7], request[6], request[5], request[4], request[3], request[2] }, 0); num = 10; break; } byte[] array = new byte[num2]; int num3 = num; num += 4; for (ulong num4 = 0uL; num4 < num2; num4++) { array[num4] = (byte)(request[num + (int)num4] ^ request[num3 + ((int)num4 & 3)]); } client.MessageBuffer.AddRange(array); if (!flag) { return null; } return client.MessageBuffer.ToArray(); } private static byte[] CreateMessageFrame(byte[] data, bool isString) { byte[] array; if (data.Length < 126) { array = new byte[data.Length + 2]; array[1] = (byte)data.Length; } else if (data.Length <= 65535) { array = new byte[data.Length + 4]; array[1] = 126; byte[] bytes = BitConverter.GetBytes((ushort)data.Length); array[2] = bytes[1]; array[3] = bytes[0]; } else { array = new byte[data.Length + 10]; array[1] = 127; byte[] bytes2 = BitConverter.GetBytes((ulong)data.Length); for (int i = 0; i < 8; i++) { array[9 - i] = bytes2[i]; } } array[0] = (byte)(isString ? 129u : 130u); Array.Copy(data, 0, array, array.Length - data.Length, data.Length); return array; } } }
Patch4_MelonLoader0.6/Mods/SpeedrunTimer.P4.ML6.dll
Decompiled 4 months agousing System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using HarmonyLib; using Il2CppSLZ.Bonelab; using Il2CppSLZ.Bonelab.SaveData; using Il2CppSLZ.Interaction; using Il2CppSLZ.Marrow.Input; using Il2CppSLZ.Marrow.SaveData; using Il2CppSLZ.Marrow.SceneStreaming; using Il2CppSLZ.Marrow.Utilities; using Il2CppSLZ.Marrow.Warehouse; using Il2CppSLZ.Rig; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using Sst.SpeedrunTimer; using Sst.Utilities; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("SpeedrunTimer")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany(null)] [assembly: AssemblyProduct("SpeedrunTimer")] [assembly: AssemblyCopyright("Created by jakzo")] [assembly: AssemblyTrademark(null)] [assembly: ComVisible(false)] [assembly: AssemblyFileVersion("1.8.1")] [assembly: NeutralResourcesLanguage("en")] [assembly: MelonInfo(typeof(Mod), "SpeedrunTimer", "1.8.1", "jakzo", "https://bonelab.thunderstore.io/package/jakzo/SpeedrunTimer/")] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyVersion("1.8.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Sst { public class Metadata { public const string AUTHOR = "jakzo"; public const string COMPANY = null; public const string DEVELOPER = "Stress Level Zero"; public const string GAME = "BONELAB"; public const string GAME_BONEWORKS = "BONEWORKS"; } public class Dbg { private static MelonPreferences_Entry<bool> _prefPrintDebugLogs; public static void Init(string prefCategoryId) { _prefPrintDebugLogs = MelonPreferences.CreateCategory(prefCategoryId).CreateEntry<bool>("printDebugLogs", false, "Print debug logs", "Print debug logs to console", false, true, (ValueValidator)null, (string)null); } public static void Log(string msg, params object[] data) { if (_prefPrintDebugLogs.Value) { MelonLogger.Msg("dbg: " + msg); } } } } namespace Sst.Utilities { internal static class LevelHooks { [HarmonyPatch(typeof(BasicTrackingRig), "Awake")] private class BasicTrackingRig_Awake_Patch { [CompilerGenerated] private static class <>O { public static LemonAction <0>__WaitForLoadFinished; } [HarmonyPrefix] internal static void Prefix(BasicTrackingRig __instance) { //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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown Dbg.Log("BasicTrackingRig_Awake_Patch"); _loadingScene = ((Component)__instance).gameObject.scene; if (Object.op_Implicit((Object)(object)CurrentLevel)) { PrevLevel = CurrentLevel; } CurrentLevel = null; NextLevel = SceneStreamer.Session.Level; RigManager = null; BasicTrackingRig = __instance; MelonEvent onUpdate = MelonEvents.OnUpdate; object obj = <>O.<0>__WaitForLoadFinished; if (obj == null) { LemonAction val = WaitForLoadFinished; <>O.<0>__WaitForLoadFinished = val; obj = (object)val; } ((MelonEventBase<LemonAction>)(object)onUpdate).Subscribe((LemonAction)obj, 0, false); LevelCrate nextLevel = NextLevel; Dbg.Log("OnLoad " + ((nextLevel != null) ? ((Scannable)nextLevel).Title : null)); SafeInvoke("OnLoad", LevelHooks.OnLoad, NextLevel); } } [HarmonyPatch(typeof(RigManager), "Awake")] private class RigManager_Awake_Patch { [HarmonyPrefix] internal static void Prefix(RigManager __instance) { Dbg.Log("RigManager_Awake_Patch"); RigManager = __instance; BasicTrackingRig = null; } } [CompilerGenerated] private static class <>O { public static LemonAction <0>__WaitForLoadFinished; } public static LevelCrate PrevLevel; public static LevelCrate CurrentLevel; public static LevelCrate NextLevel; public static RigManager RigManager; public static BasicTrackingRig BasicTrackingRig; private static Scene _loadingScene; public static bool IsLoading => !Object.op_Implicit((Object)(object)CurrentLevel); public static event Action<LevelCrate> OnLoad; public static event Action<LevelCrate> OnLevelStart; private static void SafeInvoke(string name, Action<LevelCrate> action, LevelCrate level) { try { action?.Invoke(level); } catch (Exception ex) { MelonLogger.Error("Failed to execute " + name + " event: " + ex.ToString()); } } private static void WaitForLoadFinished() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown if (!((Scene)(ref _loadingScene)).isLoaded) { MelonEvent onUpdate = MelonEvents.OnUpdate; object obj = <>O.<0>__WaitForLoadFinished; if (obj == null) { LemonAction val = WaitForLoadFinished; <>O.<0>__WaitForLoadFinished = val; obj = (object)val; } ((MelonEventBase<LemonAction>)(object)onUpdate).Unsubscribe((LemonAction)obj); CurrentLevel = NextLevel ?? SceneStreamer.Session.Level ?? CurrentLevel; NextLevel = null; LevelCrate currentLevel = CurrentLevel; Dbg.Log("OnLevelStart " + ((currentLevel != null) ? ((Scannable)currentLevel).Title : null)); SafeInvoke("OnLevelStart", LevelHooks.OnLevelStart, CurrentLevel); } } } public class Bonelab { public static void DockToWrist(GameObject gameObject, bool rightHand = false) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) PhysicsRig physicsRig = LevelHooks.RigManager.physicsRig; Hand val = (rightHand ? physicsRig.rightHand : physicsRig.leftHand); gameObject.transform.SetParent(((Component)val).transform); gameObject.transform.localPosition = new Vector3(-0.31f, 0.3f, 0f); gameObject.transform.localRotation = Quaternion.Euler(32f, 4f, 3f); } public static TextMeshPro CreateTextOnWrist(string name, bool rightHand = false) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name); TextMeshPro obj = val.AddComponent<TextMeshPro>(); ((TMP_Text)obj).alignment = (TextAlignmentOptions)1028; ((TMP_Text)obj).fontSize = 0.5f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); DockToWrist(val, rightHand); return obj; } } public class Levels { public class Barcodes { public const string DESCENT = "c2534c5a-4197-4879-8cd3-4a695363656e"; public const string HUB = "c2534c5a-6b79-40ec-8e98-e58c5363656e"; public const string LONG_RUN = "c2534c5a-56a6-40ab-a8ce-23074c657665"; public const string MINE_DIVE = "c2534c5a-54df-470b-baaf-741f4c657665"; public const string BIG_ANOMALY_A = "c2534c5a-7601-4443-bdfe-7f235363656e"; public const string STREET_PUNCHER = "SLZ.BONELAB.Content.Level.LevelStreetPunch"; public const string SPRINT_BRIDGE = "SLZ.BONELAB.Content.Level.SprintBridge04"; public const string MAGMA_GATE = "SLZ.BONELAB.Content.Level.SceneMagmaGate"; public const string MOON_BASE = "SLZ.BONELAB.Content.Level.MoonBase"; public const string MONOGON_MOTORWAY = "SLZ.BONELAB.Content.Level.LevelKartRace"; public const string PILLAR = "c2534c5a-c056-4883-ac79-e051426f6964"; public const string BIG_ANOMALY_B = "SLZ.BONELAB.Content.Level.LevelBigAnomalyB"; public const string ASCENT = "c2534c5a-db71-49cf-b694-24584c657665"; public const string OUTRO = "SLZ.BONELAB.Content.Level.LevelOutro"; public const string ROOFTOPS = "c2534c5a-c6ac-48b4-9c5f-b5cd5363656e"; public const string TUNNEL_TIPPER = "c2534c5a-c180-40e0-b2b7-325c5363656e"; public const string DISTRICT_TAC_TRIAL = "c2534c5a-4f3b-480e-ad2f-69175363656e"; public const string DROP_PIT = "c2534c5a-de61-4df9-8f6c-416954726547"; public const string TUSCANY = "c2534c5a-2c4c-4b44-b076-203b5363656e"; public const string MAIN_MENU = "c2534c5a-80e1-4a29-93ca-f3254d656e75"; public const string VOID_G114 = "fa534c5a868247138f50c62e424c4144.Level.VoidG114"; public const string FANTASY_ARENA = "fa534c5a868247138f50c62e424c4144.Level.LevelArenaMin"; } public class LabworksBarcodes { public const string BREAKROOM = "volx4.LabWorksBoneworksPort.Level.Boneworks01Breakroom"; public const string THRONE_ROOM = "volx4.LabWorksBoneworksPort.Level.Boneworks12ThroneRoom"; public const string MAIN_MENU = "volx4.LabWorksBoneworksPort.Level.BoneworksMainMenu"; } public const string TITLE_DESCENT = "01 - Descent"; public const string TITLE_MONOGON_MOTORWAY = "10 - Monogon Motorway"; public static Dictionary<string, byte> TitleToIndex = new Dictionary<string, byte> { ["01 - Descent"] = 1, ["02 - BONELAB Hub"] = 2, ["03 - LongRun"] = 3, ["04 - Mine Dive"] = 4, ["05 - Big Anomaly"] = 5, ["06 - Street Puncher"] = 6, ["07 - Sprint Bridge 04"] = 7, ["08 - Magma Gate"] = 8, ["09- MoonBase"] = 9, ["10 - Monogon Motorway"] = 10, ["11 - Pillar Climb"] = 11, ["12 - Big Anomaly B"] = 12, ["13 - Ascent"] = 13, ["14 - Home"] = 14, ["15 - Void G114"] = 15, ["Big Bone Bowling"] = 16, ["Container Yard"] = 17, ["Neon District Parkour"] = 18, ["Neon District Tac Trial"] = 19, ["Drop Pit"] = 20, ["Dungeon Warrior"] = 21, ["Fantasy Arena"] = 22, ["Gun Range"] = 23, ["Halfway Park"] = 24, ["HoloChamber"] = 25, ["Mirror"] = 26, ["Museum Basement"] = 27, ["Rooftops"] = 28, ["Tunnel Tipper"] = 29, ["Tuscany"] = 30, ["00 - Main Menu"] = 31, ["Baseline"] = 32, ["Load Default"] = 33, ["Load Mod"] = 34, ["Boneworks Main Menu"] = 100 }; public static string[] CAMPAIGN_LEVEL_BARCODES = new string[14] { "c2534c5a-4197-4879-8cd3-4a695363656e", "c2534c5a-6b79-40ec-8e98-e58c5363656e", "c2534c5a-56a6-40ab-a8ce-23074c657665", "c2534c5a-54df-470b-baaf-741f4c657665", "c2534c5a-7601-4443-bdfe-7f235363656e", "SLZ.BONELAB.Content.Level.LevelStreetPunch", "SLZ.BONELAB.Content.Level.SprintBridge04", "SLZ.BONELAB.Content.Level.SceneMagmaGate", "SLZ.BONELAB.Content.Level.MoonBase", "SLZ.BONELAB.Content.Level.LevelKartRace", "c2534c5a-c056-4883-ac79-e051426f6964", "SLZ.BONELAB.Content.Level.LevelBigAnomalyB", "c2534c5a-db71-49cf-b694-24584c657665", "SLZ.BONELAB.Content.Level.LevelOutro" }; public static HashSet<string> CAMPAIGN_LEVEL_BARCODES_SET = CAMPAIGN_LEVEL_BARCODES.ToHashSet(); public static byte GetIndex(string title) { if (TitleToIndex.TryGetValue(title, out var value)) { return value; } Match match = Regex.Match(title, "^Boneworks_(\\d+) "); if (match.Success) { return (byte)(100 + int.Parse(match.Groups[1].Value)); } return 0; } public static bool IsMenu(string barcode) { if (!(barcode == "c2534c5a-80e1-4a29-93ca-f3254d656e75")) { return barcode == "fa534c5a868247138f50c62e424c4144.Level.VoidG114"; } return true; } } } namespace Sst.SpeedrunTimer { internal static class AppVersion { public const string Value = "1.8.1"; } public static class BuildInfo { public const string Name = "SpeedrunTimer"; } public class InputServer { private const float DEGREES_TO_RADIANS = (float)Math.PI / 180f; private WebsocketServer websocketServer; public InputServer(int port = 6161, string ip = null) { websocketServer = new WebsocketServer { OnConnect = delegate(WebsocketServer.Client client) { client.Send("{\"inputVersion\":1}"); } }; websocketServer.Start(port, ip); MelonLogger.Msg("Input viewer server started at:\n" + string.Join("\n", ((ip == null) ? Network.GetAllAddresses() : new string[1] { ip }).Select((string address) => $"ws://{address}:{port}"))); } public void SendInputState() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Unknown result type (might be due to invalid IL or missing references) XRApi xr = MarrowGame.xr; if (((xr != null) ? xr.HMD : null) == null) { return; } byte item = 1; List<byte> list = new List<byte>(128) { item }; AddToData(list, Time.unscaledTime); AddToData(list, (XRDevice)(object)MarrowGame.xr.HMD); XRController[] array = (XRController[])(object)new XRController[2] { MarrowGame.xr.LeftController, MarrowGame.xr.RightController }; foreach (XRController val in array) { if (val != null) { AddToData(list, (XRDevice)(object)val); } else { AddToData(list, default(Vector3)); AddToData(list, default(Quaternion)); } bool[] obj = new bool[15] { ((XRDevice)val).IsConnected, val.TriggerButton, val.TriggerTouched, val.GripButton, false, val.TouchpadButton, val.TouchpadTouch, val.JoystickButton, val.JoystickTouch, val.AButton, val.ATouch, val.BButton, val.BTouch, val.MenuButton, false }; int num = 0; int num2 = 0; bool[] array2 = obj; for (int j = 0; j < array2.Length; j++) { if (array2[j]) { num |= 1 << num2; } num2++; } list.AddRange(BitConverter.GetBytes(num)); AddToData(list, val.Touchpad2DAxis); AddToData(list, val.Joystick2DAxis); AddToData(list, val.Trigger); AddToData(list, val.Grip); } websocketServer.Send(list.ToArray()); } private void AddToData(List<byte> data, int value) { data.AddRange(BitConverter.GetBytes(value)); } private void AddToData(List<byte> data, float value) { data.AddRange(BitConverter.GetBytes(value)); } private void AddToData(List<byte> data, Vector2 value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.x); AddToData(data, value.y); } private void AddToData(List<byte> data, Vector3 value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.x); AddToData(data, value.y); AddToData(data, value.z); } private void AddToData(List<byte> data, Quaternion value) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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) AddToData(data, value.x); AddToData(data, value.y); AddToData(data, value.z); AddToData(data, value.w); } private void AddToData(List<byte> data, XRDevice value) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.Position); AddToData(data, value.Rotation); } private void AddToData(List<byte> data, Transform value) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) AddToData(data, value.localPosition); AddToData(data, value.localRotation); } } public class Mod : MelonMod { public const string PREF_CATEGORY_ID = "SpeedrunTimer"; private static SplitsTimer _timer = new SplitsTimer(); private InputServer _inputServer; public MelonPreferences_Category PrefCategory; public SplitsServer SplitsServer; public static Mod Instance; public Mod() { Instance = this; } public override void OnInitializeMelon() { Dbg.Init("SpeedrunTimer"); PrefCategory = MelonPreferences.CreateCategory("SpeedrunTimer"); SplitsTimer.OnInitialize(); SaveDeleteImprovements.OnInitialize(); LevelHooks.OnLoad += _timer.OnLoadingScreen; LevelHooks.OnLevelStart += _timer.OnLevelStart; SplitsServer = new SplitsServer(); _inputServer = new InputServer(); } public override void OnUpdate() { _timer.OnUpdate(); _inputServer?.SendInputState(); } } public static class SaveDeleteImprovements { [HarmonyPatch(typeof(DataManager), "_MSAFAIGE")] private class DataManager_MSAFAIGE_Patch { [HarmonyPrefix] internal static void Prefix() { Dbg.Log("DataManager_MSAFAIGE_Prefix"); if (!_prefDeleteModsOnWipe.Value) { _modsBackupPath = Path.Combine(Application.persistentDataPath, "Mods.backup"); if (Directory.Exists(_modsBackupPath)) { Directory.Delete(_modsBackupPath, recursive: true); } Directory.Move(GetModsPath(), _modsBackupPath); } } [HarmonyPostfix] internal static void Postfix() { Dbg.Log("DataManager_MSAFAIGE_Postfix"); if (_modsBackupPath != null) { if (Directory.Exists(_modsBackupPath)) { MelonLogger.Warning("Failed to restore mods folder on data wipe! Old mods are in Mods.backup now."); } _modsBackupPath = null; } } } [HarmonyPatch(typeof(MarrowDataManager<DataManager, Save, Settings, PlayerProgression, PlayerUnlocks>), "get_SavePath")] private class MarrowDataManager_SavePath_Patch { [HarmonyPrefix] internal static void Prefix() { Dbg.Log("MarrowDataManager_SavePath_Patch"); RestoreModsBackup(); } } private static MelonPreferences_Entry<bool> _prefDeleteModsOnWipe; private static string _modsBackupPath; private static string GetModsPath() { return Path.Combine(Application.persistentDataPath, "Mods"); } public static void OnInitialize() { _prefDeleteModsOnWipe = Mod.Instance.PrefCategory.CreateEntry<bool>("deleteModsOnWipe", false, "Let mods be deleted when wiping all data", "Normally when resetting your save state through the main menu the game will delete all mods including the SpeedrunTimer. For convenience the timer will keep your mods folder. This option reverts to the original behavior of deleting mods.", false, false, (ValueValidator)null, (string)null); } private static void RestoreModsBackup() { if (_modsBackupPath != null && Directory.Exists(_modsBackupPath) && !Directory.Exists(GetModsPath())) { Directory.Move(_modsBackupPath, GetModsPath()); _modsBackupPath = null; } } } public class SplitsServer { private WebsocketServer _ws; public SplitsServer(int port = 6162, string ip = null) { SplitsServer splitsServer = this; _ws = new WebsocketServer { OnConnect = delegate { splitsServer.InitGameTime(); } }; _ws.Start(port, ip); MelonLogger.Msg("Splits server started at:\n" + string.Join("\n", ((ip == null) ? Network.GetAllAddresses() : new string[1] { ip }).Select((string address) => $"ws://{address}:{port}"))); } public void Start() { _ws.Send("start"); } public void Split() { _ws.Send("split"); } public void SplitOrStart() { _ws.Send("splitorstart"); } public void Reset() { _ws.Send("reset"); } public void TogglePause() { _ws.Send("togglepause"); } public void Undo() { _ws.Send("undo"); } public void Skip() { _ws.Send("skip"); } public void InitGameTime() { _ws.Send("initgametime"); } public void SetGameTime(TimeSpan time) { _ws.Send($"setgametime {time.TotalSeconds}"); } public void SetLoadingTimes(TimeSpan times) { _ws.Send($"setloadingtimes {times.TotalSeconds}"); } public void PauseGameTime() { _ws.Send("pausegametime"); } public void ResumeGameTime() { _ws.Send("resumegametime"); } } internal class SplitsTimer { [HarmonyPatch(typeof(TaxiController), "Start")] private class TaxiController_Start_Patch { [HarmonyPrefix] internal static void Prefix(TaxiController __instance) { Dbg.Log("TaxiController_Start_Patch"); __instance.OnPlayerSeated.AddListener(UnityAction.op_Implicit((Action)Instance.Finish)); } } private static Color FINISH_COLOR = new Color(0.2f, 0.8f, 0.2f); private static SplitsTimer Instance; private TextMeshPro _tmp; private TextMeshPro _tmpIl; private Splits _splits = new Splits(); private bool _isFinished; private static MelonPreferences_Entry<bool> _prefHide; private static MelonPreferences_Entry<bool> _prefHideIl; private static MelonPreferences_Entry<bool> _prefHideSplits; public SplitsTimer() { Instance = this; Livesplit.SetState(isLoading: true, isSittingInTaxi: false); } public static void OnInitialize() { _prefHide = Mod.Instance.PrefCategory.CreateEntry<bool>("hide", false, "Hide in-game timer", "Stops the timer from displaying on your wrist. Does not hide loading screen timer.", false, false, (ValueValidator)null, (string)null); _prefHideIl = Mod.Instance.PrefCategory.CreateEntry<bool>("hideIl", false, "Hide level timer", "Stops the individual level timer from displaying on your wrist.", false, false, (ValueValidator)null, (string)null); _prefHideSplits = Mod.Instance.PrefCategory.CreateEntry<bool>("hideSplits", false, "Hides split display in loading screen", "Stops the individual level times from displaying in the loading screen.", false, false, (ValueValidator)null, (string)null); } public void Reset() { _isFinished = false; _splits.Reset(); Mod.Instance.SplitsServer?.Reset(); Livesplit.SetState(isLoading: false, isSittingInTaxi: false); } public void OnLoadingScreen(LevelCrate nextLevel) { if ((Object)(object)nextLevel == (Object)null) { return; } if (_isFinished) { _splits.Reset(); _isFinished = false; } if (Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.BoneworksMainMenu" && Barcode.op_Implicit(((Scannable)LevelHooks.PrevLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.Boneworks12ThroneRoom") { _splits.Split(null); Finish(); } else { Livesplit.SetState(isLoading: true, isSittingInTaxi: false, ((Scannable)nextLevel).Title); if (Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "c2534c5a-4197-4879-8cd3-4a695363656e" || Barcode.op_Implicit(((Scannable)nextLevel).Barcode) == "volx4.LabWorksBoneworksPort.Level.Boneworks01Breakroom") { Dbg.Log("Attempting to start timer"); Mod.Instance.SplitsServer?.Start(); _splits.ResetAndPause(nextLevel); } else if (_splits.TimeStart.HasValue) { Dbg.Log("Splitting timer"); Mod.Instance.SplitsServer?.Split(); _splits.Pause(); _splits.Split(nextLevel); } } TimeSpan? time = _splits.GetTime(); if (time.HasValue) { Mod.Instance.SplitsServer?.PauseGameTime(); Mod.Instance.SplitsServer?.SetGameTime(time.Value); SplitsRenderer.RenderLoadingWatermark(time.Value); if (!_prefHideSplits.Value) { SplitsRenderer.RenderSplits(_splits); } } } public void OnLevelStart(LevelCrate level) { Livesplit.SetState(isLoading: false, isSittingInTaxi: false, ((Scannable)level).Title); Mod.Instance.SplitsServer?.ResumeGameTime(); if (!_isFinished) { _splits.ResumeIfStarted(); } AddTimerToWrist(); } private void AddTimerToWrist() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) if (!_prefHide.Value) { GameObject val = new GameObject("SpeedrunTimer_Wrist_Text"); _tmp = val.AddComponent<TextMeshPro>(); ((TMP_Text)_tmp).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_tmp).fontSize = 0.5f; ((TMP_Text)_tmp).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); if (_isFinished) { ((Graphic)_tmp).color = FINISH_COLOR; } Bonelab.DockToWrist(val); if (!_prefHideIl.Value) { GameObject val2 = new GameObject("SpeedrunTimer_Wrist_IL"); GameObject val3 = new GameObject("SpeedrunTimer_Wrist_IL_Offset"); val3.transform.parent = val2.transform; _tmpIl = val3.AddComponent<TextMeshPro>(); ((TMP_Text)_tmpIl).alignment = (TextAlignmentOptions)1028; ((TMP_Text)_tmpIl).fontSize = 0.3f; ((TMP_Text)_tmpIl).rectTransform.sizeDelta = new Vector2(0.8f, 0.5f); ((Transform)((TMP_Text)_tmpIl).rectTransform).localPosition = new Vector3(-0.005f, -0.03f, 0.005f); ((Graphic)_tmpIl).color = new Color(0.2f, 0.2f, 0.2f); Bonelab.DockToWrist(val2); } } } public void OnUpdate() { if ((Object)(object)_tmp != (Object)null) { TimeSpan? time = _splits.GetTime(); if (!time.HasValue) { return; } ((Component)_tmp).gameObject.active = true; ((TMP_Text)_tmp).SetText(SplitsRenderer.DurationToString(time.Value), true); } if ((Object)(object)_tmpIl != (Object)null) { TimeSpan? currentSplitTime = _splits.GetCurrentSplitTime(); if (currentSplitTime.HasValue) { ((Component)_tmpIl).gameObject.active = true; ((TMP_Text)_tmpIl).SetText(SplitsRenderer.DurationToString(currentSplitTime.Value), true); } } } public void Finish() { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) LevelCrate currentLevel = LevelHooks.CurrentLevel; Livesplit.SetState(isLoading: false, isSittingInTaxi: true, ((currentLevel != null) ? ((Scannable)currentLevel).Title : null) ?? ""); _splits.Pause(); Mod.Instance.SplitsServer?.Split(); Mod.Instance.SplitsServer?.SetGameTime(_splits.GetTime().Value); _isFinished = true; MelonLogger.Msg($"Stopping timer at: {_splits.GetTime()}"); if ((Object)(object)_tmp != (Object)null) { ((Graphic)_tmp).color = FINISH_COLOR; } } } internal class TimeTrialDisplayFix { [HarmonyPatch(typeof(BoneLeaderManager), "SubmitLeaderboardScore")] private class BoneLeaderManager_SubmitLeaderboardScore_Patch { [HarmonyPostfix] internal static void Postfix(BoneLeaderManager __instance, uint score) { DisplayTime(__instance, score); } } public const string LABEL_NAME = "TimeTrialDisplayFix_Label"; public static void DisplayTime(BoneLeaderManager leaderManager, uint milliseconds) { TMP_Text text_TitleBoard = leaderManager.text_TitleBoard; Transform parent = text_TitleBoard.transform.parent; Transform obj = parent.Find("TimeTrialDisplayFix_Label"); TextMeshPro obj2 = ((obj != null) ? ((Component)obj).GetComponent<TextMeshPro>() : null) ?? CreateTimeDisplay(parent, text_TitleBoard); string text = SplitsRenderer.DurationToString(TimeSpan.FromMilliseconds(milliseconds)); ((TMP_Text)obj2).SetText("Time: " + text, true); } private static TextMeshPro CreateTimeDisplay(Transform canvas, TMP_Text title) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_002c: 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) GameObject val = new GameObject("TimeTrialDisplayFix_Label"); val.transform.SetParent(canvas, false); val.transform.localPosition = new Vector3(0f, 1.2f, 3f); TextMeshPro obj = val.AddComponent<TextMeshPro>(); ((TMP_Text)obj).alignment = (TextAlignmentOptions)514; ((TMP_Text)obj).font = title.font; ((TMP_Text)obj).fontSize = 4f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(10f, 1f); return obj; } } internal class Livesplit { private static byte[] State = new byte[12] { 0, 226, 3, 52, 194, 223, 99, 36, 0, 0, 0, 0 }; public static void SetState(bool isLoading, bool isSittingInTaxi, string levelTitle = "") { State[0] = 212; State[8] = (byte)((isLoading ? 1u : 0u) | ((isSittingInTaxi ? 1u : 0u) << 1)); State[9] = Levels.GetIndex(levelTitle); } } public static class Network { public static string[] GetAllAddresses() { return new string[1] { "127.0.0.1" }.Concat(from address in Dns.GetHostEntry(Dns.GetHostName()).AddressList where address.AddressFamily == AddressFamily.InterNetwork select address.ToString()).ToArray(); } } public class Splits { private static Regex _levelPrefixPattern = new Regex("^\\s*\\d+\\s*-\\s*"); public List<Split> Items = new List<Split>(); public DateTime? TimeStart; public DateTime? TimeEnd; public DateTime? TimeStartRelative; public DateTime? TimePause; public DateTime TimeLastSplitStartRelative = DateTime.Now; private static string LevelSplitName(LevelCrate level) { return _levelPrefixPattern.Replace(((Scannable)level).Title, ""); } public void Reset() { TimeStart = null; TimeEnd = null; TimeStartRelative = null; TimePause = null; } public void ResetAndPause(LevelCrate firstLevel) { ResetAndStart(firstLevel); TimePause = TimeStart; } public void ResetAndStart(LevelCrate firstLevel) { DateTime now = DateTime.Now; TimeEnd = (TimePause = null); TimeStart = (TimeStartRelative = (TimeLastSplitStartRelative = now)); Items = new List<Split> { new Split { Level = firstLevel, Name = LevelSplitName(firstLevel), TimeStart = now } }; } public void Pause() { TimePause = DateTime.Now; } public void ResumeIfStarted() { if (TimePause.HasValue) { TimeSpan timeSpan = DateTime.Now - TimePause.Value; TimeStartRelative += timeSpan; TimeLastSplitStartRelative += timeSpan; TimePause = null; } } public TimeSpan? GetTime() { DateTime value = TimeEnd ?? TimePause ?? DateTime.Now; DateTime? timeStartRelative = TimeStartRelative; return value - timeStartRelative; } public TimeSpan? GetCurrentSplitTime() { return (TimeEnd ?? TimePause ?? DateTime.Now) - TimeLastSplitStartRelative; } public void Split(LevelCrate nextLevel) { Split split = Items[Items.Count - 1]; DateTime now = DateTime.Now; split.TimeEnd = now; DateTime valueOrDefault = TimePause.GetValueOrDefault(now); split.Duration = valueOrDefault - TimeLastSplitStartRelative; TimeLastSplitStartRelative = valueOrDefault; if (Object.op_Implicit((Object)(object)nextLevel)) { Items.Add(new Split { Level = nextLevel, Name = LevelSplitName(nextLevel), TimeStart = now }); } } } public class Split { public LevelCrate Level; public string Name; public DateTime? TimeStart; public DateTime? TimeEnd; public TimeSpan? Duration; } internal class SplitsRenderer { private static float SPLITS_WIDTH = 0.3f; private static float SPLITS_LINE_HEIGHT = 0.05f; private static float SPLITS_LEFT = -0.1f; private static float SPLITS_FONT_SIZE = 0.3f; public static void RenderLoadingWatermark(TimeSpan time) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) Dbg.Log("RenderLoadingWatermark"); BasicTrackingRig basicTrackingRig = LevelHooks.BasicTrackingRig; Transform val = ((basicTrackingRig != null) ? basicTrackingRig.head : null); if (!Object.op_Implicit((Object)(object)val)) { MelonLogger.Warning("Failed to render watermark in loading screen because could not find head position"); return; } TextMeshPro obj = CreateText(val, "Watermark"); ((TMP_Text)obj).alignment = (TextAlignmentOptions)260; ((TMP_Text)obj).fontSize = 0.5f; ((TMP_Text)obj).rectTransform.sizeDelta = new Vector2(0.8f, 0.8f); ((Transform)((TMP_Text)obj).rectTransform).localPosition = new Vector3(0f, 0f, 1f); ((Graphic)obj).color = new Color(0.4f, 0.4f, 0.6f); string value = ""; string value2 = DurationToString(time); string value3 = string.Join("\n", MelonTypeBase<MelonMod>.RegisteredMelons.Select((MelonMod mod) => ((MelonBase)mod).Info.Name)); ((TMP_Text)obj).SetText($"{"SpeedrunTimer"} v{"1.8.1"}{value}\n{value2}\n\nMods:\n{value3}", true); } public static void RenderSplits(Splits splits) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) BasicTrackingRig basicTrackingRig = LevelHooks.BasicTrackingRig; Transform val = ((basicTrackingRig != null) ? basicTrackingRig.head : null); if (!Object.op_Implicit((Object)(object)val)) { return; } GameObject val2 = new GameObject("SpeedrunTimer_Splits"); val2.transform.SetParent(((Component)val).transform); val2.transform.localPosition = new Vector3(-0.4f, 0.25f, 1f); Vector2 val4 = default(Vector2); for (int i = 0; i < splits.Items.Count; i++) { Split split = splits.Items[i]; float num = (float)i * (0f - SPLITS_LINE_HEIGHT); float num2 = 0f; if (split.Duration.HasValue) { TextMeshPro val3 = CreateText(val2.transform, "Splits_Time_" + split.Name); string text = ((split.Duration.Value >= TimeSpan.FromHours(1.0)) ? "h\\:mm\\:ss" : "m\\:ss\\.f"); ((TMP_Text)val3).SetText(split.Duration.Value.ToString(text), true); ((TMP_Text)val3).alignment = (TextAlignmentOptions)516; ((TMP_Text)val3).overflowMode = (TextOverflowModes)3; ((Transform)((TMP_Text)val3).rectTransform).localPosition = new Vector3(0f, 0f, 0f); RectTransform rectTransform = ((TMP_Text)val3).rectTransform; RectTransform rectTransform2 = ((TMP_Text)val3).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform2.anchorMax = val4; rectTransform.anchorMin = val4; ((TMP_Text)val3).rectTransform.offsetMin = new Vector2(SPLITS_LEFT, num); ((TMP_Text)val3).rectTransform.offsetMax = new Vector2(SPLITS_WIDTH + SPLITS_LEFT, num + SPLITS_LINE_HEIGHT); ((TMP_Text)val3).fontSize = SPLITS_FONT_SIZE; ((TMP_Text)val3).ForceMeshUpdate(false, false); num2 = ((TMP_Text)val3).preferredWidth; } TextMeshPro val5 = CreateText(val2.transform, "Splits_Name_" + split.Name); string text2 = Regex.Replace(split.Name, "^boneworks(?:_\\d+)?\\s+", "", RegexOptions.IgnoreCase); ((TMP_Text)val5).SetText(text2, true); ((TMP_Text)val5).alignment = (TextAlignmentOptions)513; ((TMP_Text)val5).overflowMode = (TextOverflowModes)3; ((Transform)((TMP_Text)val5).rectTransform).localPosition = new Vector3(0f, 0f, 0f); RectTransform rectTransform3 = ((TMP_Text)val5).rectTransform; RectTransform rectTransform4 = ((TMP_Text)val5).rectTransform; ((Vector2)(ref val4))..ctor(0f, 0.5f); rectTransform4.anchorMax = val4; rectTransform3.anchorMin = val4; ((TMP_Text)val5).rectTransform.offsetMin = new Vector2(SPLITS_LEFT, num); ((TMP_Text)val5).rectTransform.offsetMax = new Vector2(SPLITS_WIDTH + SPLITS_LEFT - num2, num + SPLITS_LINE_HEIGHT); ((TMP_Text)val5).fontSize = SPLITS_FONT_SIZE; } } public static string DurationToString(TimeSpan duration) { return duration.ToString(((duration.Hours >= 1) ? "h\\:m" : "") + "m\\:ss\\.ff"); } private static TextMeshPro CreateText(Transform parent, string name) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) TextMeshPro obj = new GameObject("SpeedrunTimer_" + name) { layer = LayerMask.NameToLayer("Background") }.AddComponent<TextMeshPro>(); obj.transform.SetParent(parent); obj.sortingOrder = 100; ((Graphic)obj).color = new Color(0.5f, 0.5f, 0.5f); return obj; } } public class WebsocketServer { public class Client { public TcpClient TcpClient; public List<byte> MessageBuffer = new List<byte>(); public BlockingCollection<byte[]> SendQueue = new BlockingCollection<byte[]>(); public void Send(string data) { Send(Encoding.UTF8.GetBytes(data), isString: true); } public void Send(byte[] data, bool isString = false) { SendQueue.Add(CreateMessageFrame(data, isString)); } } public TcpListener listener; private List<Client> connectedClients = new List<Client>(); public Action<Client> OnConnect; public Action<string, Client> OnMessage; public void Start(int port = 6161, string ip = null) { listener = new TcpListener((ip != null) ? IPAddress.Parse(ip) : IPAddress.Any, port); listener.Start(); Task.Run((Action)ListenForConnections); } public void Send(string data) { Send(Encoding.UTF8.GetBytes(data), isString: true); } public void Send(byte[] data, bool isString = false) { byte[] item = CreateMessageFrame(data, isString); Client[] array = connectedClients.ToArray(); foreach (Client client in array) { if (client.TcpClient.Connected) { client.SendQueue.Add(item); } else { connectedClients.Remove(client); } } } private void ListenForConnections() { while (true) { try { TcpClient tcpClient = listener.AcceptTcpClient(); MelonLogger.Msg("Websocket client connected"); Client client = new Client { TcpClient = tcpClient }; connectedClients.Add(client); Task.Run(delegate { HandleClient(client); }); Thread thread = new Thread((ThreadStart)delegate { ListenToSendQueue(client); }); thread.IsBackground = true; thread.Start(); } catch (Exception ex) { MelonLogger.Error("Error listening for Websocket connections:"); MelonLogger.Error((object)ex); Thread.Sleep(5000); } } } private void HandleClient(Client client) { byte[] array = new byte[1024]; try { NetworkStream stream = client.TcpClient.GetStream(); while (client.TcpClient.Connected) { int num = stream.Read(array, 0, array.Length); if (num == 0) { break; } byte[] array2 = new byte[num]; Array.Copy(array, array2, num); if (IsClientHandshake(array2)) { RespondToClientHandshake(array2, client); continue; } byte[] array3 = ReadMessage(array2, client); if (array3 != null) { string @string = Encoding.UTF8.GetString(array3); if (OnMessage != null) { OnMessage(@string, client); } client.MessageBuffer.Clear(); } } } catch (Exception ex) { MelonLogger.Warning("Error with websocket: {0}", new object[1] { ex }); } finally { CloseConnection(client); } } private void CloseConnection(Client client) { if (connectedClients.Contains(client)) { MelonLogger.Msg("Websocket disconnected"); client.SendQueue.CompleteAdding(); connectedClients.Remove(client); } else { client.TcpClient.Dispose(); } } private void ListenToSendQueue(Client client) { try { NetworkStream stream = client.TcpClient.GetStream(); foreach (byte[] item in client.SendQueue.GetConsumingEnumerable()) { if (client.TcpClient.Connected) { stream.Write(item, 0, item.Length); continue; } break; } } catch (Exception ex) { MelonLogger.Warning("Websocket error:"); MelonLogger.Warning((object)ex); } finally { CloseConnection(client); } } private bool IsClientHandshake(byte[] request) { return Regex.IsMatch(Encoding.UTF8.GetString(request, 0, 4), "^GET\\s", RegexOptions.IgnoreCase); } private void RespondToClientHandshake(byte[] request, Client client) { NetworkStream stream = client.TcpClient.GetStream(); string @string = Encoding.UTF8.GetString(request); byte[] bytes = Encoding.UTF8.GetBytes(string.Join("\r\n", "HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", "Upgrade: websocket", "Sec-WebSocket-Accept: " + GenerateWebsocketAcceptToken(@string), "", "")); stream.Write(bytes, 0, bytes.Length); OnConnect(client); } private string GenerateWebsocketAcceptToken(string request) { string s = Regex.Match(request, "\\n\\s*Sec-WebSocket-Key\\s*:(.*)", RegexOptions.IgnoreCase).Groups[1].Value.Trim() + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; return Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(s))); } private byte[] ReadMessage(byte[] request, Client client) { bool flag = (request[0] & 0x80) != 0; if ((request[1] & 0x80) == 0) { return null; } int num = 2; ulong num2 = (ulong)(int)(request[1] & 0x7Fu); switch (num2) { case 126uL: num2 = BitConverter.ToUInt16(new byte[2] { request[3], request[2] }, 0); num = 4; break; case 127uL: num2 = BitConverter.ToUInt64(new byte[8] { request[9], request[8], request[7], request[6], request[5], request[4], request[3], request[2] }, 0); num = 10; break; } byte[] array = new byte[num2]; int num3 = num; num += 4; for (ulong num4 = 0uL; num4 < num2; num4++) { array[num4] = (byte)(request[num + (int)num4] ^ request[num3 + ((int)num4 & 3)]); } client.MessageBuffer.AddRange(array); if (!flag) { return null; } return client.MessageBuffer.ToArray(); } private static byte[] CreateMessageFrame(byte[] data, bool isString) { byte[] array; if (data.Length < 126) { array = new byte[data.Length + 2]; array[1] = (byte)data.Length; } else if (data.Length <= 65535) { array = new byte[data.Length + 4]; array[1] = 126; byte[] bytes = BitConverter.GetBytes((ushort)data.Length); array[2] = bytes[1]; array[3] = bytes[0]; } else { array = new byte[data.Length + 10]; array[1] = 127; byte[] bytes2 = BitConverter.GetBytes((ulong)data.Length); for (int i = 0; i < 8; i++) { array[9 - i] = bytes2[i]; } } array[0] = (byte)(isString ? 129u : 130u); Array.Copy(data, 0, array, array.Length - data.Length, data.Length); return array; } } }