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 WiresNativeCursorSupport v0.3.2
WiresNativeCursorSupport.dll
Decompiled 2 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.UI; using Valheim.SettingsGui; [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("WiresNativeCursorSupport")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("WiresNativeCursorSupport")] [assembly: AssemblyTitle("WiresNativeCursorSupport")] [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 WiresNativeCursorSupport { internal static class AccessibilityIntegration { private static readonly FieldInfo ToggleBlockField = AccessTools.Field(typeof(AccessibilitySettings), "m_toggleBlockToggle"); private static AccessibilitySettings _owner; private static Toggle _toggle; private static bool _openedValue; internal static void Initialize(AccessibilitySettings settings) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown if ((Object)(object)settings == (Object)null || ToggleBlockField == null) { return; } object? value = ToggleBlockField.GetValue(settings); Toggle val = (Toggle)((value is Toggle) ? value : null); if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).transform.parent == (Object)null) { return; } if ((Object)(object)_owner != (Object)(object)settings || (Object)(object)_toggle == (Object)null) { _owner = settings; _toggle = FindOrCreateToggle(val); } if (!((Object)(object)_toggle == (Object)null)) { _openedValue = Plugin.RuntimeEnabled; _toggle.SetIsOnWithoutNotify(_openedValue); _toggle.onValueChanged = new ToggleEvent(); ((UnityEvent<bool>)(object)_toggle.onValueChanged).AddListener((UnityAction<bool>)delegate(bool runtimeEnabled) { Plugin.SetRuntimeEnabled(runtimeEnabled); }); SetLabel(_toggle); } } private static Toggle FindOrCreateToggle(Toggle source) { Transform parent = ((Component)source).transform.parent; Transform val = parent.Find("WireNativeCursor"); if ((Object)(object)val != (Object)null) { return ((Component)val).GetComponent<Toggle>(); } GameObject obj = Object.Instantiate<GameObject>(((Component)source).gameObject, parent, false); ((Object)obj).name = "WireNativeCursor"; obj.transform.SetSiblingIndex(((Component)source).transform.GetSiblingIndex() + 1); Toggle component = obj.GetComponent<Toggle>(); if ((Object)(object)component != (Object)null) { SetLabel(component); } return component; } private static void SetLabel(Toggle toggle) { if (!((Object)(object)toggle == (Object)null)) { Transform obj = ((Component)toggle).transform.Find("Label"); TMP_Text val = ((obj != null) ? ((Component)obj).GetComponent<TMP_Text>() : null); if ((Object)(object)val == (Object)null) { val = ((Component)toggle).GetComponentInChildren<TMP_Text>(true); } if ((Object)(object)val != (Object)null) { val.text = "Native System Cursor"; } } } internal static void Save() { if (!((Object)(object)_toggle == (Object)null)) { bool isOn = _toggle.isOn; PlayerPrefs.SetInt("WireNativeCursorSupport", isOn ? 1 : 0); PlayerPrefs.Save(); _openedValue = isOn; Plugin.SetRuntimeEnabled(isOn); } } internal static void Cancel() { if ((Object)(object)_toggle != (Object)null) { _toggle.SetIsOnWithoutNotify(_openedValue); } Plugin.SetRuntimeEnabled(_openedValue); } internal static void ConfigureNavigation(AccessibilitySettings settings, Button okButton) { if (!((Object)(object)settings == (Object)null) && !((Object)(object)okButton == (Object)null) && !((Object)(object)_toggle == (Object)null) && !(ToggleBlockField == null)) { object? value = ToggleBlockField.GetValue(settings); Toggle val = (Toggle)((value is Toggle) ? value : null); if (!((Object)(object)val == (Object)null)) { GuiUtils.SetNavigationDown((Selectable)(object)val, (Selectable)(object)_toggle); GuiUtils.SetNavigationUp((Selectable)(object)_toggle, (Selectable)(object)val); GuiUtils.SetNavigationDown((Selectable)(object)_toggle, (Selectable)(object)okButton); GuiUtils.SetNavigationUp((Selectable)(object)okButton, (Selectable)(object)_toggle); } } } } [HarmonyPatch(typeof(AccessibilitySettings), "Initialize")] internal static class AccessibilityInitializePatch { [HarmonyPostfix] private static void Postfix(AccessibilitySettings __instance) { AccessibilityIntegration.Initialize(__instance); } } [HarmonyPatch(typeof(AccessibilitySettings), "OnOkAsync")] internal static class AccessibilityOkPatch { [HarmonyPrefix] private static void Prefix() { AccessibilityIntegration.Save(); } } [HarmonyPatch(typeof(AccessibilitySettings), "OnBack")] internal static class AccessibilityBackPatch { [HarmonyPostfix] private static void Postfix() { AccessibilityIntegration.Cancel(); } } [HarmonyPatch(typeof(AccessibilitySettings), "OnTabOpen")] internal static class AccessibilityTabOpenPatch { [HarmonyPostfix] private static void Postfix(AccessibilitySettings __instance, Button okButton) { AccessibilityIntegration.ConfigureNavigation(__instance, okButton); } } internal static class HoverCursorTracker { private const float MapDragThresholdPixels = 6f; private static readonly MethodInfo ClosestPinToCursorMethod = AccessTools.DeclaredMethod(typeof(Minimap), "GetClosestPinToCursor", (Type[])null, (Type[])null); private static EventSystem _eventSystem; private static PointerEventData _pointer; private static readonly List<RaycastResult> Results = new List<RaycastResult>(32); private static int _resolvedFrame = -1; private static NativeCursorKind _resolvedKind = NativeCursorKind.Arrow; private static bool _mapPointerDown; private static bool _mapDragging; private static Vector2 _mapPressPosition; internal static NativeCursorKind DesiredKind { get { if (_resolvedFrame == Time.frameCount) { return _resolvedKind; } _resolvedFrame = Time.frameCount; _resolvedKind = ResolveCurrentKind(); return _resolvedKind; } } private static NativeCursorKind ResolveCurrentKind() { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) bool flag = IsLargeMapSurface(); if (UpdateMapDragState(flag)) { return NativeCursorKind.Move; } EventSystem current = EventSystem.current; if ((Object)(object)current == (Object)null) { return NativeCursorKind.Arrow; } if (_pointer == null || (Object)(object)_eventSystem != (Object)(object)current) { _eventSystem = current; _pointer = new PointerEventData(current); } ((AbstractEventData)_pointer).Reset(); _pointer.position = Vector2.op_Implicit(Input.mousePosition); Results.Clear(); current.RaycastAll(_pointer, Results); if (Results.Count == 0) { return NativeCursorKind.Arrow; } RaycastResult val = Results[0]; GameObject gameObject = ((RaycastResult)(ref val)).gameObject; if ((Object)(object)gameObject == (Object)null || !gameObject.activeInHierarchy) { return NativeCursorKind.Arrow; } TMP_InputField componentInParent = gameObject.GetComponentInParent<TMP_InputField>(); if ((Object)(object)componentInParent != (Object)null && ((UIBehaviour)componentInParent).IsActive() && ((Selectable)componentInParent).IsInteractable()) { return NativeCursorKind.Text; } InputField componentInParent2 = gameObject.GetComponentInParent<InputField>(); if ((Object)(object)componentInParent2 != (Object)null && ((UIBehaviour)componentInParent2).IsActive() && ((Selectable)componentInParent2).IsInteractable()) { return NativeCursorKind.Text; } InventoryElement componentInParent3 = gameObject.GetComponentInParent<InventoryElement>(); if ((Object)(object)componentInParent3 != (Object)null) { InventoryGrid componentInParent4 = ((Component)componentInParent3).GetComponentInParent<InventoryGrid>(); Inventory val2 = (((Object)(object)componentInParent4 != (Object)null) ? componentInParent4.GetInventory() : null); if (val2 != null && val2.GetItemAt(componentInParent3.Position.x, componentInParent3.Position.y) != null) { return NativeCursorKind.Hand; } return NativeCursorKind.Arrow; } if (flag && IsMapPinUnderPointer()) { return NativeCursorKind.Hand; } return NativeCursorKind.Arrow; } private static bool UpdateMapDragState(bool pointerInsideLargeMap) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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) //IL_0036: 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_0040: Unknown result type (might be due to invalid IL or missing references) if (!Input.GetMouseButton(0)) { _mapPointerDown = false; _mapDragging = false; return false; } if (!_mapPointerDown) { if (!Input.GetMouseButtonDown(0) || !pointerInsideLargeMap) { return false; } _mapPointerDown = true; _mapDragging = false; _mapPressPosition = Vector2.op_Implicit(Input.mousePosition); return false; } if (!_mapDragging) { Vector2 val = Vector2.op_Implicit(Input.mousePosition) - _mapPressPosition; if (((Vector2)(ref val)).sqrMagnitude >= 36f) { _mapDragging = true; } } return _mapDragging; } private static bool IsLargeMapSurface() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) Minimap instance = Minimap.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_largeRoot == (Object)null || !instance.m_largeRoot.activeInHierarchy || (Object)(object)instance.m_mapImageLarge == (Object)null) { return false; } return RectTransformUtility.RectangleContainsScreenPoint(((Graphic)instance.m_mapImageLarge).rectTransform, Vector2.op_Implicit(Input.mousePosition)); } private static bool IsMapPinUnderPointer() { Minimap instance = Minimap.instance; if ((Object)(object)instance == (Object)null || ClosestPinToCursorMethod == null) { return false; } try { return ClosestPinToCursorMethod.Invoke(instance, null) != null; } catch { return false; } } internal static void PruneDestroyed() { } internal static void Clear() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) Results.Clear(); _pointer = null; _eventSystem = null; _resolvedFrame = -1; _resolvedKind = NativeCursorKind.Arrow; _mapPointerDown = false; _mapDragging = false; _mapPressPosition = Vector2.zero; } } internal static class LinuxNativeCursorBackend { private sealed class CursorFrame { internal Texture2D Texture; internal Vector2 Hotspot; internal int DelayMs; } private sealed class CursorFrames { internal readonly List<CursorFrame> Frames = new List<CursorFrame>(); internal int TotalDelayMs; } private struct XcursorImageNative { internal uint version; internal uint size; internal uint width; internal uint height; internal uint xhot; internal uint yhot; internal uint delay; internal IntPtr pixels; } private struct XcursorImagesNative { internal int nimage; internal IntPtr images; internal IntPtr name; } private const string XcursorLib = "libXcursor.so.1"; private const string X11Lib = "libX11.so.6"; private static readonly Dictionary<NativeCursorKind, CursorFrames> Cursors = new Dictionary<NativeCursorKind, CursorFrames>(); private static string _theme; private static int _size; private static bool _initialized; private static bool _dirty = true; private static NativeCursorKind _appliedKind = NativeCursorKind.Arrow; private static int _appliedFrame = -1; internal static bool Initialize() { try { ResolveThemeAndSize(); Cursors.Clear(); LoadKind(NativeCursorKind.Arrow, "left_ptr", "default", "arrow"); LoadKind(NativeCursorKind.Hand, "pointer", "hand2", "hand1"); LoadKind(NativeCursorKind.Text, "text", "xterm"); LoadKind(NativeCursorKind.Move, "grabbing", "closedhand", "fleur", "move"); LoadKind(NativeCursorKind.ResizeHorizontal, "ew-resize", "sb_h_double_arrow", "size_hor"); LoadKind(NativeCursorKind.ResizeVertical, "ns-resize", "sb_v_double_arrow", "size_ver"); LoadKind(NativeCursorKind.Crosshair, "crosshair", "cross"); LoadKind(NativeCursorKind.Working, "progress", "left_ptr_watch", "half-busy", "watch"); LoadKind(NativeCursorKind.Wait, "wait", "watch", "progress"); _initialized = Cursors.ContainsKey(NativeCursorKind.Arrow); if (!_initialized) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogError((object)"Linux system cursor theme could not supply an Arrow cursor. Native cursor support will stay inactive."); } return false; } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("Initialized Linux system cursor-theme backend " + string.Format("(theme='{0}', size={1}px, ", _theme ?? "default", _size) + $"loaded states={Cursors.Count}/9). " + "Cursor images come from the desktop Xcursor theme and are displayed through Unity, so the same backend works under X11, XWayland and native Wayland.")); } return true; } catch (DllNotFoundException ex) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogError((object)("Linux libXcursor is unavailable; native cursor support cannot load the desktop cursor theme. " + ex.Message)); } _initialized = false; return false; } catch (Exception ex2) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogError((object)("Could not initialize Linux system cursor backend: " + ex2.Message)); } _initialized = false; return false; } } internal static void Apply(NativeCursorKind kind, bool force) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (!_initialized) { return; } CursorFrames frames = GetFrames(kind); if (frames != null && frames.Frames.Count != 0) { int frameIndex = GetFrameIndex(frames); if (force || _dirty || kind != _appliedKind || frameIndex != _appliedFrame) { CursorFrame cursorFrame = frames.Frames[frameIndex]; Cursor.SetCursor(cursorFrame.Texture, cursorFrame.Hotspot, (CursorMode)0); _appliedKind = kind; _appliedFrame = frameIndex; _dirty = false; } } } internal static void MarkDirty() { _dirty = true; } internal static void RestoreDefault() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Cursor.SetCursor((Texture2D)null, Vector2.zero, (CursorMode)0); _dirty = true; _appliedFrame = -1; _appliedKind = NativeCursorKind.Arrow; } internal static void Shutdown() { foreach (CursorFrames value in Cursors.Values) { foreach (CursorFrame frame in value.Frames) { if ((Object)(object)frame.Texture != (Object)null) { Object.Destroy((Object)(object)frame.Texture); } } } Cursors.Clear(); _initialized = false; _dirty = true; _appliedFrame = -1; } private static CursorFrames GetFrames(NativeCursorKind kind) { if (Cursors.TryGetValue(kind, out var value)) { return value; } Cursors.TryGetValue(NativeCursorKind.Arrow, out value); return value; } private static int GetFrameIndex(CursorFrames frames) { if (frames.Frames.Count <= 1 || frames.TotalDelayMs <= 0) { return 0; } int num = (int)(Time.unscaledTime * 1000f) % frames.TotalDelayMs; int num2 = 0; for (int i = 0; i < frames.Frames.Count; i++) { int num3 = Math.Max(1, frames.Frames[i].DelayMs); num2 += num3; if (num < num2) { return i; } } return frames.Frames.Count - 1; } private static void ResolveThemeAndSize() { _theme = Environment.GetEnvironmentVariable("XCURSOR_THEME"); _size = 0; string environmentVariable = Environment.GetEnvironmentVariable("XCURSOR_SIZE"); if (!string.IsNullOrEmpty(environmentVariable)) { int.TryParse(environmentVariable, out _size); } IntPtr intPtr = IntPtr.Zero; try { intPtr = XOpenDisplay(IntPtr.Zero); if (intPtr != IntPtr.Zero) { IntPtr intPtr2 = XcursorGetTheme(intPtr); if (intPtr2 != IntPtr.Zero) { string text = Marshal.PtrToStringAnsi(intPtr2); if (!string.IsNullOrEmpty(text)) { _theme = text; } } int num = XcursorGetDefaultSize(intPtr); if (num > 0) { _size = num; } } } catch { } finally { if (intPtr != IntPtr.Zero) { try { XCloseDisplay(intPtr); } catch { } } } if (string.IsNullOrEmpty(_theme)) { _theme = ReadDefaultThemeInheritance(); } if (_size <= 0) { _size = 24; } } private static string ReadDefaultThemeInheritance() { try { string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); string[] array = new string[2] { Path.Combine(folderPath, ".icons", "default", "index.theme"), Path.Combine(folderPath, ".local", "share", "icons", "default", "index.theme") }; foreach (string path in array) { if (!File.Exists(path)) { continue; } string[] array2 = File.ReadAllLines(path); for (int j = 0; j < array2.Length; j++) { string text = array2[j].Trim(); if (text.StartsWith("Inherits=", StringComparison.OrdinalIgnoreCase)) { string text2 = text.Substring("Inherits=".Length).Trim(); int num = text2.IndexOfAny(new char[3] { ',', ';', ':' }); if (num >= 0) { text2 = text2.Substring(0, num).Trim(); } if (!string.IsNullOrEmpty(text2)) { return text2; } } } } } catch { } return null; } private static void LoadKind(NativeCursorKind kind, params string[] names) { foreach (string text in names) { CursorFrames cursorFrames = LoadImages(text); if (cursorFrames != null && cursorFrames.Frames.Count != 0) { Cursors[kind] = cursorFrames; ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)($"Linux cursor '{kind}' -> Xcursor '{text}' " + $"({cursorFrames.Frames.Count} frame(s)).")); } return; } } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)($"Linux cursor theme has no native image for '{kind}'. " + "Arrow will be used for that state.")); } } private static CursorFrames LoadImages(string name) { //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) IntPtr intPtr = XcursorLibraryLoadImages(name, _theme, _size); if (intPtr == IntPtr.Zero) { return null; } try { XcursorImagesNative xcursorImagesNative = Marshal.PtrToStructure<XcursorImagesNative>(intPtr); if (xcursorImagesNative.nimage <= 0 || xcursorImagesNative.images == IntPtr.Zero) { return null; } CursorFrames cursorFrames = new CursorFrames(); for (int i = 0; i < xcursorImagesNative.nimage; i++) { IntPtr intPtr2 = Marshal.ReadIntPtr(xcursorImagesNative.images, i * IntPtr.Size); if (intPtr2 == IntPtr.Zero) { continue; } XcursorImageNative image = Marshal.PtrToStructure<XcursorImageNative>(intPtr2); if (image.width != 0 && image.height != 0 && !(image.pixels == IntPtr.Zero) && image.width <= 512 && image.height <= 512) { Texture2D val = CreateTexture(name, image); if (!((Object)(object)val == (Object)null)) { CursorFrame cursorFrame = new CursorFrame { Texture = val, Hotspot = new Vector2((float)image.xhot, (float)image.yhot), DelayMs = (int)image.delay }; cursorFrames.Frames.Add(cursorFrame); cursorFrames.TotalDelayMs += Math.Max(1, cursorFrame.DelayMs); } } } if (cursorFrames.Frames.Count == 0) { return null; } return cursorFrames; } finally { XcursorImagesDestroy(intPtr); } } private static Texture2D CreateTexture(string cursorName, XcursorImageNative image) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) int num; int num2; int[] array; Color32[] array2; checked { num = (int)image.width; num2 = (int)image.height; int num3 = num * num2; array = new int[num3]; Marshal.Copy(image.pixels, array, 0, num3); array2 = (Color32[])(object)new Color32[num3]; } for (int i = 0; i < num2; i++) { int num4 = num2 - 1 - i; for (int j = 0; j < num; j++) { int num5 = array[i * num + j]; byte b = (byte)((uint)num5 >> 24); byte b2 = (byte)((uint)num5 >> 16); byte b3 = (byte)((uint)num5 >> 8); byte b4 = (byte)num5; if (b > 0 && b < byte.MaxValue) { b2 = Unpremultiply(b2, b); b3 = Unpremultiply(b3, b); b4 = Unpremultiply(b4, b); } array2[num4 * num + j] = new Color32(b2, b3, b4, b); } } Texture2D val = new Texture2D(num, num2, (TextureFormat)4, false, false) { name = "WireNativeCursor_Linux_" + cursorName, hideFlags = (HideFlags)61 }; val.SetPixels32(array2); val.Apply(false, false); return val; } private static byte Unpremultiply(byte value, byte alpha) { int val = (value * 255 + alpha / 2) / alpha; return (byte)Math.Min(255, val); } [DllImport("libXcursor.so.1", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern IntPtr XcursorLibraryLoadImages(string name, string theme, int size); [DllImport("libXcursor.so.1", CallingConvention = CallingConvention.Cdecl)] private static extern void XcursorImagesDestroy(IntPtr images); [DllImport("libXcursor.so.1", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr XcursorGetTheme(IntPtr display); [DllImport("libXcursor.so.1", CallingConvention = CallingConvention.Cdecl)] private static extern int XcursorGetDefaultSize(IntPtr display); [DllImport("libX11.so.6", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr XOpenDisplay(IntPtr displayName); [DllImport("libX11.so.6", CallingConvention = CallingConvention.Cdecl)] private static extern int XCloseDisplay(IntPtr display); } internal static class MacNativeCursorBackend { private const string ObjCLib = "/usr/lib/libobjc.A.dylib"; private static readonly Dictionary<NativeCursorKind, IntPtr> Cursors = new Dictionary<NativeCursorKind, IntPtr>(); private static IntPtr _nsCursorClass; private static IntPtr _setSelector; private static IntPtr _currentCursorSelector; private static IntPtr _respondsSelector; private static bool _initialized; internal static bool Initialize() { try { _nsCursorClass = objc_getClass("NSCursor"); if (_nsCursorClass == IntPtr.Zero) { return false; } _setSelector = sel_registerName("set"); _currentCursorSelector = sel_registerName("currentCursor"); _respondsSelector = sel_registerName("respondsToSelector:"); Cursors.Clear(); Cursors[NativeCursorKind.Arrow] = GetClassCursor("arrowCursor"); Cursors[NativeCursorKind.Hand] = GetClassCursor("pointingHandCursor"); Cursors[NativeCursorKind.Text] = GetClassCursor("IBeamCursor"); Cursors[NativeCursorKind.Move] = GetClassCursor("closedHandCursor", "openHandCursor"); Cursors[NativeCursorKind.ResizeHorizontal] = GetClassCursor("columnResizeCursor", "resizeLeftRightCursor"); Cursors[NativeCursorKind.ResizeVertical] = GetClassCursor("rowResizeCursor", "resizeUpDownCursor"); Cursors[NativeCursorKind.Crosshair] = GetClassCursor("crosshairCursor"); IntPtr intPtr = GetClassCursor("busyButClickableCursor"); if (intPtr == IntPtr.Zero) { intPtr = Cursors[NativeCursorKind.Arrow]; } Cursors[NativeCursorKind.Working] = intPtr; Cursors[NativeCursorKind.Wait] = intPtr; _initialized = Cursors[NativeCursorKind.Arrow] != IntPtr.Zero; if (_initialized) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"Initialized macOS AppKit system cursor backend (Arrow, Pointing Hand, I-beam, Closed Hand, Resize, Crosshair, Busy/Progress where available)."); } } return _initialized; } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)("Could not initialize macOS AppKit cursor backend: " + ex.Message)); } _initialized = false; return false; } } internal static void Apply(NativeCursorKind kind, bool force) { if (_initialized) { IntPtr cursor = GetCursor(kind); if (!(cursor == IntPtr.Zero) && (force || !(objc_msgSend(_nsCursorClass, _currentCursorSelector) == cursor))) { objc_msgSend_void(cursor, _setSelector); } } } internal static void RestoreDefault() { if (_initialized) { Apply(NativeCursorKind.Arrow, force: true); } } internal static void Shutdown() { Cursors.Clear(); _initialized = false; _nsCursorClass = IntPtr.Zero; } private static IntPtr GetCursor(NativeCursorKind kind) { if (Cursors.TryGetValue(kind, out var value) && value != IntPtr.Zero) { return value; } if (Cursors.TryGetValue(NativeCursorKind.Arrow, out value)) { return value; } return IntPtr.Zero; } private static IntPtr GetClassCursor(params string[] selectors) { for (int i = 0; i < selectors.Length; i++) { IntPtr intPtr = sel_registerName(selectors[i]); if (!(intPtr == IntPtr.Zero) && objc_msgSend_bool(_nsCursorClass, _respondsSelector, intPtr)) { IntPtr intPtr2 = objc_msgSend(_nsCursorClass, intPtr); if (intPtr2 != IntPtr.Zero) { return intPtr2; } } } return IntPtr.Zero; } [DllImport("/usr/lib/libobjc.A.dylib", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr objc_getClass(string name); [DllImport("/usr/lib/libobjc.A.dylib", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr sel_registerName(string name); [DllImport("/usr/lib/libobjc.A.dylib", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector); [DllImport("/usr/lib/libobjc.A.dylib", CallingConvention = CallingConvention.Cdecl, EntryPoint = "objc_msgSend")] private static extern void objc_msgSend_void(IntPtr receiver, IntPtr selector); [DllImport("/usr/lib/libobjc.A.dylib", CallingConvention = CallingConvention.Cdecl, EntryPoint = "objc_msgSend")] [return: MarshalAs(UnmanagedType.I1)] private static extern bool objc_msgSend_bool(IntPtr receiver, IntPtr selector, IntPtr argument); } internal enum NativeCursorKind { Arrow, Hand, Text, Move, ResizeHorizontal, ResizeVertical, Crosshair, Working, Wait } internal static class NativeCursorController { private enum HostCursorPlatform { Unsupported, Windows, MacOS, Linux } [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate IntPtr WindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); private static class NativeMethods { internal struct POINT { internal int x; internal int y; } internal struct CURSORINFO { internal int cbSize; internal int flags; internal IntPtr hCursor; internal POINT ptScreenPos; } internal struct RECT { internal int left; internal int top; internal int right; internal int bottom; } internal delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); internal const int CURSOR_SHOWING = 1; [DllImport("user32.dll", CharSet = CharSet.Auto)] internal static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); [DllImport("user32.dll")] internal static extern IntPtr SetCursor(IntPtr hCursor); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetCursorInfo(ref CURSORINFO pci); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetCursorPos(out POINT lpPoint); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool IsWindow(IntPtr hWnd); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); [DllImport("user32.dll")] internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("user32.dll", CharSet = CharSet.Unicode)] internal static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); [DllImport("user32.dll", CharSet = CharSet.Unicode)] internal static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = true)] private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong); [DllImport("user32.dll", EntryPoint = "SetWindowLongW", SetLastError = true)] private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, int dwNewLong); [DllImport("user32.dll", EntryPoint = "GetWindowLongPtrW", SetLastError = true)] private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "GetWindowLongW", SetLastError = true)] private static extern int GetWindowLong32(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "CallWindowProcW")] internal static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", EntryPoint = "DefWindowProcW")] internal static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); internal static IntPtr SetClassLongPtr(IntPtr hWnd, int nIndex, IntPtr value) { return IntPtr.Zero; } internal static IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) { return IntPtr.Zero; } internal static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr value) { if (IntPtr.Size == 8) { return SetWindowLongPtr64(hWnd, nIndex, value); } return new IntPtr(SetWindowLong32(hWnd, nIndex, value.ToInt32())); } internal static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex) { if (IntPtr.Size == 8) { return GetWindowLongPtr64(hWnd, nIndex); } return new IntPtr(GetWindowLong32(hWnd, nIndex)); } } private const int IDC_ARROW = 32512; private const int IDC_IBEAM = 32513; private const int IDC_WAIT = 32514; private const int IDC_CROSS = 32515; private const int IDC_SIZEWE = 32644; private const int IDC_SIZENS = 32645; private const int IDC_SIZEALL = 32646; private const int IDC_HAND = 32649; private const int IDC_APPSTARTING = 32650; private const int GWL_WNDPROC = -4; private const int GCLP_HCURSOR = -12; private const uint WM_SETCURSOR = 32u; private const uint WM_MOUSEACTIVATE = 33u; private const uint WM_INPUT = 255u; private const uint WM_MOUSEMOVE = 512u; private const uint WM_LBUTTONDOWN = 513u; private const uint WM_LBUTTONUP = 514u; private const uint WM_RBUTTONDOWN = 516u; private const uint WM_RBUTTONUP = 517u; private const uint WM_MBUTTONDOWN = 519u; private const uint WM_MBUTTONUP = 520u; private const uint WM_MOUSEWHEEL = 522u; private const uint WM_XBUTTONDOWN = 523u; private const uint WM_XBUTTONUP = 524u; private const uint WM_MOUSEHWHEEL = 526u; private const uint WM_CAPTURECHANGED = 533u; private const uint WM_MOUSEHOVER = 673u; private const uint WM_MOUSELEAVE = 675u; private const int HTCLIENT = 1; private static readonly WindowProc HookDelegate = WindowProcHook; private static readonly IntPtr HookDelegatePtr = Marshal.GetFunctionPointerForDelegate(HookDelegate); private static readonly FieldInfo CraftTimerField = AccessTools.Field(typeof(InventoryGui), "m_craftTimer"); private static readonly FieldInfo DragItemField = AccessTools.Field(typeof(InventoryGui), "m_dragItem"); private static readonly HashSet<NativeCursorKind> VerifiedKinds = new HashSet<NativeCursorKind>(); private static bool _supported; private static bool _enabled; private static HostCursorPlatform _hostPlatform = HostCursorPlatform.Unsupported; private static IntPtr _arrow; private static IntPtr _hand; private static IntPtr _text; private static IntPtr _move; private static IntPtr _resizeHorizontal; private static IntPtr _resizeVertical; private static IntPtr _crosshair; private static IntPtr _working; private static IntPtr _wait; private static IntPtr _window; private static IntPtr _previousWindowProc; private static bool _hookInstalled; private static float _nextHookAttempt; private static IntPtr _capturedGameCursor; private static bool _capturedGameCursorValid; private static bool _unitySystemDefaultInstalled; private static Texture2D _transparentUnityCursor; private static IntPtr _originalClassCursor; private static bool _originalClassCursorCaptured; private static IntPtr _lastClassCursor; private static NativeCursorKind _desiredKind = NativeCursorKind.Arrow; private static bool _lastWait; private static bool _lastCrafting; private static bool _lastShouldControl; private static bool _loggedUnityOverwrite; private static int _overwriteCorrections; internal static void Initialize() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Invalid comparison between Unknown and I4 //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Invalid comparison between Unknown and I4 RuntimePlatform platform = Application.platform; if ((int)platform == 2 || (int)platform == 7) { _hostPlatform = HostCursorPlatform.Windows; _supported = InitializeWindowsBackend(); } else if ((int)platform == 1 || (int)platform == 0) { _hostPlatform = HostCursorPlatform.MacOS; _supported = MacNativeCursorBackend.Initialize(); if (_supported) { CreateTransparentUnityCursor(); } } else if ((int)platform == 13 || (int)platform == 16) { _hostPlatform = HostCursorPlatform.Linux; _supported = LinuxNativeCursorBackend.Initialize(); } else { _hostPlatform = HostCursorPlatform.Unsupported; _supported = false; } if (!_supported) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("Native cursor support is inactive on " + $"platform '{platform}'.")); } } else { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("Native cursor backend active: " + _hostPlatform.ToString() + ".")); } } } private static bool InitializeWindowsBackend() { _arrow = NativeMethods.LoadCursor(IntPtr.Zero, new IntPtr(32512)); _hand = NativeMethods.LoadCursor(IntPtr.Zero, new IntPtr(32649)); _text = NativeMethods.LoadCursor(IntPtr.Zero, new IntPtr(32513)); _move = NativeMethods.LoadCursor(IntPtr.Zero, new IntPtr(32646)); _resizeHorizontal = NativeMethods.LoadCursor(IntPtr.Zero, new IntPtr(32644)); _resizeVertical = NativeMethods.LoadCursor(IntPtr.Zero, new IntPtr(32645)); _crosshair = NativeMethods.LoadCursor(IntPtr.Zero, new IntPtr(32515)); _working = NativeMethods.LoadCursor(IntPtr.Zero, new IntPtr(32650)); _wait = NativeMethods.LoadCursor(IntPtr.Zero, new IntPtr(32514)); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("Loaded Windows system cursors: " + $"arrow=0x{_arrow.ToInt64():X}, " + $"hand=0x{_hand.ToInt64():X}, " + $"text=0x{_text.ToInt64():X}, " + $"move=0x{_move.ToInt64():X}, " + $"resizeH=0x{_resizeHorizontal.ToInt64():X}, " + $"resizeV=0x{_resizeVertical.ToInt64():X}, " + $"crosshair=0x{_crosshair.ToInt64():X}, " + $"working=0x{_working.ToInt64():X}, " + $"wait=0x{_wait.ToInt64():X}.")); } if (_arrow == IntPtr.Zero) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)"Windows did not return the standard arrow cursor handle."); } return false; } CreateTransparentUnityCursor(); return true; } private static void CreateTransparentUnityCursor() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown if (!((Object)(object)_transparentUnityCursor != (Object)null)) { _transparentUnityCursor = new Texture2D(32, 32, (TextureFormat)4, false, false); ((Object)_transparentUnityCursor).name = "WireNativeCursor_TransparentUnityCursor"; ((Object)_transparentUnityCursor).hideFlags = (HideFlags)61; _transparentUnityCursor.SetPixels32((Color32[])(object)new Color32[1024]); _transparentUnityCursor.Apply(false, false); } } internal static void SetEnabled(bool enabled) { _enabled = enabled && _supported; if (!_enabled) { RestorePlatformCursor(); } else { RefreshControlState(forceApply: true); } } internal static void Shutdown() { _enabled = false; RestorePlatformCursor(); if (_hostPlatform == HostCursorPlatform.MacOS) { MacNativeCursorBackend.Shutdown(); } else if (_hostPlatform == HostCursorPlatform.Linux) { LinuxNativeCursorBackend.Shutdown(); } HoverCursorTracker.Clear(); VerifiedKinds.Clear(); _capturedGameCursor = IntPtr.Zero; _capturedGameCursorValid = false; _unitySystemDefaultInstalled = false; if ((Object)(object)_transparentUnityCursor != (Object)null) { Object.Destroy((Object)(object)_transparentUnityCursor); _transparentUnityCursor = null; } _originalClassCursor = IntPtr.Zero; _originalClassCursorCaptured = false; _lastClassCursor = IntPtr.Zero; _loggedUnityOverwrite = false; _overwriteCorrections = 0; _hostPlatform = HostCursorPlatform.Unsupported; _supported = false; } private static void RestorePlatformCursor() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) switch (_hostPlatform) { case HostCursorPlatform.Windows: RestoreVanillaCursorState(); RemoveWindowHook(); break; case HostCursorPlatform.MacOS: Cursor.SetCursor((Texture2D)null, Vector2.zero, (CursorMode)0); MacNativeCursorBackend.RestoreDefault(); _unitySystemDefaultInstalled = false; break; case HostCursorPlatform.Linux: LinuxNativeCursorBackend.RestoreDefault(); break; } _desiredKind = NativeCursorKind.Arrow; } internal static void LateTick() { if (_enabled && _supported) { bool num = (_lastShouldControl = ShouldControlCursor()); _lastWait = IsWaitState(); _lastCrafting = IsCrafting(); if (num) { ReconcileDesiredCursor("LateUpdate", forceApply: false); } } } internal static void OnBeforeRender() { if (_enabled && _supported && ShouldControlCursor()) { ReconcileDesiredCursor("BeforeRender", forceApply: false); } } internal static void EndOfFrameTick() { if (_enabled && _supported && ShouldControlCursor()) { ReconcileDesiredCursor("EndOfFrame", forceApply: false); } } internal static void OnUnityCursorVisibilityApplied() { if (_enabled && _supported && ShouldControlCursor()) { ReconcileDesiredCursor("ZCursor.SetVisible", forceApply: false); } } internal static void OnCursorContextChanged() { if (_enabled) { RefreshControlState(forceApply: true); } } internal static void OnApplicationFocusChanged(bool focused) { if (_enabled && focused) { if (_hostPlatform == HostCursorPlatform.Windows || _hostPlatform == HostCursorPlatform.MacOS) { _unitySystemDefaultInstalled = false; } else if (_hostPlatform == HostCursorPlatform.Linux) { LinuxNativeCursorBackend.MarkDirty(); } if (_hostPlatform == HostCursorPlatform.Windows) { _nextHookAttempt = 0f; } RefreshControlState(forceApply: true); } } internal static void OnHoverStateChanged() { if (_enabled) { RefreshControlState(forceApply: true); } } private static void RefreshControlState(bool forceApply) { if (_enabled && _supported && ShouldControlCursor()) { ReconcileDesiredCursor("ContextChanged", forceApply); } } private static void ReconcileDesiredCursor(string source, bool forceApply) { if (!_enabled || !_supported || !ShouldControlCursor()) { return; } NativeCursorKind nativeCursorKind = DetermineCursorKind(); bool flag = nativeCursorKind != _desiredKind; _desiredKind = nativeCursorKind; if (_hostPlatform == HostCursorPlatform.Linux) { LinuxNativeCursorBackend.Apply(nativeCursorKind, flag || forceApply); } else if (_hostPlatform == HostCursorPlatform.MacOS) { EnsureUnityUsesSystemDefault(); MacNativeCursorBackend.Apply(nativeCursorKind, flag || forceApply); } else if (_hostPlatform == HostCursorPlatform.Windows) { CaptureGameCursorIfPossible(); if (!_hookInstalled && Time.unscaledTime >= _nextHookAttempt) { EnsureWindowHook(); } EnsureUnityUsesSystemDefault(); if (flag || forceApply) { ApplyDesiredCursor(); } EnsureDesiredCursorPresent(source); } } private static void EnsureUnityUsesSystemDefault() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_transparentUnityCursor == (Object)null) && !_unitySystemDefaultInstalled) { Cursor.SetCursor(_transparentUnityCursor, Vector2.zero, (CursorMode)0); _unitySystemDefaultInstalled = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"Masked Unity's own cursor layer with a transparent CPU-readable RGBA32 cursor."); } } } private static void CaptureOriginalClassCursor() { if (!_originalClassCursorCaptured && !(_window == IntPtr.Zero) && NativeMethods.IsWindow(_window)) { _originalClassCursor = NativeMethods.GetClassLongPtr(_window, -12); _originalClassCursorCaptured = true; } } private static void UpdateWindowClassCursor() { if (!(_window == IntPtr.Zero) && NativeMethods.IsWindow(_window)) { CaptureOriginalClassCursor(); IntPtr intPtr = GetHandle(_desiredKind); if (intPtr == IntPtr.Zero) { intPtr = _arrow; } if (!(intPtr == IntPtr.Zero) && !(intPtr == _lastClassCursor)) { NativeMethods.SetClassLongPtr(_window, -12, intPtr); _lastClassCursor = intPtr; } } } private static void RestoreVanillaCursorState() { //IL_0009: Unknown result type (might be due to invalid IL or missing references) if (_supported) { Cursor.SetCursor((Texture2D)null, Vector2.zero, (CursorMode)0); IntPtr intPtr = (_capturedGameCursorValid ? _capturedGameCursor : _originalClassCursor); if (_window != IntPtr.Zero && NativeMethods.IsWindow(_window) && intPtr != IntPtr.Zero) { NativeMethods.SetClassLongPtr(_window, -12, intPtr); _lastClassCursor = intPtr; } if (intPtr != IntPtr.Zero) { NativeMethods.SetCursor(intPtr); } _unitySystemDefaultInstalled = false; _desiredKind = NativeCursorKind.Arrow; } } private static NativeCursorKind DetermineCursorKind() { if (IsWaitState()) { return NativeCursorKind.Wait; } if (IsCrafting()) { return NativeCursorKind.Working; } if (IsDraggingInventoryItem()) { return NativeCursorKind.Move; } return HoverCursorTracker.DesiredKind; } private static bool IsWaitState() { FejdStartup instance = FejdStartup.instance; if ((Object)(object)instance == (Object)null) { return false; } if (!((Object)(object)instance.m_loading != (Object)null) || !instance.m_loading.activeInHierarchy) { if ((Object)(object)instance.m_pleaseWait != (Object)null) { return instance.m_pleaseWait.activeInHierarchy; } return false; } return true; } private static bool IsCrafting() { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance == (Object)null || !InventoryGui.IsVisible() || CraftTimerField == null) { return false; } if (CraftTimerField.GetValue(instance) is float num) { return num >= 0f; } return false; } private static bool IsDraggingInventoryItem() { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance == (Object)null || !InventoryGui.IsVisible() || DragItemField == null) { return false; } return DragItemField.GetValue(instance) != null; } private static bool ShouldControlCursor() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) try { return Application.isFocused && (int)Cursor.lockState == 0 && ZCursor.IsRequested && ZCursor.IsVisible && ZInput.IsMouseActive() && !ZInput.IsTouchActive() && !ZInput.IsGamepadMouseActive() && (_hostPlatform != HostCursorPlatform.Windows || IsCursorInsideClientArea()); } catch { return false; } } private static bool IsCursorInsideClientArea() { if (_window == IntPtr.Zero || !NativeMethods.IsWindow(_window)) { return true; } if (!NativeMethods.GetCursorPos(out var lpPoint)) { return true; } if (!NativeMethods.ScreenToClient(_window, ref lpPoint)) { return true; } if (!NativeMethods.GetClientRect(_window, out var lpRect)) { return true; } if (lpPoint.x >= lpRect.left && lpPoint.y >= lpRect.top && lpPoint.x < lpRect.right) { return lpPoint.y < lpRect.bottom; } return false; } private static void ApplyDesiredCursor() { if (ShouldControlCursor()) { IntPtr intPtr = GetHandle(_desiredKind); if (intPtr == IntPtr.Zero) { intPtr = _arrow; } if (!(intPtr == IntPtr.Zero)) { NativeMethods.SetCursor(intPtr); VerifyCursor(intPtr, _desiredKind); } } } private static void EnsureDesiredCursorPresent(string source) { if (!ShouldControlCursor()) { return; } IntPtr intPtr = GetHandle(_desiredKind); if (intPtr == IntPtr.Zero) { intPtr = _arrow; } if (intPtr == IntPtr.Zero) { return; } NativeMethods.CURSORINFO pci = CreateCursorInfo(); bool flag = NativeMethods.GetCursorInfo(ref pci) && (pci.flags & 1) != 0; if (flag && pci.hCursor == intPtr) { return; } NativeMethods.SetCursor(intPtr); _overwriteCorrections++; if (!_loggedUnityOverwrite) { _loggedUnityOverwrite = true; string arg = ((flag && pci.hCursor != IntPtr.Zero) ? $"0x{pci.hCursor.ToInt64():X}" : "hidden/unknown"); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("Detected Unity replacing the native cursor " + $"(actual={arg}, expected=0x{intPtr.ToInt64():X}, " + "source=" + source + "). Persistent cursor reassertion is now active.")); } } VerifyCursor(intPtr, _desiredKind); } private static bool IsMouseCursorMessage(uint msg) { switch (msg) { case 33u: case 255u: case 512u: case 513u: case 514u: case 516u: case 517u: case 519u: case 520u: case 522u: case 523u: case 524u: case 526u: case 533u: case 673u: case 675u: return true; default: return false; } } private static IntPtr GetHandle(NativeCursorKind kind) { switch (kind) { case NativeCursorKind.Hand: return _hand; case NativeCursorKind.Text: return _text; case NativeCursorKind.Move: if (!(_move != IntPtr.Zero)) { return _hand; } return _move; case NativeCursorKind.ResizeHorizontal: if (!(_resizeHorizontal != IntPtr.Zero)) { return _arrow; } return _resizeHorizontal; case NativeCursorKind.ResizeVertical: if (!(_resizeVertical != IntPtr.Zero)) { return _arrow; } return _resizeVertical; case NativeCursorKind.Crosshair: if (!(_crosshair != IntPtr.Zero)) { return _arrow; } return _crosshair; case NativeCursorKind.Working: return _working; case NativeCursorKind.Wait: return _wait; default: return _arrow; } } private static void CaptureGameCursorIfPossible() { if (_capturedGameCursorValid) { return; } NativeMethods.CURSORINFO pci = CreateCursorInfo(); if (NativeMethods.GetCursorInfo(ref pci) && (pci.flags & 1) != 0 && !(pci.hCursor == IntPtr.Zero)) { _capturedGameCursor = pci.hCursor; _capturedGameCursorValid = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("Captured Valheim/Unity cursor handle before override: " + $"0x{_capturedGameCursor.ToInt64():X}.")); } } } private static void RestoreCapturedGameCursor() { RestoreVanillaCursorState(); } private static void VerifyCursor(IntPtr expected, NativeCursorKind kind) { if (VerifiedKinds.Contains(kind)) { return; } NativeMethods.CURSORINFO pci = CreateCursorInfo(); if (NativeMethods.GetCursorInfo(ref pci) && (pci.flags & 1) != 0 && pci.hCursor == expected) { VerifiedKinds.Add(kind); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)($"Runtime verified native cursor state '{kind}' " + $"(Windows reports handle 0x{expected.ToInt64():X}).")); } } } private static NativeMethods.CURSORINFO CreateCursorInfo() { return new NativeMethods.CURSORINFO { cbSize = Marshal.SizeOf(typeof(NativeMethods.CURSORINFO)) }; } private static void EnsureWindowHook() { if (_hookInstalled) { if (_window != IntPtr.Zero && NativeMethods.IsWindow(_window)) { return; } _hookInstalled = false; _window = IntPtr.Zero; _previousWindowProc = IntPtr.Zero; } if (Time.unscaledTime < _nextHookAttempt) { return; } _nextHookAttempt = Time.unscaledTime + 1f; IntPtr intPtr = FindUnityWindow(); if (intPtr == IntPtr.Zero) { return; } IntPtr intPtr2 = NativeMethods.SetWindowLongPtr(intPtr, -4, HookDelegatePtr); if (intPtr2 == IntPtr.Zero) { int lastWin32Error = Marshal.GetLastWin32Error(); ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Could not subclass the Valheim window for " + $"WM_SETCURSOR (Win32 error {lastWin32Error}). " + "The mod will retry; direct cursor updates remain active.")); } return; } _window = intPtr; _previousWindowProc = intPtr2; _hookInstalled = true; CaptureOriginalClassCursor(); UpdateWindowClassCursor(); ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("Installed WM_SETCURSOR hook on " + DescribeWindow(intPtr) + ".")); } } private static void RemoveWindowHook() { if (!_hookInstalled || _window == IntPtr.Zero || _previousWindowProc == IntPtr.Zero) { _hookInstalled = false; _window = IntPtr.Zero; _previousWindowProc = IntPtr.Zero; return; } if (NativeMethods.IsWindow(_window) && NativeMethods.GetWindowLongPtr(_window, -4) == HookDelegatePtr) { NativeMethods.SetWindowLongPtr(_window, -4, _previousWindowProc); } _hookInstalled = false; _window = IntPtr.Zero; _previousWindowProc = IntPtr.Zero; } private static IntPtr FindUnityWindow() { Process currentProcess = Process.GetCurrentProcess(); currentProcess.Refresh(); IntPtr mainWindowHandle = currentProcess.MainWindowHandle; if (mainWindowHandle != IntPtr.Zero && NativeMethods.IsWindow(mainWindowHandle)) { return mainWindowHandle; } uint currentPid = (uint)currentProcess.Id; IntPtr best = IntPtr.Zero; long bestScore = long.MinValue; NativeMethods.EnumWindows(delegate(IntPtr hwnd, IntPtr lParam) { NativeMethods.GetWindowThreadProcessId(hwnd, out var lpdwProcessId); if (lpdwProcessId != currentPid || !NativeMethods.IsWindowVisible(hwnd)) { return true; } long num = 0L; if (NativeMethods.GetClientRect(hwnd, out var lpRect)) { num = (long)Math.Max(0, lpRect.right - lpRect.left) * (long)Math.Max(0, lpRect.bottom - lpRect.top); } string windowClass = GetWindowClass(hwnd); long num2 = num; if (windowClass.IndexOf("Unity", StringComparison.OrdinalIgnoreCase) >= 0) { num2 += 1152921504606846976L; } if (num2 > bestScore) { bestScore = num2; best = hwnd; } return true; }, IntPtr.Zero); return best; } private static string DescribeWindow(IntPtr hwnd) { return $"HWND=0x{hwnd.ToInt64():X}, " + "class='" + GetWindowClass(hwnd) + "', title='" + GetWindowTitle(hwnd) + "'"; } private static string GetWindowClass(IntPtr hwnd) { StringBuilder stringBuilder = new StringBuilder(256); NativeMethods.GetClassName(hwnd, stringBuilder, stringBuilder.Capacity); return stringBuilder.ToString(); } private static string GetWindowTitle(IntPtr hwnd) { StringBuilder stringBuilder = new StringBuilder(512); NativeMethods.GetWindowText(hwnd, stringBuilder, stringBuilder.Capacity); return stringBuilder.ToString(); } private static IntPtr WindowProcHook(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { if (msg == 32 && _enabled && ShouldControlCursor() && LowWord(lParam) == 1) { IntPtr intPtr = GetHandle(_desiredKind); if (intPtr == IntPtr.Zero) { intPtr = _arrow; } if (intPtr != IntPtr.Zero) { NativeMethods.SetCursor(intPtr); VerifyCursor(intPtr, _desiredKind); return new IntPtr(1); } } IntPtr result = ((!(_previousWindowProc != IntPtr.Zero)) ? NativeMethods.DefWindowProc(hWnd, msg, wParam, lParam) : NativeMethods.CallWindowProc(_previousWindowProc, hWnd, msg, wParam, lParam)); if (_enabled && IsMouseCursorMessage(msg) && ShouldControlCursor()) { EnsureDesiredCursorPresent($"WM 0x{msg:X4}"); } return result; } private static int LowWord(IntPtr value) { return (ushort)(value.ToInt64() & 0xFFFF); } } [HarmonyPatch(typeof(ZCursor), "SetVisible")] internal static class ZCursorSetVisiblePatch { [HarmonyPostfix] private static void Postfix() { NativeCursorController.OnUnityCursorVisibilityApplied(); } } [BepInPlugin("wire.valheim.nativecursorsupport", "Wire's Native Cursor", "0.3.2")] [DefaultExecutionOrder(32000)] public sealed class Plugin : BaseUnityPlugin { [CompilerGenerated] private static class <>O { public static Action <0>__OnCursorContextChanged; public static Action<bool> <1>__OnApplicationFocusChanged; public static UnityAction <2>__OnBeforeRender; } public const string Guid = "wire.valheim.nativecursorsupport"; public const string Name = "Wire's Native Cursor"; public const string Version = "0.3.2"; internal const string PrefKey = "WireNativeCursorSupport"; internal static Plugin Instance; internal static ManualLogSource Log; private Harmony _harmony; private Coroutine _endOfFrameLoop; internal static bool RuntimeEnabled { get; private set; } private void Awake() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; RuntimeEnabled = PlayerPrefs.GetInt("WireNativeCursorSupport", 1) == 1; try { ValidateCurrentGameApi(); NativeCursorController.Initialize(); _harmony = new Harmony("wire.valheim.nativecursorsupport"); InstallCorePatches(); InstallAccessibilityPatches(); ZCursor.IsRequestedChanged += NativeCursorController.OnCursorContextChanged; ZCursor.IsVisibleChanged += NativeCursorController.OnCursorContextChanged; Application.focusChanged += NativeCursorController.OnApplicationFocusChanged; object obj = <>O.<2>__OnBeforeRender; if (obj == null) { UnityAction val = NativeCursorController.OnBeforeRender; <>O.<2>__OnBeforeRender = val; obj = (object)val; } Application.onBeforeRender += (UnityAction)obj; NativeCursorController.SetEnabled(RuntimeEnabled); _endOfFrameLoop = ((MonoBehaviour)this).StartCoroutine(EndOfFrameCursorLoop()); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Wire's Native Cursor 0.3.2 loaded. " + $"Enabled={RuntimeEnabled}. " + "Valheim remains the sole owner of cursor visibility and lock state; this mod selects the native/system cursor for the active desktop platform.")); } catch (Exception ex) { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; NativeCursorController.Shutdown(); ((BaseUnityPlugin)this).Logger.LogError((object)("Wire's Native Cursor 0.3.2 could not initialize safely. No cursor override will be left active. " + ex)); } } private void LateUpdate() { NativeCursorController.LateTick(); } private IEnumerator EndOfFrameCursorLoop() { WaitForEndOfFrame wait = new WaitForEndOfFrame(); while (true) { yield return wait; NativeCursorController.EndOfFrameTick(); } } private void OnDestroy() { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown if (_endOfFrameLoop != null) { ((MonoBehaviour)this).StopCoroutine(_endOfFrameLoop); _endOfFrameLoop = null; } ZCursor.IsRequestedChanged -= NativeCursorController.OnCursorContextChanged; ZCursor.IsVisibleChanged -= NativeCursorController.OnCursorContextChanged; Application.focusChanged -= NativeCursorController.OnApplicationFocusChanged; object obj = <>O.<2>__OnBeforeRender; if (obj == null) { UnityAction val = NativeCursorController.OnBeforeRender; <>O.<2>__OnBeforeRender = val; obj = (object)val; } Application.onBeforeRender -= (UnityAction)obj; NativeCursorController.Shutdown(); Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } _harmony = null; Instance = null; } internal static void SetRuntimeEnabled(bool enabled) { RuntimeEnabled = enabled; NativeCursorController.SetEnabled(enabled); } private static void ValidateCurrentGameApi() { RequireMethod(typeof(ZCursor), "SetVisible", typeof(bool)); RequireMethod(typeof(AccessibilitySettings), "Initialize"); RequireMethod(typeof(AccessibilitySettings), "OnOkAsync"); RequireMethod(typeof(AccessibilitySettings), "OnBack"); RequireMethod(typeof(AccessibilitySettings), "OnTabOpen"); RequireField(typeof(AccessibilitySettings), "m_toggleBlockToggle"); RequireField(typeof(InventoryGui), "m_craftTimer"); RequireField(typeof(FejdStartup), "m_loading"); RequireField(typeof(FejdStartup), "m_pleaseWait"); } private static void InstallCorePatches() { try { Instance._harmony.CreateClassProcessor(typeof(ZCursorSetVisiblePatch)).Patch(); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"Installed ZCursor visibility-reset compensation."); } } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("Could not patch ZCursor.SetVisible; continuing with the native/window/frame reconciliation paths. " + ex.Message)); } } } private static void InstallAccessibilityPatches() { Instance._harmony.CreateClassProcessor(typeof(AccessibilityInitializePatch)).Patch(); Instance._harmony.CreateClassProcessor(typeof(AccessibilityOkPatch)).Patch(); Instance._harmony.CreateClassProcessor(typeof(AccessibilityBackPatch)).Patch(); Instance._harmony.CreateClassProcessor(typeof(AccessibilityTabOpenPatch)).Patch(); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"Validated and installed Accessibility settings integration."); } } private static void RequireMethod(Type type, string name, params Type[] args) { if (((args != null && args.Length != 0) ? AccessTools.DeclaredMethod(type, name, args, (Type[])null) : AccessTools.DeclaredMethod(type, name, (Type[])null, (Type[])null)) == null) { throw new MissingMethodException(type.FullName, name); } } private static void RequireField(Type type, string name) { if (AccessTools.Field(type, name) == null) { throw new MissingFieldException(type.FullName, name); } } } }