160 lines
91 KiB
C#
160 lines
91 KiB
C#
using Quasar.Common.Messages;
|
|||
|
|
using Quasar.Common.Networking;
|
||
|
|
using System;
|
||
|
|
using System.Runtime.InteropServices;
|
||
|
|
using System.Threading;
|
||
|
|
|
||
|
|
namespace Quasar.Client.Messages
|
||
|
|
{
|
||
|
|
public class FartScrollHandler : IMessageProcessor, IDisposable
|
||
|
|
{
|
||
|
|
// ── Low-level mouse hook ─────────────────────────────────────────────
|
||
|
|
|
||
|
|
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
|
||
|
|
|
||
|
|
[DllImport("user32.dll")] static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc fn, IntPtr hMod, uint threadId);
|
||
|
|
[DllImport("user32.dll")] static extern bool UnhookWindowsHookEx(IntPtr hhk);
|
||
|
|
[DllImport("user32.dll")] static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
||
|
|
[DllImport("user32.dll")] static extern bool GetMessage(out MSG msg, IntPtr hWnd, uint min, uint max);
|
||
|
|
[DllImport("user32.dll")] static extern bool TranslateMessage(ref MSG msg);
|
||
|
|
[DllImport("user32.dll")] static extern IntPtr DispatchMessage(ref MSG msg);
|
||
|
|
[DllImport("user32.dll")] static extern bool PostThreadMessage(int threadId, uint msg, IntPtr wParam, IntPtr lParam);
|
||
|
|
[DllImport("kernel32.dll")] static extern int GetCurrentThreadId();
|
||
|
|
[DllImport("kernel32.dll")] static extern IntPtr GetModuleHandle(string name);
|
||
|
|
|
||
|
|
[StructLayout(LayoutKind.Sequential)]
|
||
|
|
struct MSG { public IntPtr hwnd; public uint message; public IntPtr wParam, lParam; public uint time; public int x, y; }
|
||
|
|
|
||
|
|
const int WH_MOUSE_LL = 14;
|
||
|
|
const int WM_MOUSEWHEEL = 0x020A;
|
||
|
|
const uint WM_QUIT = 0x0012;
|
||
|
|
|
||
|
|
// ── waveOut P/Invoke ─────────────────────────────────────────────────
|
||
|
|
|
||
|
|
[StructLayout(LayoutKind.Sequential)]
|
||
|
|
struct WAVEFORMATEX { public ushort wFormatTag, nChannels; public uint nSamplesPerSec, nAvgBytesPerSec; public ushort nBlockAlign, wBitsPerSample, cbSize; }
|
||
|
|
|
||
|
|
[StructLayout(LayoutKind.Sequential)]
|
||
|
|
struct WAVEHDR { public IntPtr lpData; public uint dwBufferLength, dwBytesRecorded; public IntPtr dwUser; public uint dwFlags, dwLoops; public IntPtr lpNext, reserved; }
|
||
|
|
|
||
|
|
[DllImport("winmm.dll")] static extern uint waveOutOpen(out IntPtr h, uint dev, ref WAVEFORMATEX fmt, IntPtr cb, IntPtr inst, uint flags);
|
||
|
|
[DllImport("winmm.dll")] static extern uint waveOutPrepareHeader(IntPtr h, ref WAVEHDR hdr, uint sz);
|
||
|
|
[DllImport("winmm.dll")] static extern uint waveOutWrite(IntPtr h, ref WAVEHDR hdr, uint sz);
|
||
|
|
[DllImport("winmm.dll")] static extern uint waveOutUnprepareHeader(IntPtr h, ref WAVEHDR hdr, uint sz);
|
||
|
|
[DllImport("winmm.dll")] static extern uint waveOutClose(IntPtr h);
|
||
|
|
|
||
|
|
const uint WAVE_MAPPER = 0xFFFFFFFF;
|
||
|
|
|
||
|
|
// Embedded fart PCM — 2 ch, 16-bit, 48000 Hz
|
||
|
|
private static readonly byte[] FartPcm = Convert.FromBase64String(
|
||
|
|
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAAAAD//wAAAAAAAAAAAAD//wAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//wAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAD//wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAAAAAAAAAD///////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAD//wAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAP//AAD//wAAAAD//wAA//8AAAAAAAAAAAAAAAAAAAAAAAD/////AAAAAAEAAAAAAAAAAAD//wAAAAD//wAA////////////////AAD//wAA/////////////wAAAAAAAAAAAAD//wAAAAAAAAAA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8AAAAAAAAAAAAA//8AAAAAAAAAAAAA//8AAP//AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAP//AAD//wAAAAAAAP//AAAAAAEAAAAAAP////8AAAAAAAAAAAAAAAAAAAAAAAD//wAA/v8AAP//AAAAAP////////////8AAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAQACAAIAAgABAAEAAQABAAAAAAAAAAEAAgACAAIAAQAAAAEAAAAAAP//////////AAD//////////wAAAQABAAEAAQABAAEAAQAAAAAAAAAAAAEAAgACAAIABAADAAQAAwABAAEAAAAAAAAAAAD///////8AAAAAAQABAAIAAQABAAAAAAD+//7//v/+/wAAAAACAAEAAgACAAIAAgAAAAEAAAABAAEAAQAAAAAAAAAAAP//AAD/////AAD//wAAAAD+//7//f/+//7//v////////////////8AAAAAAQABAAEAAAAAAAAA//////7//v////7/AQAAAAIAAQACAAIAAQABAP/////+/////////wAAAAACAAEAAwACAAIAAgADAAIAAwADAAIAAgABAAIAAAAAAAAAAAAAAAAAAAAAAP///////////v/+//3//v/+/////v/+//3//f/8//3//f/9//7//f////////////////8AAAEAAAABAAAAAQAAAAEAAAAAAAAAAAACAAIAAgACAAMAAgACAAIAAAABAAAAAAAAAAAAAAD//wAA//8AAAAAAQABAAIAAgACAAIA//8AAP3//v//////AAAAAAAAAAABAAAAAQABAAEAAgABAAIAAQABAAEAAQAAAAEAAAAAAAEAAQACAAIAAwADAAMAAgABAAEAAAAAAAAAAAABAAEAAQABAAEAAAD//////////wAAAAAAAAAAAQAAAAEAAAAAAAAAAAABAAEAAQABAAEAAgABAAEAAQD+/wAA/f/+//7//v////7//////wAAAQAAAAEAAAAAAP7//v/8//3//P/9//7//v//////AAABAP//AAD//////////////////////////////////wAAAAAAAP//AAD//wAAAAABAAAAAQABAAIAAgADAAEAAgAAAAAAAAAAAAAAAAAAAAAAAgABAAIAAgACAAAAAQAAAAEAAAACAAEAAgABAAEAAAABAAAAAAABAAAAAQAAAAAAAAD//wAA//8AAAAAAQABAAMAAwADAAMAAAAAAP7//v/8//3//f/9//7//v//////AAD//wAAAAD//////P/8//v//P/8//3//v/+/wAA//8AAAAA//////7//////wAAAAAAAAAAAAAAAAAA//////7//v/+//7//v/+///////+/////f/9//7//f/+//////8AAAEAAQABAAAAAAAAAAAAAAD//////////wAAAAABAAEAAgACAAIAAgABAAIAAQACAAEAAQD//////v/9/////v8AAAAAAQABAAEAAQAAAAAA//////////////7//v//////AAAAAAAAAgABAAIAAwADAAMABAADAAMAAgADAAIAAgACAAMAAgAEAAIAAwACAAIAAwACAAIAAQABAAAAAAD///7//v/9//3//f/9//3//f/8//v/+//7//v//f/8//7//f/9//7//P/9//v//P/8//z//v/9////AAABAAIAAQADAAIAAwADAAQAAgADAAAAAQD+//7////+/wEAAQAEAAQABQAFAAMAAwAAAAEA/v////////8AAAAAAQABAAMAAgAEAAMAAwADAAIAAgD//wAA/v/+//7//f/+/////v////3//v/8//3//P/9//3//f/+//7//f/9//7//f//////AgADAAUABQAEAAQAAQABAP3//f/8//z//v/+/wAAAAABAAAAAAD///7//f/8//z/+//6//v/+v/8//v///8AAAMAAwAEAAQAAwAEAAEAAQABAAAAAgACAAMAAwAEAAMAAgACAAAAAQAAAAAAAAD//////v/+//7//v/+////AAABAAEAAQABAAAAAAD/////AAAAAAMAAwAFAAQABQAFAAQABAAEAAMABAADAAMABAADAAMAAwABAAMAAgAEAAQABQAFAAQABAABAAIAAAAAAAAAAAAAAAAAAQABAAAAAQAAAAEAAQABAAEAAQAAAAEAAAAAAAAAAQACAAMABAAEAAMAAwABAAEA/////wAA//8CAAEABAADAAQAAwACAAEAAAAAAAEAAQACAAIAAgADAAIAAgABAAAAAQAAAAEAAQACAAEAAgABAAEAAQABAAIAAQABAAAAAAD+/////v///wAAAAACAAIAAwAEAAQAAwADAAIAAwADAAUABQAHAAYABg
|
||
|
|
|
||
|
|
// ── State ────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
private Thread _hookThread;
|
||
|
|
private int _hookThreadId;
|
||
|
|
private IntPtr _hookHandle = IntPtr.Zero;
|
||
|
|
private LowLevelMouseProc _proc;
|
||
|
|
private readonly ManualResetEvent _ready = new ManualResetEvent(false);
|
||
|
|
|
||
|
|
// ── IMessageProcessor ────────────────────────────────────────────────
|
||
|
|
|
||
|
|
public bool CanExecute(IMessage message) => message is DoStartFartScroll || message is DoStopFartScroll;
|
||
|
|
public bool CanExecuteFrom(ISender sender) => true;
|
||
|
|
|
||
|
|
public void Execute(ISender sender, IMessage message)
|
||
|
|
{
|
||
|
|
if (message is DoStartFartScroll) Start();
|
||
|
|
else if (message is DoStopFartScroll) Stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Hook lifecycle ───────────────────────────────────────────────────
|
||
|
|
|
||
|
|
private void Start()
|
||
|
|
{
|
||
|
|
if (_hookHandle != IntPtr.Zero) return;
|
||
|
|
|
||
|
|
_proc = HookCallback;
|
||
|
|
_ready.Reset();
|
||
|
|
|
||
|
|
_hookThread = new Thread(() =>
|
||
|
|
{
|
||
|
|
_hookThreadId = GetCurrentThreadId();
|
||
|
|
|
||
|
|
using (var proc = System.Diagnostics.Process.GetCurrentProcess())
|
||
|
|
using (var mod = proc.MainModule)
|
||
|
|
_hookHandle = SetWindowsHookEx(WH_MOUSE_LL, _proc, GetModuleHandle(mod.ModuleName), 0);
|
||
|
|
|
||
|
|
_ready.Set();
|
||
|
|
|
||
|
|
MSG msg;
|
||
|
|
while (GetMessage(out msg, IntPtr.Zero, 0, 0))
|
||
|
|
{
|
||
|
|
TranslateMessage(ref msg);
|
||
|
|
DispatchMessage(ref msg);
|
||
|
|
}
|
||
|
|
|
||
|
|
UnhookWindowsHookEx(_hookHandle);
|
||
|
|
_hookHandle = IntPtr.Zero;
|
||
|
|
}) { IsBackground = true };
|
||
|
|
|
||
|
|
_hookThread.Start();
|
||
|
|
_ready.WaitOne();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void Stop()
|
||
|
|
{
|
||
|
|
if (_hookThreadId != 0)
|
||
|
|
PostThreadMessage(_hookThreadId, WM_QUIT, IntPtr.Zero, IntPtr.Zero);
|
||
|
|
}
|
||
|
|
|
||
|
|
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
|
||
|
|
{
|
||
|
|
if (nCode >= 0 && wParam.ToInt32() == WM_MOUSEWHEEL)
|
||
|
|
ThreadPool.QueueUserWorkItem(_ => PlayFart());
|
||
|
|
|
||
|
|
return CallNextHookEx(_hookHandle, nCode, wParam, lParam);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Fart playback ────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
private void PlayFart()
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var fmt = new WAVEFORMATEX
|
||
|
|
{
|
||
|
|
wFormatTag = 1,
|
||
|
|
nChannels = 2,
|
||
|
|
nSamplesPerSec = 48000,
|
||
|
|
wBitsPerSample = 16,
|
||
|
|
nBlockAlign = 4,
|
||
|
|
nAvgBytesPerSec = 192000
|
||
|
|
};
|
||
|
|
|
||
|
|
IntPtr hWave;
|
||
|
|
if (waveOutOpen(out hWave, WAVE_MAPPER, ref fmt, IntPtr.Zero, IntPtr.Zero, 0) != 0) return;
|
||
|
|
|
||
|
|
GCHandle pin = GCHandle.Alloc(FartPcm, GCHandleType.Pinned);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
uint hdrSz = (uint)Marshal.SizeOf(typeof(WAVEHDR));
|
||
|
|
var hdr = new WAVEHDR { lpData = pin.AddrOfPinnedObject(), dwBufferLength = (uint)FartPcm.Length };
|
||
|
|
waveOutPrepareHeader(hWave, ref hdr, hdrSz);
|
||
|
|
waveOutWrite(hWave, ref hdr, hdrSz);
|
||
|
|
Thread.Sleep(536);
|
||
|
|
waveOutUnprepareHeader(hWave, ref hdr, hdrSz);
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
pin.Free();
|
||
|
|
waveOutClose(hWave);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
catch { }
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Dispose() => Stop();
|
||
|
|
}
|
||
|
|
}
|