using System; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using SleddingMouseFix; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Mod), "Sledding Mouse Stutter Fix", "1.0.0", "mitch", null)] [assembly: MelonGame(null, null)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("SleddingMouseFix")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+995f2301cfd0105f783a5991938cce51e25272ed")] [assembly: AssemblyProduct("SleddingMouseFix")] [assembly: AssemblyTitle("SleddingMouseFix")] [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 SleddingMouseFix { public class Mod : MelonMod { [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr param); private struct RECT { public int Left; public int Top; public int Right; public int Bottom; } private struct POINT { public int X; public int Y; } private const int GWLP_WNDPROC = -4; private const uint WM_NCHITTEST = 132u; private const int HTCLIENT = 1; private static IntPtr _hwnd; private static IntPtr _prevProc; private static IntPtr _myProcPtr; private static WndProcDelegate _keepAlive; private static bool _installed; private static long _handled; private static long _passedThrough; private static MelonPreferences_Category _cat; private static MelonPreferences_Entry _enabled; private static MelonPreferences_Entry _logStats; private double _nextCheckMs; private double _nextStatsMs; [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetWindowLongPtrW(IntPtr h, int i, IntPtr v); [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr GetWindowLongPtrW(IntPtr h, int i); [DllImport("user32.dll")] private static extern IntPtr CallWindowProcW(IntPtr p, IntPtr h, uint m, IntPtr w, IntPtr l); [DllImport("user32.dll")] private static extern bool EnumThreadWindows(uint tid, EnumWindowsProc cb, IntPtr p); [DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassNameW(IntPtr h, StringBuilder b, int max); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr h); [DllImport("user32.dll")] private static extern bool GetClientRect(IntPtr h, out RECT r); [DllImport("user32.dll")] private static extern bool ScreenToClient(IntPtr h, ref POINT p); [DllImport("kernel32.dll")] private static extern uint GetCurrentThreadId(); public override void OnInitializeMelon() { _cat = MelonPreferences.CreateCategory("SleddingMouseFix"); _enabled = _cat.CreateEntry("Enabled", true, (string)null, "Short-circuit WM_NCHITTEST inside the client area to stop the mouse-movement freeze.", false, false, (ValueValidator)null, (string)null); _logStats = _cat.CreateEntry("LogStats", false, (string)null, "Log how many hit-tests were handled each 10s (diagnostic).", false, false, (ValueValidator)null, (string)null); ((MelonBase)this).LoggerInstance.Msg("Sledding Mouse Stutter Fix loaded. Enabled=" + _enabled.Value); } private static double NowMs() { return (double)Stopwatch.GetTimestamp() * 1000.0 / (double)Stopwatch.Frequency; } public override void OnUpdate() { double num = NowMs(); if (num >= _nextCheckMs) { _nextCheckMs = num + 1000.0; EnsureHooked(); } if (_logStats.Value && num >= _nextStatsMs) { _nextStatsMs = num + 10000.0; if (_installed) { ((MelonBase)this).LoggerInstance.Msg("hit-tests handled=" + _handled + " passedThrough=" + _passedThrough); } } } private void EnsureHooked() { try { if (!_enabled.Value) { if (_installed) { Unhook(); } return; } IntPtr intPtr = FindGameWindow(); if (intPtr == IntPtr.Zero) { return; } if (_installed && intPtr == _hwnd) { if (GetWindowLongPtrW(_hwnd, -4) == _myProcPtr) { return; } _installed = false; } else if (_installed && intPtr != _hwnd) { Unhook(); } _hwnd = intPtr; if (_keepAlive == null) { _keepAlive = MyProc; _myProcPtr = Marshal.GetFunctionPointerForDelegate(_keepAlive); } IntPtr intPtr2 = SetWindowLongPtrW(_hwnd, -4, _myProcPtr); if (intPtr2 == IntPtr.Zero) { ((MelonBase)this).LoggerInstance.Warning("could not subclass window (err " + Marshal.GetLastWin32Error() + ")"); return; } _prevProc = intPtr2; _installed = true; ((MelonBase)this).LoggerInstance.Msg("attached to game window 0x" + _hwnd.ToInt64().ToString("X")); } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Warning("EnsureHooked: " + ex.Message); } } private void Unhook() { try { if (_installed && _hwnd != IntPtr.Zero && _prevProc != IntPtr.Zero && GetWindowLongPtrW(_hwnd, -4) == _myProcPtr) { SetWindowLongPtrW(_hwnd, -4, _prevProc); } } catch { } _installed = false; } private static IntPtr FindGameWindow() { IntPtr found = IntPtr.Zero; IntPtr firstVisible = IntPtr.Zero; try { EnumThreadWindows(GetCurrentThreadId(), delegate(IntPtr h, IntPtr p) { if (!IsWindowVisible(h)) { return true; } if (firstVisible == IntPtr.Zero) { firstVisible = h; } StringBuilder stringBuilder = new StringBuilder(256); GetClassNameW(h, stringBuilder, stringBuilder.Capacity); if (stringBuilder.ToString().IndexOf("Unity", StringComparison.OrdinalIgnoreCase) >= 0) { found = h; } return true; }, IntPtr.Zero); } catch { } if (!(found != IntPtr.Zero)) { return firstVisible; } return found; } private static IntPtr MyProc(IntPtr h, uint msg, IntPtr w, IntPtr l) { if (msg == 132) { int x = (short)((long)l & 0xFFFF); int y = (short)(((long)l >> 16) & 0xFFFF); POINT p = new POINT { X = x, Y = y }; if (ScreenToClient(h, ref p) && GetClientRect(h, out var r) && p.X >= r.Left && p.X < r.Right && p.Y >= r.Top && p.Y < r.Bottom) { _handled++; return (IntPtr)1; } _passedThrough++; } try { return CallWindowProcW(_prevProc, h, msg, w, l); } catch { return IntPtr.Zero; } } public override void OnDeinitializeMelon() { Unhook(); } } }