using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ValheimCursorLockFix")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ValheimCursorLockFix")] [assembly: AssemblyTitle("ValheimCursorLockFix")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ValheimCursorLockFix { [BepInPlugin("ua.gyrocopter.valheim.cursorlockfix", "ValheimCursorLockFix", "1.0.2")] public sealed class ValheimCursorLockFixPlugin : BaseUnityPlugin { public const string PluginGuid = "ua.gyrocopter.valheim.cursorlockfix"; public const string PluginName = "ValheimCursorLockFix"; public const string PluginVersion = "1.0.2"; private ConfigEntry _enableFix; private ConfigEntry _forceLockInGameplay; private ConfigEntry _unlockInMenus; private ConfigEntry _toggleKey; private ConfigEntry _debugLogging; private bool _lastShouldLock; private bool _hasAppliedCursorState; private string _lastReason; private static readonly Dictionary TypeCache = new Dictionary(); private void Awake() { //IL_007e: Unknown result type (might be due to invalid IL or missing references) _enableFix = ((BaseUnityPlugin)this).Config.Bind("General", "EnableFix", true, "Enable or disable the cursor lock fix."); _forceLockInGameplay = ((BaseUnityPlugin)this).Config.Bind("General", "ForceLockInGameplay", true, "Force Unity cursor lock while gameplay is active."); _unlockInMenus = ((BaseUnityPlugin)this).Config.Bind("General", "UnlockInMenus", true, "Force cursor unlock while menus, inventory, chat, console, build menu, map, or text input are active."); _toggleKey = ((BaseUnityPlugin)this).Config.Bind("General", "ToggleKey", new KeyboardShortcut((KeyCode)289, Array.Empty()), "Keyboard shortcut that toggles EnableFix during play."); _debugLogging = ((BaseUnityPlugin)this).Config.Bind("General", "DebugLogging", false, "Enable verbose debug logging."); DebugLog("Plugin 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"); } } if (!_enableFix.Value) { return; } if (ShouldLockCursor(out var reason)) { if (_forceLockInGameplay.Value) { ApplyLock(reason); } } else if (_unlockInMenus.Value) { ApplyUnlock(reason); } } 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 (IsMenuVisibleSafe()) { reason = "menu visible"; return false; } if (IsInventoryVisibleSafe()) { reason = "inventory visible"; return false; } if (IsBuildMenuVisibleSafe()) { reason = "build menu visible"; return false; } if (IsMapVisibleSafe()) { reason = "map visible"; return false; } if (IsStoreVisibleSafe()) { reason = "store visible"; return false; } if (IsTextViewerVisibleSafe()) { reason = "text viewer visible"; return false; } if (IsTextInputVisibleSafe()) { reason = "text input visible"; return false; } if (IsChatFocusedSafe()) { reason = "chat focused"; return false; } if (IsConsoleVisibleSafe()) { reason = "console visible"; return false; } reason = "gameplay"; return true; } private void ApplyLock(string reason) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)Cursor.lockState != 1) { Cursor.lockState = (CursorLockMode)1; } if (Cursor.visible) { Cursor.visible = false; } LogCursorStateChanged(shouldLock: true, reason); } private void ApplyUnlock(string reason) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if ((int)Cursor.lockState != 0) { Cursor.lockState = (CursorLockMode)0; } 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 IsMenuVisibleSafe() { return InvokeStaticBool(FindType("Menu"), "IsVisible"); } private bool IsInventoryVisibleSafe() { return InvokeInstanceBool(GetStaticMemberValue(FindType("InventoryGui"), "instance"), "IsVisible"); } private bool IsTextInputVisibleSafe() { return InvokeStaticBool(FindType("TextInput"), "IsVisible"); } private bool IsBuildMenuVisibleSafe() { object staticMemberValue = GetStaticMemberValue(FindType("Hud"), "instance"); if (InvokeInstanceBool(staticMemberValue, "IsPieceSelectionVisible") || InvokeInstanceBool(staticMemberValue, "IsBuildMenuVisible") || InvokeInstanceBool(staticMemberValue, "IsBuildSelectionVisible")) { return true; } return IsAnyInstanceMemberActive(staticMemberValue, "m_pieceSelectionWindow", "m_pieceSelection", "m_buildSelection", "m_buildMenu", "m_pieceListRoot"); } private bool IsMapVisibleSafe() { object staticMemberValue = GetStaticMemberValue(FindType("Minimap"), "instance"); if (!InvokeInstanceBool(staticMemberValue, "IsOpen") && !InvokeInstanceBool(staticMemberValue, "IsVisible")) { return IsAnyInstanceMemberActive(staticMemberValue, "m_largeRoot", "m_mapRoot"); } return true; } private bool IsStoreVisibleSafe() { object staticMemberValue = GetStaticMemberValue(FindType("StoreGui"), "instance"); if (!InvokeInstanceBool(staticMemberValue, "IsVisible")) { return IsAnyInstanceMemberActive(staticMemberValue, "m_rootPanel", "m_panel"); } return true; } private bool IsTextViewerVisibleSafe() { object staticMemberValue = GetStaticMemberValue(FindType("TextViewer"), "instance"); if (!InvokeInstanceBool(staticMemberValue, "IsVisible")) { return IsAnyInstanceMemberActive(staticMemberValue, "m_root", "m_texts"); } return true; } private bool IsChatFocusedSafe() { return InvokeInstanceBool(GetStaticMemberValue(FindType("Chat"), "instance"), "HasFocus"); } private bool IsConsoleVisibleSafe() { return InvokeStaticBool(FindType("Console"), "IsVisible"); } 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 IsAnyInstanceMemberActive(object instance, params string[] memberNames) { if (instance == null || memberNames == null) { return false; } for (int i = 0; i < memberNames.Length; i++) { if (IsUnityObjectActive(GetInstanceMemberValue(instance, memberNames[i]))) { return true; } } return false; } 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); } } } }