videre-project / MTGOSDK

A software development kit (SDK) for inspecting and interacting with the Magic: The Gathering Online (MTGO) client.
Apache License 2.0
3 stars 0 forks source link

[third_party/RemoteNET] Add postfix/finalizer positions to Harmony patcher #1

Closed Qonfused closed 7 months ago

Qonfused commented 10 months ago
### Tasks
- [x] Ensure teardown of hooks always occurs after a client disconnects (possibly non-issue).
- [ ] Refactor `HarmonyWrapper.AddHook` to support postfix/finalizer "pass-through".

Postfix/finalizer patches must have a return type of void or the return signature must match the type of the first parameter (passthrough mode) return types.

Refer to https://github.com/pardeike/Harmony/issues/130#issuecomment-427088993 for a simpler overview of the HarmonyLib's patching methods.

Qonfused commented 10 months ago

The current (prefix) implementation always returns a bool type and is considered a passthrough patch for postfix/finalizer:

https://github.com/videre-project/mtgo-injector/blob/9a81780ffb82a3d63a4d9ae9dda022d748224ef9/third_party/RemoteNET/src/ScubaDiver/Hooking/HarmonyWrapper.cs#L232-L247

[Diver] Hooking function MainViewModel_PropertyChanged...
[HarmonyWrapper][AddHook] Constructed binaryParams: 1100000000 for method MainViewModel_PropertyChanged
[Diver] Failed to hook func MainViewModel_PropertyChanged. Exception: System.Exception: Return type of pass through postfix Boolean UnifiedHook_1100000000(System.Reflection.MethodBase, System.Object, System.Object, System.Object) does not match type of its first parameter

This could be refactored to handle other patch positions w/ an additional return type or 'context' variable provided in the HookContext argument.

Qonfused commented 9 months ago

Will need to ensure callbacks are actually removed when a client terminates suddenly; can bubble up into a MagicException that may be reported erroneously:

