Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ValheimCursorLockFix v1.0.7
plugins\ValheimCursorLockFix.dll
Decompiled 3 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading; using BepInEx; using BepInEx.Configuration; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ValheimCursorLockFix")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.7.0")] [assembly: AssemblyInformationalVersion("1.0.7")] [assembly: AssemblyProduct("ValheimCursorLockFix")] [assembly: AssemblyTitle("ValheimCursorLockFix")] [assembly: AssemblyVersion("1.0.7.0")] namespace ValheimCursorLockFix; [BepInPlugin("ua.gyrocopter.valheim.cursorlockfix", "ValheimCursorLockFix", "1.0.7")] [DefaultExecutionOrder(10000)] public sealed class ValheimCursorLockFixPlugin : BaseUnityPlugin { private struct RECT { public int Left; public int Top; public int Right; public int Bottom; } private struct POINT { public int X; public int Y; } public const string PluginGuid = "ua.gyrocopter.valheim.cursorlockfix"; public const string PluginName = "ValheimCursorLockFix"; public const string PluginVersion = "1.0.7"; private const float ChatStickySeconds = 1f; private ConfigEntry<bool> _enableFix; private ConfigEntry<bool> _forceLockInGameplay; private ConfigEntry<bool> _unlockInMenus; private ConfigEntry<bool> _hardConfine; private ConfigEntry<bool> _centerWhenHidden; private ConfigEntry<int> _edgePadding; private ConfigEntry<int> _pollIntervalMs; private ConfigEntry<KeyboardShortcut> _toggleKey; private ConfigEntry<bool> _debugLogging; private bool _lastShouldLock; private bool _hasAppliedCursorState; private string _lastReason; private float _chatStickyUntil; private IntPtr _windowHandle = IntPtr.Zero; private readonly object _stateLock = new object(); private RECT _targetRect; private bool _targetValid; private bool _shouldCenter; private volatile bool _running; private Thread _enforcementThread; private bool _clipActive; private static readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>(); private void Awake() { //IL_0102: Unknown result type (might be due to invalid IL or missing references) _enableFix = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableFix", true, "Enable or disable the cursor lock fix."); _forceLockInGameplay = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ForceLockInGameplay", true, "Force Unity cursor lock while gameplay is active."); _unlockInMenus = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "UnlockInMenus", true, "Force cursor unlock while menus / chat / map / etc. are active."); _hardConfine = ((BaseUnityPlugin)this).Config.Bind<bool>("HardConfine", "Enabled", true, "Use Win32 ClipCursor so the OS cursor cannot leave the game window."); _centerWhenHidden = ((BaseUnityPlugin)this).Config.Bind<bool>("HardConfine", "CenterWhenHidden", true, "While the cursor is hidden in gameplay, pin it to the center of the game window."); _edgePadding = ((BaseUnityPlugin)this).Config.Bind<int>("HardConfine", "EdgePadding", 0, "Shrink the clip rectangle by this many pixels on each side."); _pollIntervalMs = ((BaseUnityPlugin)this).Config.Bind<int>("HardConfine", "PollIntervalMs", 4, "How often the background thread re-asserts ClipCursor (1-100 ms)."); _toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "ToggleKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Keyboard shortcut that toggles EnableFix during play."); _debugLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", false, "Enable verbose debug logging."); _running = true; _enforcementThread = new Thread(EnforcementLoop) { IsBackground = true, Name = "ValheimCursorLockFix.Clip", Priority = ThreadPriority.AboveNormal }; _enforcementThread.Start(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"ValheimCursorLockFix 1.0.7 loaded."); } private void Update() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = _toggleKey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { _enableFix.Value = !_enableFix.Value; ((BaseUnityPlugin)this).Config.Save(); DebugLog("EnableFix toggled to " + _enableFix.Value + "."); if (!_enableFix.Value) { ApplyUnlock("fix disabled"); PublishClip(valid: false, center: false, default(RECT)); } } } private void LateUpdate() { object chatInput; string reason; if (!_enableFix.Value) { PublishClip(valid: false, center: false, default(RECT)); } else if (TryGetChatSession(out chatInput)) { ApplyChatCursorMode("chat"); PublishHardConfine(center: true); RestoreChatFocus(chatInput); } else if (ShouldLockCursor(out reason)) { if (_forceLockInGameplay.Value) { ApplyLock(reason); } PublishHardConfine(center: true); } else { if (_unlockInMenus.Value) { ApplyUnlock(reason); } PublishClip(valid: false, center: false, default(RECT)); } } private void PublishHardConfine(bool center) { if (!_hardConfine.Value || !Application.isFocused) { PublishClip(valid: false, center: false, default(RECT)); return; } IntPtr intPtr = ResolveWindowHandle(); if (intPtr == IntPtr.Zero || GetForegroundWindow() != intPtr) { PublishClip(valid: false, center: false, default(RECT)); } else { if (!GetClientRect(intPtr, out var lpRect)) { return; } POINT lpPoint = new POINT { X = lpRect.Left, Y = lpRect.Top }; POINT lpPoint2 = new POINT { X = lpRect.Right, Y = lpRect.Bottom }; if (ClientToScreen(intPtr, ref lpPoint) && ClientToScreen(intPtr, ref lpPoint2)) { int num = Mathf.Max(0, _edgePadding.Value); RECT rect = new RECT { Left = lpPoint.X + num, Top = lpPoint.Y + num, Right = lpPoint2.X - num, Bottom = lpPoint2.Y - num }; if (rect.Right > rect.Left && rect.Bottom > rect.Top) { bool center2 = center && _centerWhenHidden.Value; PublishClip(valid: true, center2, rect); } } } } private void PublishClip(bool valid, bool center, RECT rect) { lock (_stateLock) { _targetValid = valid; _shouldCenter = center; _targetRect = rect; } } private void EnforcementLoop() { while (_running) { bool targetValid; bool shouldCenter; RECT lpRect; lock (_stateLock) { targetValid = _targetValid; shouldCenter = _shouldCenter; lpRect = _targetRect; } int num = 4; try { if (targetValid) { ClipCursor(ref lpRect); _clipActive = true; POINT lpPoint; if (shouldCenter) { SetCursorPos((lpRect.Left + lpRect.Right) / 2, (lpRect.Top + lpRect.Bottom) / 2); } else if (GetCursorPos(out lpPoint) && !InsideRect(lpRect, lpPoint)) { SetCursorPos(Clamp(lpPoint.X, lpRect.Left, lpRect.Right - 1), Clamp(lpPoint.Y, lpRect.Top, lpRect.Bottom - 1)); } } else if (_clipActive) { ClipCursor(IntPtr.Zero); _clipActive = false; } num = ((_pollIntervalMs == null) ? 4 : Mathf.Clamp(_pollIntervalMs.Value, 1, 100)); } catch { } Thread.Sleep(targetValid ? num : 32); } if (_clipActive) { ClipCursor(IntPtr.Zero); _clipActive = false; } } private bool ShouldLockCursor(out string reason) { if (!Application.isFocused) { reason = "application not focused"; return false; } if (!HasZInputInstance()) { reason = "ZInput unavailable"; return false; } if (!HasLocalPlayer()) { reason = "local player unavailable"; return false; } if (InvokeStaticBool(FindType("Hud"), "InRadial")) { reason = "radial menu"; return false; } if (InvokeStaticBool(FindType("Menu"), "IsActive")) { reason = "menu active"; return false; } if (InvokeStaticBool(FindType("InventoryGui"), "IsVisible")) { reason = "inventory visible"; return false; } if (InvokeStaticBool(FindType("Hud"), "IsPieceSelectionVisible")) { reason = "build menu visible"; return false; } if (InvokeStaticBool(FindType("Minimap"), "IsOpen")) { reason = "map open"; return false; } if (InvokeStaticBool(FindType("StoreGui"), "IsVisible")) { reason = "store visible"; return false; } if (InvokeStaticBool(FindType("TextInput"), "IsVisible")) { reason = "text input visible"; return false; } if (IsTextViewerVisibleSafe()) { reason = "text viewer visible"; return false; } if (InvokeStaticBool(FindType("Console"), "IsVisible")) { reason = "console visible"; return false; } if (InvokeStaticBool(FindType("UnifiedPopup"), "IsVisible")) { reason = "popup visible"; return false; } if (IsPasswordDialogShowingSafe()) { reason = "password dialog"; return false; } reason = "gameplay"; return true; } private void ApplyChatCursorMode(string reason) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) TrySetZCursorLock((CursorLockMode)0); if ((int)Cursor.lockState != 0) { Cursor.lockState = (CursorLockMode)0; } TryZCursorHide(); if (Cursor.visible) { Cursor.visible = false; } LogCursorStateChanged(shouldLock: true, reason); } private void ApplyLock(string reason) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 TrySetZCursorLock((CursorLockMode)1); if ((int)Cursor.lockState != 1) { Cursor.lockState = (CursorLockMode)1; } TryZCursorHide(); if (Cursor.visible) { Cursor.visible = false; } LogCursorStateChanged(shouldLock: true, reason); } private void ApplyUnlock(string reason) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) TrySetZCursorLock((CursorLockMode)0); if ((int)Cursor.lockState != 0) { Cursor.lockState = (CursorLockMode)0; } TryZCursorShow(); if (!Cursor.visible) { Cursor.visible = true; } LogCursorStateChanged(shouldLock: false, reason); } private void LogCursorStateChanged(bool shouldLock, string reason) { if (!_hasAppliedCursorState || _lastShouldLock != shouldLock || !string.Equals(_lastReason, reason, StringComparison.Ordinal)) { _hasAppliedCursorState = true; _lastShouldLock = shouldLock; _lastReason = reason; DebugLog((shouldLock ? "Locked" : "Unlocked") + " cursor: " + reason + "."); } } private bool HasZInputInstance() { return GetStaticMemberValue(FindType("ZInput"), "instance") != null; } private bool HasLocalPlayer() { return GetStaticMemberValue(FindType("Player"), "m_localPlayer") != null; } private bool IsTextViewerVisibleSafe() { return InvokeInstanceBool(GetStaticMemberValue(FindType("TextViewer"), "instance"), "IsVisible"); } private bool IsPasswordDialogShowingSafe() { object staticMemberValue = GetStaticMemberValue(FindType("ZNet"), "instance"); if (!InvokeInstanceBool(staticMemberValue, "IsPasswordDialogShowing")) { return InvokeInstanceBool(staticMemberValue, "InPasswordDialog"); } return true; } private bool TryGetChatSession(out object chatInput) { chatInput = null; object staticMemberValue = GetStaticMemberValue(FindType("Chat"), "instance"); if (staticMemberValue == null) { return Time.unscaledTime < _chatStickyUntil; } chatInput = GetInstanceMemberValue(staticMemberValue, "m_input"); bool num = InvokeInstanceBool(staticMemberValue, "HasFocus"); bool result = default(bool); bool flag = chatInput != null && TryGetBoolProperty(chatInput, "isFocused", out result) && result; bool flag2 = IsUnityObjectActive(chatInput); bool instanceBoolField = GetInstanceBoolField(staticMemberValue, "m_wasFocused"); if (num || flag) { _chatStickyUntil = Time.unscaledTime + 1f; return true; } if (flag2 && (instanceBoolField || Time.unscaledTime < _chatStickyUntil)) { _chatStickyUntil = Time.unscaledTime + 1f; return true; } return false; } private void RestoreChatFocus(object chatInput) { if (chatInput == null || !IsUnityObjectActive(chatInput) || (TryGetBoolProperty(chatInput, "isFocused", out var result) && result)) { return; } try { Type type = chatInput.GetType(); MethodInfo method = type.GetMethod("ActivateInputField", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); MethodInfo? method2 = type.GetMethod("Select", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); method?.Invoke(chatInput, null); method2?.Invoke(chatInput, null); PropertyInfo property = type.GetProperty("text", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = ((property != null) ? (property.GetValue(chatInput, null) as string) : null)?.Length ?? 0; PropertyInfo property2 = type.GetProperty("caretPosition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); PropertyInfo property3 = type.GetProperty("selectionAnchorPosition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); PropertyInfo? property4 = type.GetProperty("selectionFocusPosition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); property2?.SetValue(chatInput, num, null); property3?.SetValue(chatInput, num, null); property4?.SetValue(chatInput, num, null); DebugLog("Restored chat input focus after blip."); } catch { } } private void OnApplicationFocus(bool hasFocus) { if (!hasFocus) { _windowHandle = IntPtr.Zero; PublishClip(valid: false, center: false, default(RECT)); } } private void OnDestroy() { Shutdown(); } private void OnApplicationQuit() { Shutdown(); } private void Shutdown() { PublishClip(valid: false, center: false, default(RECT)); _running = false; if (_enforcementThread != null) { _enforcementThread.Join(250); _enforcementThread = null; } ClipCursor(IntPtr.Zero); _clipActive = false; } private IntPtr ResolveWindowHandle() { if (_windowHandle != IntPtr.Zero && IsWindow(_windowHandle)) { return _windowHandle; } IntPtr activeWindow = GetActiveWindow(); _windowHandle = ((activeWindow != IntPtr.Zero && IsWindow(activeWindow)) ? activeWindow : IntPtr.Zero); return _windowHandle; } private static bool InsideRect(RECT r, POINT p) { if (p.X >= r.Left && p.X < r.Right && p.Y >= r.Top) { return p.Y < r.Bottom; } return false; } private static int Clamp(int value, int min, int max) { if (max < min) { return min; } if (value >= min) { if (value <= max) { return value; } return max; } return min; } private static bool TrySetZCursorLock(CursorLockMode mode) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) Type type = FindType("ZCursor"); if (type == null) { return false; } try { PropertyInfo property = type.GetProperty("LockState", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property == null) { return false; } property.SetValue(null, mode, null); return true; } catch { return false; } } private static bool TryZCursorHide() { Type type = FindType("ZCursor"); if (type == null) { return false; } try { MethodInfo method = type.GetMethod("Hide", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method == null) { return false; } method.Invoke(null, null); return true; } catch { return false; } } private static bool TryZCursorShow() { Type type = FindType("ZCursor"); if (type == null) { return false; } try { MethodInfo method = type.GetMethod("Show", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method == null) { return false; } method.Invoke(null, null); return true; } catch { return false; } } private static Type FindType(string typeName) { if (TypeCache.TryGetValue(typeName, out var value)) { return value; } Type type = Type.GetType(typeName); if (type != null) { TypeCache[typeName] = type; return type; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; i++) { Type type2 = assemblies[i].GetType(typeName); if (type2 != null) { TypeCache[typeName] = type2; return type2; } } TypeCache[typeName] = null; return null; } private static object GetStaticMemberValue(Type type, string memberName) { if (type == null) { return null; } try { FieldInfo field = type.GetField(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(null); } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); return (property != null) ? property.GetValue(null, null) : null; } catch { return null; } } private static object GetInstanceMemberValue(object instance, string memberName) { if (instance == null) { return null; } try { Type type = instance.GetType(); FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(instance); } PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); return (property != null) ? property.GetValue(instance, null) : null; } catch { return null; } } private static bool GetInstanceBoolField(object instance, string memberName) { object instanceMemberValue = GetInstanceMemberValue(instance, memberName); bool flag = default(bool); int num; if (instanceMemberValue is bool) { flag = (bool)instanceMemberValue; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private static bool IsUnityObjectActive(object value) { if (value == null) { return false; } if (TryGetBoolProperty(value, "activeInHierarchy", out var result) || TryGetBoolProperty(value, "activeSelf", out result) || TryGetNestedGameObjectActive(value, out result)) { return result; } return false; } private static bool TryGetNestedGameObjectActive(object value, out bool active) { active = false; try { PropertyInfo property = value.GetType().GetProperty("gameObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object value2 = ((property != null) ? property.GetValue(value, null) : null); return TryGetBoolProperty(value2, "activeInHierarchy", out active) || TryGetBoolProperty(value2, "activeSelf", out active); } catch { return false; } } private static bool TryGetBoolProperty(object value, string propertyName, out bool result) { result = false; if (value == null) { return false; } try { PropertyInfo property = value.GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property == null || property.PropertyType != typeof(bool)) { return false; } result = (bool)property.GetValue(value, null); return true; } catch { return false; } } private static bool InvokeStaticBool(Type type, string methodName) { if (type == null) { return false; } MethodInfo method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method == null || method.ReturnType != typeof(bool)) { return false; } try { return (bool)method.Invoke(null, null); } catch { return false; } } private static bool InvokeInstanceBool(object instance, string methodName) { if (instance == null) { return false; } MethodInfo method = instance.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if (method == null || method.ReturnType != typeof(bool)) { return false; } try { return (bool)method.Invoke(instance, null); } catch { return false; } } private void DebugLog(string message) { if (_debugLogging != null && _debugLogging.Value) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } } [DllImport("user32.dll", SetLastError = true)] private static extern bool ClipCursor(ref RECT lpRect); [DllImport("user32.dll", SetLastError = true)] private static extern bool ClipCursor(IntPtr lpRect); [DllImport("user32.dll", SetLastError = true)] private static extern bool SetCursorPos(int x, int y); [DllImport("user32.dll", SetLastError = true)] private static extern bool GetCursorPos(out POINT lpPoint); [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] private static extern IntPtr GetActiveWindow(); [DllImport("user32.dll")] private static extern bool IsWindow(IntPtr hWnd); [DllImport("user32.dll", SetLastError = true)] private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll", SetLastError = true)] private static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint); }