using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using HarmonyLib;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using YAPYAP;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("speedrunchrono")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("speedrunchrono")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("6cd78860-c1c2-4a61-a3a2-ce05ca1f4adf")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
[BepInPlugin("Xemotal.SpeedrunChrono", "Speedrun Chrono", "2.0.0")]
public class SpeedrunChrono : BaseUnityPlugin
{
public static SpeedrunChrono Instance;
private Harmony harmony;
private float timer = 0f;
private bool timerStarted = false;
private bool timerPaused = false;
private bool gameRunning = false;
private KeyCode pauseKey = (KeyCode)96;
private const string SavedKeyName = "Xemotal.SpeedrunChrono.PauseKey";
private bool waitingForKey = false;
private GameObject chronoCanvasObject;
private Canvas chronoCanvas;
private TextMeshProUGUI chronoText;
private GameObject messageCanvasObject;
private Canvas messageCanvas;
private TextMeshProUGUI messageText;
private float messageTimer = 0f;
private bool showSettings = false;
private GUIStyle windowStyle;
private GUIStyle titleStyle;
private GUIStyle normalStyle;
private GUIStyle buttonStyle;
private bool guiInitialized = false;
private void Awake()
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Expected O, but got Unknown
//IL_008f: Unknown result type (might be due to invalid IL or missing references)
Instance = this;
LoadPauseKey();
harmony = new Harmony("Xemotal.SpeedrunChrono");
try
{
harmony.PatchAll(typeof(SpeedrunChrono));
((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Harmony patches installed.");
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogError((object)("[SpeedrunChrono] Harmony error: " + ex));
}
((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Speedrun Chrono loaded.");
((BaseUnityPlugin)this).Logger.LogInfo((object)("[SpeedrunChrono] Pause key: " + GetReadableKeyName(pauseKey)));
}
private void OnDestroy()
{
try
{
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
catch
{
}
DestroyChronoHUD();
DestroyMessage();
Instance = null;
}
private void Update()
{
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
if (timerStarted && !timerPaused)
{
timer += Time.unscaledDeltaTime;
}
if (!waitingForKey && timerStarted && Input.GetKeyDown(pauseKey))
{
TogglePause();
}
if (!waitingForKey && Input.GetKeyDown((KeyCode)289))
{
showSettings = !showSettings;
}
if (messageTimer > 0f)
{
messageTimer -= Time.unscaledDeltaTime;
if (messageTimer <= 0f)
{
DestroyMessage();
}
}
UpdateChronoHUD();
}
private void TogglePause()
{
timerPaused = !timerPaused;
if (timerPaused)
{
((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Timer paused.");
}
else
{
((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Timer resumed.");
}
UpdateChronoHUD();
}
[HarmonyPatch(typeof(GameManager), "SvStartRound")]
[HarmonyPostfix]
private static void SvStartRoundPostfix()
{
if (!((Object)(object)Instance == (Object)null))
{
Instance.OnRoundStarted();
}
}
private void OnRoundStarted()
{
if (!gameRunning)
{
StartNewGame();
}
else
{
((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] New round - timer continues.");
}
timerStarted = true;
UpdateChronoHUD();
}
private void StartNewGame()
{
gameRunning = true;
timer = 0f;
timerPaused = false;
timerStarted = true;
CreateChronoHUD();
ShowStartMessage();
((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] New game detected.");
((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Timer started at 00:00.000.");
}
[HarmonyPatch(typeof(GameManager), "RestartGame")]
[HarmonyPostfix]
private static void RestartGamePostfix()
{
if (!((Object)(object)Instance == (Object)null))
{
Instance.PrepareNextGame();
}
}
private void PrepareNextGame()
{
gameRunning = false;
timerStarted = false;
timerPaused = false;
timer = 0f;
DestroyChronoHUD();
DestroyMessage();
((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Game ended. Waiting for next game.");
}
private void CreateChronoHUD()
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
//IL_0080: 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_00a2: Expected O, but got Unknown
//IL_011d: Unknown result type (might be due to invalid IL or missing references)
//IL_0133: Unknown result type (might be due to invalid IL or missing references)
//IL_0149: Unknown result type (might be due to invalid IL or missing references)
//IL_015f: Unknown result type (might be due to invalid IL or missing references)
//IL_0175: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)chronoCanvasObject != (Object)null))
{
chronoCanvasObject = new GameObject("SpeedrunChronoHUD");
Object.DontDestroyOnLoad((Object)(object)chronoCanvasObject);
chronoCanvas = chronoCanvasObject.AddComponent<Canvas>();
chronoCanvas.renderMode = (RenderMode)0;
chronoCanvas.sortingOrder = 500;
CanvasScaler val = chronoCanvasObject.AddComponent<CanvasScaler>();
val.uiScaleMode = (ScaleMode)1;
val.referenceResolution = new Vector2(1920f, 1080f);
chronoCanvasObject.AddComponent<GraphicRaycaster>();
GameObject val2 = new GameObject("ChronoText");
val2.transform.SetParent(chronoCanvasObject.transform, false);
chronoText = val2.AddComponent<TextMeshProUGUI>();
((TMP_Text)chronoText).text = "00:00.000";
((TMP_Text)chronoText).fontSize = 32f;
((TMP_Text)chronoText).alignment = (TextAlignmentOptions)514;
((TMP_Text)chronoText).textWrappingMode = (TextWrappingModes)0;
RectTransform component = ((Component)chronoText).GetComponent<RectTransform>();
component.anchorMin = new Vector2(0.5f, 1f);
component.anchorMax = new Vector2(0.5f, 1f);
component.pivot = new Vector2(0.5f, 1f);
component.anchoredPosition = new Vector2(0f, -35f);
component.sizeDelta = new Vector2(500f, 60f);
TryUseGameFont();
UpdateChronoHUD();
((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Chrono HUD created.");
}
}
private void TryUseGameFont()
{
if ((Object)(object)chronoText == (Object)null)
{
return;
}
try
{
PawnView val = Object.FindFirstObjectByType<PawnView>();
if (!((Object)(object)val == (Object)null) && (Object)(object)val.PlayerNameText != (Object)null && (Object)(object)val.PlayerNameText.font != (Object)null)
{
((TMP_Text)chronoText).font = val.PlayerNameText.font;
((BaseUnityPlugin)this).Logger.LogInfo((object)"[SpeedrunChrono] Game font applied.");
}
}
catch (Exception ex)
{
((BaseUnityPlugin)this).Logger.LogWarning((object)("[SpeedrunChrono] Could not get game font: " + ex.Message));
}
}
private void UpdateChronoHUD()
{
if (!((Object)(object)chronoText == (Object)null))
{
TimeSpan timeSpan = TimeSpan.FromSeconds(timer);
string text = $"{(int)timeSpan.TotalMinutes:00}:{timeSpan.Seconds:00}.{timeSpan.Milliseconds:000}";
if (timerPaused)
{
((TMP_Text)chronoText).text = text + " [PAUSED]";
}
else
{
((TMP_Text)chronoText).text = text;
}
}
}
private void DestroyChronoHUD()
{
if ((Object)(object)chronoCanvasObject != (Object)null)
{
Object.Destroy((Object)(object)chronoCanvasObject);
}
chronoCanvasObject = null;
chronoCanvas = null;
chronoText = null;
}
private void ShowStartMessage()
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
CreateMessage();
string readableKeyName = GetReadableKeyName(pauseKey);
((TMP_Text)messageText).text = "The key to pause/resume the timer is " + readableKeyName + ", and you can change it in the settings.";
messageTimer = 7f;
}
private void CreateMessage()
{
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0026: Expected O, but got Unknown
//IL_0080: 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_00a2: Expected O, but got Unknown
//IL_010c: Unknown result type (might be due to invalid IL or missing references)
//IL_0122: Unknown result type (might be due to invalid IL or missing references)
//IL_0138: Unknown result type (might be due to invalid IL or missing references)
//IL_014e: Unknown result type (might be due to invalid IL or missing references)
//IL_0164: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)messageCanvasObject != (Object)null))
{
messageCanvasObject = new GameObject("SpeedrunChronoMessage");
Object.DontDestroyOnLoad((Object)(object)messageCanvasObject);
messageCanvas = messageCanvasObject.AddComponent<Canvas>();
messageCanvas.renderMode = (RenderMode)0;
messageCanvas.sortingOrder = 501;
CanvasScaler val = messageCanvasObject.AddComponent<CanvasScaler>();
val.uiScaleMode = (ScaleMode)1;
val.referenceResolution = new Vector2(1920f, 1080f);
messageCanvasObject.AddComponent<GraphicRaycaster>();
GameObject val2 = new GameObject("MessageText");
val2.transform.SetParent(messageCanvasObject.transform, false);
messageText = val2.AddComponent<TextMeshProUGUI>();
((TMP_Text)messageText).fontSize = 28f;
((TMP_Text)messageText).alignment = (TextAlignmentOptions)514;
((TMP_Text)messageText).textWrappingMode = (TextWrappingModes)1;
RectTransform component = ((Component)messageText).GetComponent<RectTransform>();
component.anchorMin = new Vector2(0.5f, 0.5f);
component.anchorMax = new Vector2(0.5f, 0.5f);
component.pivot = new Vector2(0.5f, 0.5f);
component.anchoredPosition = new Vector2(0f, -100f);
component.sizeDelta = new Vector2(1200f, 160f);
TryUseMessageFont();
}
}
private void TryUseMessageFont()
{
if ((Object)(object)messageText == (Object)null)
{
return;
}
try
{
PawnView val = Object.FindFirstObjectByType<PawnView>();
if (!((Object)(object)val == (Object)null) && (Object)(object)val.PlayerNameText != (Object)null && (Object)(object)val.PlayerNameText.font != (Object)null)
{
((TMP_Text)messageText).font = val.PlayerNameText.font;
}
}
catch
{
}
}
private void DestroyMessage()
{
if ((Object)(object)messageCanvasObject != (Object)null)
{
Object.Destroy((Object)(object)messageCanvasObject);
}
messageCanvasObject = null;
messageCanvas = null;
messageText = null;
messageTimer = 0f;
}
private void LoadPauseKey()
{
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
string value = PlayerPrefs.GetString("Xemotal.SpeedrunChrono.PauseKey", "");
if (string.IsNullOrEmpty(value))
{
pauseKey = (KeyCode)96;
return;
}
try
{
pauseKey = (KeyCode)Enum.Parse(typeof(KeyCode), value);
}
catch
{
pauseKey = (KeyCode)96;
}
}
private void SavePauseKey()
{
//IL_0030: Unknown result type (might be due to invalid IL or missing references)
PlayerPrefs.SetString("Xemotal.SpeedrunChrono.PauseKey", ((object)Unsafe.As<KeyCode, KeyCode>(ref pauseKey)/*cast due to .constrained prefix*/).ToString());
PlayerPrefs.Save();
((BaseUnityPlugin)this).Logger.LogInfo((object)("[SpeedrunChrono] Key saved: " + GetReadableKeyName(pauseKey)));
}
private unsafe string GetReadableKeyName(KeyCode key)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0004: Invalid comparison between Unknown and I4
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_001c: Invalid comparison between Unknown and I4
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Invalid comparison between Unknown and I4
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Invalid comparison between Unknown and I4
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: Expected I4, but got Unknown
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Invalid comparison between Unknown and I4
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_0077: Expected I4, but got Unknown
if ((int)key == 96)
{
return "²";
}
KeyCode val = key;
KeyCode val2 = val;
if ((int)val2 <= 13)
{
if ((int)val2 == 8)
{
return "Backspace";
}
if ((int)val2 == 13)
{
return "Enter";
}
}
else
{
if ((int)val2 == 32)
{
return "Space";
}
switch (val2 - 273)
{
case 0:
return "Up Arrow";
case 1:
return "Down Arrow";
case 3:
return "Left Arrow";
case 2:
return "Right Arrow";
}
switch (val2 - 303)
{
case 1:
return "Left Shift";
case 0:
return "Right Shift";
case 3:
return "Left Ctrl";
case 2:
return "Right Ctrl";
case 5:
return "Left Alt";
case 4:
return "Right Alt";
}
}
return ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString();
}
private void InitGUI()
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Expected O, but got Unknown
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
//IL_0041: Expected O, but got Unknown
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Expected O, but got Unknown
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Expected O, but got Unknown
if (!guiInitialized)
{
guiInitialized = true;
windowStyle = new GUIStyle(GUI.skin.window);
titleStyle = new GUIStyle(GUI.skin.label);
titleStyle.fontSize = 22;
titleStyle.alignment = (TextAnchor)4;
normalStyle = new GUIStyle(GUI.skin.label);
normalStyle.fontSize = 17;
buttonStyle = new GUIStyle(GUI.skin.button);
buttonStyle.fontSize = 17;
}
}
private void OnGUI()
{
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_0072: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Expected O, but got Unknown
//IL_007c: Unknown result type (might be due to invalid IL or missing references)
InitGUI();
if (waitingForKey)
{
HandleKeyCapture();
DrawKeyCaptureWindow();
}
else if (showSettings)
{
float num = 520f;
float num2 = 330f;
Rect val = default(Rect);
((Rect)(ref val))..ctor(((float)Screen.width - num) / 2f, ((float)Screen.height - num2) / 2f, num, num2);
GUI.ModalWindow(987654, val, new WindowFunction(DrawSettingsWindow), "Speedrun Chrono");
}
}
private void HandleKeyCapture()
{
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Invalid comparison between Unknown and I4
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Invalid comparison between Unknown and I4
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
//IL_003c: Invalid comparison between Unknown and I4
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
Event current = Event.current;
if (current != null && (int)current.type == 4)
{
KeyCode keyCode = current.keyCode;
if ((int)keyCode != 0 && (int)keyCode != 289)
{
pauseKey = keyCode;
waitingForKey = false;
SavePauseKey();
current.Use();
}
}
}
private void DrawKeyCaptureWindow()
{
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
//IL_010e: Unknown result type (might be due to invalid IL or missing references)
//IL_0114: Invalid comparison between Unknown and I4
//IL_0117: Unknown result type (might be due to invalid IL or missing references)
//IL_011e: Invalid comparison between Unknown and I4
float num = 500f;
float num2 = 190f;
Rect val = default(Rect);
((Rect)(ref val))..ctor(((float)Screen.width - num) / 2f, ((float)Screen.height - num2) / 2f, num, num2);
GUI.Box(val, "Speedrun Chrono");
GUI.Label(new Rect(((Rect)(ref val)).x + 30f, ((Rect)(ref val)).y + 55f, ((Rect)(ref val)).width - 60f, 35f), "Press a key...", titleStyle);
GUI.Label(new Rect(((Rect)(ref val)).x + 30f, ((Rect)(ref val)).y + 95f, ((Rect)(ref val)).width - 60f, 25f), "Press Escape to cancel.", normalStyle);
if (GUI.Button(new Rect(((Rect)(ref val)).x + 150f, ((Rect)(ref val)).y + 135f, 200f, 35f), "Cancel"))
{
waitingForKey = false;
}
Event current = Event.current;
if (current != null && (int)current.type == 4 && (int)current.keyCode == 27)
{
waitingForKey = false;
current.Use();
}
}
private void DrawSettingsWindow(int windowID)
{
//IL_0055: Unknown result type (might be due to invalid IL or missing references)
//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
//IL_0163: Unknown result type (might be due to invalid IL or missing references)
GUILayout.Space(10f);
GUILayout.Label("Speedrun Chrono", titleStyle, Array.Empty<GUILayoutOption>());
GUILayout.Space(15f);
GUILayout.Label("Pause / Resume Timer", normalStyle, Array.Empty<GUILayoutOption>());
GUILayout.Space(5f);
GUILayout.Label("Current key: " + GetReadableKeyName(pauseKey), normalStyle, Array.Empty<GUILayoutOption>());
GUILayout.Space(10f);
if (GUILayout.Button("Change Key", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(42f) }))
{
waitingForKey = true;
}
GUILayout.Space(10f);
if (GUILayout.Button("Reset to ²", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(42f) }))
{
pauseKey = (KeyCode)96;
SavePauseKey();
}
GUILayout.Space(15f);
GUILayout.Label("F8: Open / close this menu", normalStyle, Array.Empty<GUILayoutOption>());
GUILayout.Space(10f);
if (GUILayout.Button("Close", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) }))
{
showSettings = false;
}
GUI.DragWindow(new Rect(0f, 0f, 10000f, 25f));
}
}