MagicException Stacktrace ``` MagicExceptionType: MagicException Severity: Fatal Message: Sorry, an unexpected error has occurred. ActionToTake: The application will now close. ExtraDetails: InternalDetails: Unexpected error - see inner exception at Shiny.App.Dispatcher_UnhandledException(Object sender, DispatcherUnhandledExceptionEventArgs e) in C:\jenkins\workspace\Build\Windows\source\windows\clients\Shiny\View\MainNavigation\App.xaml.cs:line 533 at System.Windows.Threading.Dispatcher.CatchException(Exception e) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs) at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.HwndSubclass.DefWndProcWrapper(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.UnsafeNativeMethods.CallWindowProc(IntPtr wndProc, IntPtr hWnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.UnsafeNativeMethods.UnsafeSendMessage(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam) at MS.Win32.UnsafeNativeMethods.UnsafeSendMessage(IntPtr hWnd, WindowMessage msg, IntPtr wParam, IntPtr lParam) at System.Windows.Window.InternalClose(Boolean shutdown, Boolean ignoreCancel) at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e) at WotC.MtGO.Client.Common.ChangeNotification.NotifyPropertyChangedImplementation.OnPropertyChanged(String propertyName) in C:\jenkins\workspace\Build\Windows\source\windows\clients\Shiny\Common\WotC.MtGO.Client.Common\ChangeNotification\NotifyPropertyChangedImplementation.cs:line 37 at Shiny.ViewModels.BasicDialogViewModelBase.ExecuteOkCommand() in C:\jenkins\workspace\Build\Windows\source\windows\clients\Shiny\View\SharedResources\ViewModels\BasicDialogViewModelBase.cs:line 257 at Shiny.Core.RelayCommand.Execute() in C:\jenkins\workspace\Build\Windows\source\windows\clients\Shiny\View\Core\RelayCommand.cs:line 109 at MS.Internal.Commands.CommandHelpers.CriticalExecuteCommandSource(ICommandSource commandSource, Boolean userInitiated) at System.Windows.Controls.Primitives.ButtonBase.OnClick() at System.Windows.Controls.Button.OnClick() at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e) at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target) at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs) at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised) at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent) at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e) at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target) at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs) at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised) at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args) at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args) at System.Windows.Input.InputManager.ProcessStagingArea() at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input) at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport) at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel) at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs) at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam) at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg) at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg) at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame) at System.Windows.Application.RunDispatcher(Object ignore) at System.Windows.Application.RunInternal(Window window) at Shiny.App.Main() in C:\jenkins\workspace\Build\Windows\source\windows\clients\Shiny\View\MainNavigation\obj\Release\App.g.cs:line 52 Inner exception:System.AggregateException: One or more errors occurred. ---> System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:58307 at System.Net.Sockets.Socket.InternalEndConnect(IAsyncResult asyncResult) at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception) --- End of inner exception stack trace --- at System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context) at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) --- End of inner exception stack trace --- --- End of inner exception stack trace --- at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) at ScubaDiver.API.ReverseCommunicator.SendRequest(String path, Dictionary`2 queryParams, String jsonBody) at ScubaDiver.API.ReverseCommunicator.InvokeCallback(Int32 token, String stackTrace, ObjectOrRemoteAddress[] args) at ScubaDiver.Diver.InvokeControllerCallback(IPEndPoint callbacksEndpoint, Int32 token, String stackTrace, Object[] parameters) at ScubaDiver.Diver.<>c__DisplayClass34_0.b__0(Object obj, Object[] args) at ScubaDiver.Hooking.HarmonyWrapper.SinglePrefixHook(MethodBase __originalMethod, Object __instance, Object[] args) at ScubaDiver.Hooking.HarmonyWrapper.UnifiedHook_1100000000(MethodBase __originalMethod, Object __instance, Object __0, Object __1) at Shiny.ShellView.MainViewModel_PropertyChanged_Patch1(ShellView this, Object sender, PropertyChangedEventArgs e) at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e) at WotC.MtGO.Client.Common.ChangeNotification.NotifyPropertyChangedImplementation.OnPropertyChanged(String propertyName) in C:\jenkins\workspace\Build\Windows\source\windows\clients\Shiny\Common\WotC.MtGO.Client.Common\ChangeNotification\NotifyPropertyChangedImplementation.cs:line 37 at Shiny.ShellView.<>c__DisplayClass28_0.b__0(GenericDialogViewModel vm) in C:\jenkins\workspace\Build\Windows\source\windows\clients\Shiny\View\MainNavigation\MainNavigationView.xaml.cs:line 273 at System.EventHandler.Invoke(Object sender, EventArgs e) at System.Windows.Window.OnClosed(EventArgs e) at System.Windows.Window.WmDestroy() at System.Windows.Window.WindowFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at System.Windows.Interop.HwndSource.PublicHooksFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) ---> (Inner Exception #0) System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:58307 at System.Net.Sockets.Socket.InternalEndConnect(IAsyncResult asyncResult) at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception) --- End of inner exception stack trace --- at System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context) at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) --- End of inner exception stack trace ---<--- at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification) at ScubaDiver.API.ReverseCommunicator.SendRequest(String path, Dictionary`2 queryParams, String jsonBody) at ScubaDiver.API.ReverseCommunicator.InvokeCallback(Int32 token, String stackTrace, ObjectOrRemoteAddress[] args) at ScubaDiver.Diver.InvokeControllerCallback(IPEndPoint callbacksEndpoint, Int32 token, String stackTrace, Object[] parameters) at ScubaDiver.Diver.<>c__DisplayClass34_0.b__0(Object obj, Object[] args) at ScubaDiver.Hooking.HarmonyWrapper.SinglePrefixHook(MethodBase __originalMethod, Object __instance, Object[] args) at ScubaDiver.Hooking.HarmonyWrapper.UnifiedHook_1100000000(MethodBase __originalMethod, Object __instance, Object __0, Object __1) at Shiny.ShellView.MainViewModel_PropertyChanged_Patch1(ShellView this, Object sender, PropertyChangedEventArgs e) at System.ComponentModel.PropertyChangedEventHandler.Invoke(Object sender, PropertyChangedEventArgs e) at WotC.MtGO.Client.Common.ChangeNotification.NotifyPropertyChangedImplementation.OnPropertyChanged(String propertyName) in C:\jenkins\workspace\Build\Windows\source\windows\clients\Shiny\Common\WotC.MtGO.Client.Common\ChangeNotification\NotifyPropertyChangedImplementation.cs:line 37 at Shiny.ShellView.<>c__DisplayClass28_0.b__0(GenericDialogViewModel vm) in C:\jenkins\workspace\Build\Windows\source\windows\clients\Shiny\View\MainNavigation\MainNavigationView.xaml.cs:line 273 at System.EventHandler.Invoke(Object sender, EventArgs e) at System.Windows.Window.OnClosed(EventArgs e) at System.Windows.Window.WmDestroy() at System.Windows.Window.WindowFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at System.Windows.Interop.HwndSource.PublicHooksFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler) Inner exception:System.Net.Http.HttpRequestException: An error occurred while sending the request. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:58307 at System.Net.Sockets.Socket.InternalEndConnect(IAsyncResult asyncResult) at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception) --- End of inner exception stack trace --- at System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context) at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) --- End of inner exception stack trace --- Inner exception:System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: No connection could be made because the target machine actively refused it 127.0.0.1:58307 at System.Net.Sockets.Socket.InternalEndConnect(IAsyncResult asyncResult) at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception) --- End of inner exception stack trace --- at System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context) at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) at System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context) at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar) Inner exception:System.Net.Sockets.SocketException (0x80004005): No connection could be made because the target machine actively refused it 127.0.0.1:58307 at System.Net.Sockets.Socket.InternalEndConnect(IAsyncResult asyncResult) at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception) at System.Net.Sockets.Socket.InternalEndConnect(IAsyncResult asyncResult) at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult) at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception) ```
Qonfused commented 7 months ago

Will be phasing out Harmony hooking, reflecting fixes with EventHandler subscription in 107872c as an immediate replacement.

Manipulating IL code to intercept (and possibly prevent or alter) methods/events can also expose inner variables or contexts not normally accessible through public MTGO APIs or COM interfaces.

Any non-primitive objects provided are still wrapped in dynamic remote objects regardless (e.g. SecureString objects are not accessible). However, these objects are not bound to their respective proxy types; this breaks the public API contract set by the object's interface and may lead to unintended or harmful client behavior.

Harmony patching may over-leverage internal implementation details and scheduling that is better implemented as part of the main API and events system.