From dbf97beb41b7c86f3678341e09257fd3f0333df3 Mon Sep 17 00:00:00 2001 From: Thomas Mathieson Date: Thu, 14 Dec 2023 15:24:22 +0000 Subject: [PATCH] Bugfixes for plugins: - async D3DTexture methods now correctly return a Task and can be awaited - OmsiRemoteMethods now actually has the option of using the local OmsiHookInvoker instead of RPC if available - This also fixes the race condition when loading a plugin using OmsiHook might have required the RPC plugin to already be loaded - Updated OmsiHookPlugin demo - Started work on function hooking code --- OmsiExtensionsCLI/Program.cs | 6 +- OmsiHook/D3DTexture.cs | 12 +- OmsiHook/OmsiHook.cs | 10 +- OmsiHook/OmsiHook.csproj | 6 +- OmsiHook/OmsiRemoteMethods.cs | 462 ++++++++++++------------ OmsiHookInvoker/FunctionHooks.cpp | 63 ++++ OmsiHookInvoker/FunctionHooks.h | 14 + OmsiHookInvoker/OmsiHookInvoker.vcxproj | 4 +- OmsiHookInvoker/dllmain.cpp | 10 + OmsiHookPlugin/OmsiHookPlugin.cs | 69 ++-- OmsiHookPlugin/OmsiHookPlugin.opl | 2 +- 11 files changed, 395 insertions(+), 263 deletions(-) create mode 100644 OmsiHookInvoker/FunctionHooks.cpp create mode 100644 OmsiHookInvoker/FunctionHooks.h diff --git a/OmsiExtensionsCLI/Program.cs b/OmsiExtensionsCLI/Program.cs index 9eadbdf..8655891 100644 --- a/OmsiExtensionsCLI/Program.cs +++ b/OmsiExtensionsCLI/Program.cs @@ -92,7 +92,7 @@ public void CreateTexture() try { - texture.CreateD3DTexture(texWidth, texHeight); + texture.CreateD3DTexture(texWidth, texHeight).Wait(); } catch (Exception ex) { Console.WriteLine(ex); @@ -128,17 +128,19 @@ public void UpdateTexture() } iter++; - texture.UpdateTexture(texBuffer.AsMemory(), new OmsiRemoteMethods.Rectangle() { left=0, top=0, right=texWidth/2, bottom=texHeight/2}); + texture.UpdateTexture(texBuffer.AsMemory(), new OmsiRemoteMethods.Rectangle() { left=0, top=0, right=texWidth, bottom=texHeight}).Wait(); } private void Omsi_OnOmsiGotD3DContext(object sender, EventArgs e) { // d3dGotContext.Set(); + Console.WriteLine("Got D3D Context!"); } private void Omsi_OnMapLoaded(object sender, bool e) { d3dGotContext.Set(); + Console.WriteLine("Map Loaded!"); } } } diff --git a/OmsiHook/D3DTexture.cs b/OmsiHook/D3DTexture.cs index ad1003a..ff4a2d3 100644 --- a/OmsiHook/D3DTexture.cs +++ b/OmsiHook/D3DTexture.cs @@ -52,7 +52,7 @@ public D3DTexture() : base() { } /// the address of the existing texture /// /// - public async void CreateFromExisting(uint address) + public async Task CreateFromExisting(uint address) { if (Address == 0) throw new NullReferenceException("Texture was already null!"); @@ -77,10 +77,10 @@ public async void CreateFromExisting(uint address) /// the height of the texture /// the of the texture /// - public async void CreateD3DTexture(uint width, uint height, D3DFORMAT format = D3DFORMAT.D3DFMT_A8R8G8B8) + public async Task CreateD3DTexture(uint width, uint height, D3DFORMAT format = D3DFORMAT.D3DFMT_A8R8G8B8) { if(Address != 0) - ReleaseTexture(); + await ReleaseTexture(); var (hresult, pTexture) = await OmsiCreateTextureAsync(width, height, format); if (HRESULTFailed(hresult)) @@ -100,7 +100,7 @@ public async void CreateD3DTexture(uint width, uint height, D3DFORMAT format = D /// /// /// - public async void ReleaseTexture() + public async Task ReleaseTexture() { if(Address == 0) throw new NullReferenceException("Texture was already null!"); @@ -123,7 +123,7 @@ public async void ReleaseTexture() /// must match the size of this rectangle; pass in to update the whole texture /// /// - public void UpdateTexture(Memory textureData, Rectangle? updateArea = null) where T : unmanaged + public async Task UpdateTexture(Memory textureData, Rectangle? updateArea = null) where T : unmanaged { if((textureData.Length * Marshal.SizeOf()) > stagingBufferSize) throw new ArgumentOutOfRangeException(nameof(textureData)); @@ -132,7 +132,7 @@ public void UpdateTexture(Memory textureData, Rectangle? updateArea = null uint dataWidth = (updateArea?.right - updateArea?.left) ?? width; uint dataHeight = (updateArea?.bottom - updateArea?.top) ?? height; - HRESULT hr = OmsiUpdateTextureAsync(TextureAddress, remoteStagingBufferPtr, dataWidth, dataHeight, updateArea).Result; + HRESULT hr = await OmsiUpdateTextureAsync(TextureAddress, remoteStagingBufferPtr, dataWidth, dataHeight, updateArea); if (HRESULTFailed(hr)) throw new Exception("Couldn't update D3D texture! Result: " + hr); } diff --git a/OmsiHook/OmsiHook.cs b/OmsiHook/OmsiHook.cs index cd8c559..cb243d3 100644 --- a/OmsiHook/OmsiHook.cs +++ b/OmsiHook/OmsiHook.cs @@ -57,6 +57,8 @@ public class OmsiHook public event EventHandler OnOmsiGotD3DContext; /// /// An event raised when Omsi gets a DirectX context. + /// + /// Note that this is one of the last events raised when exiting the game; it's raised after PluginFinalize is called. /// /// /// @@ -71,6 +73,10 @@ public class OmsiHook public event EventHandler OnMapChange; /// /// An event raised when Omsi has loaded or unloaded a new map. The EventArgs is a boolean representing whether the map is loaded. + /// + /// Note that while this event is raised when the map has finished loading; other systems may still be + /// loading (timetables, weather, humans, ai vehicles, and the map camera are loaded later). If you want to be sure + /// everything has loaded, the best bet would be to wait for an AccessVariable() call. /// /// /// @@ -85,8 +91,8 @@ public class OmsiHook /// /// Attaches the hooking application to OMSI.exe. /// Always call this at some point before trying to read and write data. - /// Try to initialise the connection to OmsiHookRPCPlugin, which is needed if you intend to call Omsi code or allocate memory. /// + /// Try to initialise the connection to OmsiHookRPCPlugin, which is needed if you intend to call Omsi code or allocate memory. public async Task AttachToOMSI(bool initialiseRemoteMethods = true) { omsiMemory = new Memory(); @@ -113,7 +119,7 @@ public async Task AttachToOMSI(bool initialiseRemoteMethods = true) if(initialiseRemoteMethods) { - await OmsiRemoteMethods.InitRemoteMethods(omsiMemory); + await OmsiRemoteMethods.InitRemoteMethods(omsiMemory, isLocalPlugin: Process.GetCurrentProcess().ProcessName == process.ProcessName); } stateMonitorTask = new(MonitorStateTask); diff --git a/OmsiHook/OmsiHook.csproj b/OmsiHook/OmsiHook.csproj index ae479e1..1450bda 100644 --- a/OmsiHook/OmsiHook.csproj +++ b/OmsiHook/OmsiHook.csproj @@ -10,9 +10,9 @@ OmsiHook is a simple library for hooking into Omsi's memory for modding. true true - 2.3.1.1 - 2.3.1.1 - 2.3.1 + 2.3.2.1 + 2.3.2.1 + 2.3.2 LGPL-3.0-only False README.md diff --git a/OmsiHook/OmsiRemoteMethods.cs b/OmsiHook/OmsiRemoteMethods.cs index 4f2827a..e943fc6 100644 --- a/OmsiHook/OmsiRemoteMethods.cs +++ b/OmsiHook/OmsiRemoteMethods.cs @@ -21,17 +21,21 @@ public static class OmsiRemoteMethods private static ConcurrentDictionary> resultPromises; private static Task resultReaderThread; private static Memory memory; + private static bool localPlugin; private static readonly ThreadLocal asyncWriteBuff = new(() => new byte[256]); public static bool IsInitialised => (pipeRX?.IsConnected ?? false) && (pipeTX?.IsConnected ?? false); // TODO: Okay maybe this shouldn't be static... Singleton? - internal static async Task InitRemoteMethods(Memory omsiMemory, bool inifiniteTimeout = false) + internal static async Task InitRemoteMethods(Memory omsiMemory, bool inifiniteTimeout = false, bool isLocalPlugin = false) { memory = omsiMemory; + localPlugin = isLocalPlugin; + + if (localPlugin) + return; -#if !OMSI_PLUGIN resultPromises = new(); // We swap rx and tx here so that it makes semantic sense (since the tx of the client goes to the rx of the server) @@ -54,7 +58,6 @@ internal static async Task InitRemoteMethods(Memory omsiMemory, bool inifiniteTi resultReaderThread = new Task(ResultReaderTask); resultReaderThread.Start(); -#endif } /// @@ -102,7 +105,8 @@ public static int MakeVehicle() public static void CloseRPCSession(bool killAllConnections) { -#if !OMSI_PLUGIN + if (!localPlugin) + return; if (!IsInitialised) throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); @@ -117,7 +121,6 @@ public static void CloseRPCSession(bool killAllConnections) lock (pipeTX) pipeTX.Write(writeBuffer); // promise.Task.Wait(); -#endif } /// @@ -133,37 +136,40 @@ public static void CloseRPCSession(bool killAllConnections) /// The index of the vehicle that was placed. public static int PlaceRandomBus(int aiType = 0, int group = 1, int type = -1, bool scheduled = false, int tour = 0, int line = 0) { -#if OMSI_PLUGIN - return TProgManPlaceRandomBus(memory.ReadMemory(0x00862f28), aiType, group, 0, false, true, type, scheduled, 0, tour, line); -#else - if (!IsInitialised) - throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); - - int argPos = 0; - var method = OmsiHookRPCMethods.RemoteMethod.TProgManPlaceRandomBus; - Span writeBuffer = stackalloc byte[OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8]; - //Span readBuffer = stackalloc byte[4]; - (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); - BitConverter.TryWriteBytes(writeBuffer[(argPos)..], (int)method); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], resultPromise); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], aiType); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], group); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], 0); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 1)..], false); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 1)..], true); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], type); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 1)..], scheduled); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], 0); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], aiType); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], tour); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], line); - lock (pipeTX) - pipeTX.Write(writeBuffer); - return promise.Task.Result; - //lock (pipeRX) - // pipeRX.Read(readBuffer); - //return BitConverter.ToInt32(readBuffer); -#endif + if (localPlugin) + { + return TProgManPlaceRandomBus(memory.ReadMemory(0x00862f28), aiType, group, 0, false, true, type, scheduled, 0, tour, line); + } + else + { + if (!IsInitialised) + throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); + + int argPos = 0; + var method = OmsiHookRPCMethods.RemoteMethod.TProgManPlaceRandomBus; + Span writeBuffer = stackalloc byte[OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8]; + //Span readBuffer = stackalloc byte[4]; + (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); + BitConverter.TryWriteBytes(writeBuffer[(argPos)..], (int)method); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], resultPromise); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], aiType); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], group); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], 0); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 1)..], false); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 1)..], true); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], type); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 1)..], scheduled); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], 0); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], aiType); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], tour); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], line); + lock (pipeTX) + pipeTX.Write(writeBuffer); + return promise.Task.Result; + //lock (pipeRX) + // pipeRX.Read(readBuffer); + //return BitConverter.ToInt32(readBuffer); + } } /// @@ -175,28 +181,29 @@ public static int PlaceRandomBus(int aiType = 0, int group = 1, int type = -1, b /// VirtualProtect it to access it). public static async Task OmsiGetMem(int length) { -#if OMSI_PLUGIN - return (uint)GetMem(length); -#else - if(!IsInitialised) - return 0; - - int argPos = 0; - var method = OmsiHookRPCMethods.RemoteMethod.GetMem; - int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; - // This should be thread safe as the asyncWriteBuff is thread local - byte[] writeBuffer = asyncWriteBuff.Value; - (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], length); - lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); - return (uint)await promise.Task; - /*lock (pipeRX) - pipeRX.Read(readBuffer); - return BitConverter.ToUInt32(readBuffer);*/ -#endif + if (localPlugin) + return (uint)GetMem(length); + else + { + if (!IsInitialised) + return 0; + + int argPos = 0; + var method = OmsiHookRPCMethods.RemoteMethod.GetMem; + int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; + // This should be thread safe as the asyncWriteBuff is thread local + byte[] writeBuffer = asyncWriteBuff.Value; + (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], length); + lock (pipeTX) + pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + return (uint)await promise.Task; + /*lock (pipeRX) + pipeRX.Read(readBuffer); + return BitConverter.ToUInt32(readBuffer);*/ + } } /// @@ -206,23 +213,24 @@ public static async Task OmsiGetMem(int length) /// The pointer to the object to deallocate public static void OmsiFreeMemAsync(int addr) { -#if OMSI_PLUGIN - FreeMem(addr); -#else - if (!IsInitialised) - throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); - - int argPos = 0; - var method = OmsiHookRPCMethods.RemoteMethod.FreeMem; - Span writeBuffer = stackalloc byte[OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8]; - (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); - BitConverter.TryWriteBytes(writeBuffer[(argPos)..], (int)method); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], resultPromise); - BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], addr); - lock (pipeTX) - pipeTX.Write(writeBuffer); - //promise.AsTask().Wait(); -#endif + if (localPlugin) + FreeMem(addr); + else + { + if (!IsInitialised) + throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); + + int argPos = 0; + var method = OmsiHookRPCMethods.RemoteMethod.FreeMem; + Span writeBuffer = stackalloc byte[OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8]; + (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); + BitConverter.TryWriteBytes(writeBuffer[(argPos)..], (int)method); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], resultPromise); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], addr); + lock (pipeTX) + pipeTX.Write(writeBuffer); + //promise.AsTask().Wait(); + } } /// @@ -230,22 +238,23 @@ public static void OmsiFreeMemAsync(int addr) /// public static bool OmsiHookD3D() { -#if OMSI_PLUGIN - return HookD3D() != 0; -#else - if (!IsInitialised) - throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); - - int argPos = 0; - Span writeBuffer = stackalloc byte[8]; - (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); - BitConverter.TryWriteBytes(writeBuffer[(argPos)..], (int)OmsiHookRPCMethods.RemoteMethod.HookD3D); - BitConverter.TryWriteBytes(writeBuffer[(argPos+=4)..], resultPromise); - lock (pipeTX) - pipeTX.Write(writeBuffer); - var res = promise.Task.Result; - return res != 0; -#endif + if (localPlugin) + return HookD3D() != 0; + else + { + if (!IsInitialised) + throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); + + int argPos = 0; + Span writeBuffer = stackalloc byte[8]; + (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); + BitConverter.TryWriteBytes(writeBuffer[(argPos)..], (int)OmsiHookRPCMethods.RemoteMethod.HookD3D); + BitConverter.TryWriteBytes(writeBuffer[(argPos += 4)..], resultPromise); + lock (pipeTX) + pipeTX.Write(writeBuffer); + var res = promise.Task.Result; + return res != 0; + } } /// @@ -253,36 +262,39 @@ public static bool OmsiHookD3D() /// public static async Task<(HRESULT hresult, uint pTexture)> OmsiCreateTextureAsync(uint width, uint height, D3DFORMAT format) { -#if OMSI_PLUGIN - uint ppTexture = OmsiGetMem(4).Result; - HRESULT hresult = (HRESULT)CreateTexture(width, height, (uint)format, ppTexture); - return (hresult, ppTexture); -#else - if (!IsInitialised) - throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); - - // Allocate the pointers - int ppTexture = memory.AllocRemoteMemory(4, true).Result;//OmsiGetMem(4).Result; - memory.WriteMemory(ppTexture, 0); - - int argPos = 0; - var method = OmsiHookRPCMethods.RemoteMethod.CreateTexture; - // This should be thread safe as the asyncWriteBuff is thread local - int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; - byte[] writeBuffer = asyncWriteBuff.Value; - (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], width); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], height); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], (uint)format); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], ppTexture); - lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); - HRESULT result = (HRESULT)await promise.Task; - uint pTexture = memory.ReadMemory(ppTexture); - return (result, pTexture); -#endif + if (localPlugin) + { + uint ppTexture = OmsiGetMem(4).Result; + HRESULT hresult = (HRESULT)CreateTexture(width, height, (uint)format, ppTexture); + return (hresult, ppTexture); + } + else + { + if (!IsInitialised) + throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); + + // Allocate the pointers + int ppTexture = memory.AllocRemoteMemory(4, true).Result;//OmsiGetMem(4).Result; + memory.WriteMemory(ppTexture, 0); + + int argPos = 0; + var method = OmsiHookRPCMethods.RemoteMethod.CreateTexture; + // This should be thread safe as the asyncWriteBuff is thread local + int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; + byte[] writeBuffer = asyncWriteBuff.Value; + (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], width); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], height); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], (uint)format); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], ppTexture); + lock (pipeTX) + pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + HRESULT result = (HRESULT)await promise.Task; + uint pTexture = memory.ReadMemory(ppTexture); + return (result, pTexture); + } } /// @@ -297,34 +309,37 @@ public static bool OmsiHookD3D() /// public static async Task OmsiUpdateTextureAsync(uint texturePtr, uint textureDataPtr, uint width, uint height, Rectangle? updateRect = null) { -#if OMSI_PLUGIN - return (HRESULT)UpdateSubresource(texturePtr, textureDataPtr, width, height, - updateRect.HasValue ? 1 : 0, updateRect?.left??0, updateRect?.top??0, updateRect?.right??0, updateRect?.bottom??0); -#else - if (!IsInitialised) - throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); - - int argPos = 0; - var method = OmsiHookRPCMethods.RemoteMethod.UpdateSubresource; - // This should be thread safe as the asyncWriteBuff is thread local - int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; - byte[] writeBuffer = asyncWriteBuff.Value; - (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], texturePtr); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], textureDataPtr); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], width); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], height); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], updateRect.HasValue ? 1 : 0); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], updateRect?.left ?? 0); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], updateRect?.top ?? 0); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], updateRect?.right ?? 0); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], updateRect?.bottom ?? 0); - lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); - return (HRESULT)await promise.Task; -#endif + if (localPlugin) + { + return (HRESULT)UpdateSubresource(texturePtr, textureDataPtr, width, height, + updateRect.HasValue ? 1 : 0, updateRect?.left ?? 0, updateRect?.top ?? 0, updateRect?.right ?? 0, updateRect?.bottom ?? 0); + } + else + { + if (!IsInitialised) + throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); + + int argPos = 0; + var method = OmsiHookRPCMethods.RemoteMethod.UpdateSubresource; + // This should be thread safe as the asyncWriteBuff is thread local + int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; + byte[] writeBuffer = asyncWriteBuff.Value; + (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], texturePtr); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], textureDataPtr); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], width); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], height); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], updateRect.HasValue ? 1 : 0); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], updateRect?.left ?? 0); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], updateRect?.top ?? 0); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], updateRect?.right ?? 0); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], updateRect?.bottom ?? 0); + lock (pipeTX) + pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + return (HRESULT)await promise.Task; + } } /// @@ -335,25 +350,26 @@ public static async Task OmsiUpdateTextureAsync(uint texturePtr, uint t /// public static async Task OmsiReleaseTextureAsync(uint texturePtr) { -#if OMSI_PLUGIN - return (HRESULT)ReleaseTexture(texturePtr); -#else - if (!IsInitialised) - throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); - - int argPos = 0; - var method = OmsiHookRPCMethods.RemoteMethod.ReleaseTexture; - // This should be thread safe as the asyncWriteBuff is thread local - int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; - byte[] writeBuffer = asyncWriteBuff.Value; - (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], texturePtr); - lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); - return (HRESULT)await promise.Task; -#endif + if (localPlugin) + return (HRESULT)ReleaseTexture(texturePtr); + else + { + if (!IsInitialised) + throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); + + int argPos = 0; + var method = OmsiHookRPCMethods.RemoteMethod.ReleaseTexture; + // This should be thread safe as the asyncWriteBuff is thread local + int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; + byte[] writeBuffer = asyncWriteBuff.Value; + (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], texturePtr); + lock (pipeTX) + pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + return (HRESULT)await promise.Task; + } } /// @@ -365,42 +381,45 @@ public static async Task OmsiReleaseTextureAsync(uint texturePtr) public static async Task<(HRESULT hresult, uint width, uint height, D3DFORMAT format)> OmsiGetTextureDescAsync(uint texturePtr) { uint descPtr = unchecked((uint)await memory.AllocRemoteMemory(4 * 3, true)); -#if OMSI_PLUGIN - HRESULT res = (HRESULT)GetTextureDesc(texturePtr, descPtr, descPtr + 4, descPtr + 8); - - uint width = memory.ReadMemory(descPtr); - uint height = memory.ReadMemory(descPtr+4); - D3DFORMAT format = (D3DFORMAT)memory.ReadMemory(descPtr+8); - memory.FreeRemoteMemory(descPtr, true); - - return (res, width, height, format); -#else - if (!IsInitialised) - throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); - - int argPos = 0; - var method = OmsiHookRPCMethods.RemoteMethod.GetTextureDesc; - // This should be thread safe as the asyncWriteBuff is thread local - int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; - byte[] writeBuffer = asyncWriteBuff.Value; - (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], texturePtr); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], descPtr); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], descPtr+4); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], descPtr+8); - lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + if (localPlugin) + { + HRESULT res = (HRESULT)GetTextureDesc(texturePtr, descPtr, descPtr + 4, descPtr + 8); - HRESULT res = (HRESULT)await promise.Task; - uint width = memory.ReadMemory(descPtr); - uint height = memory.ReadMemory(descPtr + 4); - D3DFORMAT format = (D3DFORMAT)memory.ReadMemory(descPtr + 8); - memory.FreeRemoteMemory(descPtr, true); + uint width = memory.ReadMemory(descPtr); + uint height = memory.ReadMemory(descPtr + 4); + D3DFORMAT format = (D3DFORMAT)memory.ReadMemory(descPtr + 8); + memory.FreeRemoteMemory(descPtr, true); - return (res, width, height, format); -#endif + return (res, width, height, format); + } + else + { + if (!IsInitialised) + throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); + + int argPos = 0; + var method = OmsiHookRPCMethods.RemoteMethod.GetTextureDesc; + // This should be thread safe as the asyncWriteBuff is thread local + int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; + byte[] writeBuffer = asyncWriteBuff.Value; + (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], texturePtr); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], descPtr); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], descPtr + 4); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], descPtr + 8); + lock (pipeTX) + pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + + HRESULT res = (HRESULT)await promise.Task; + uint width = memory.ReadMemory(descPtr); + uint height = memory.ReadMemory(descPtr + 4); + D3DFORMAT format = (D3DFORMAT)memory.ReadMemory(descPtr + 8); + memory.FreeRemoteMemory(descPtr, true); + + return (res, width, height, format); + } } /// @@ -413,25 +432,26 @@ public static async Task OmsiReleaseTextureAsync(uint texturePtr) /// public static async Task OmsiIsTextureAsync(uint texturePtr) { -#if OMSI_PLUGIN - return !HRESULTFailed((HRESULT)IsTexture(texturePtr)); -#else - if (!IsInitialised) - throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); - - int argPos = 0; - var method = OmsiHookRPCMethods.RemoteMethod.IsTexture; - // This should be thread safe as the asyncWriteBuff is thread local - int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; - byte[] writeBuffer = asyncWriteBuff.Value; - (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], texturePtr); - lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); - return !HRESULTFailed((HRESULT)await promise.Task); -#endif + if (localPlugin) + return !HRESULTFailed((HRESULT)IsTexture(texturePtr)); + else + { + if (!IsInitialised) + throw new NotInitialisedException("OmsiHook RPC plugin is not connected! Did you make sure to call OmsiRemoteMethods.InitRemoteMethods() before this call?"); + + int argPos = 0; + var method = OmsiHookRPCMethods.RemoteMethod.IsTexture; + // This should be thread safe as the asyncWriteBuff is thread local + int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; + byte[] writeBuffer = asyncWriteBuff.Value; + (int resultPromise, TaskCompletionSource promise) = CreateResultPromise(); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos)..], (int)method); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], resultPromise); + BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], texturePtr); + lock (pipeTX) + pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + return !HRESULTFailed((HRESULT)await promise.Task); + } } [DllImport("OmsiHookInvoker.dll")] diff --git a/OmsiHookInvoker/FunctionHooks.cpp b/OmsiHookInvoker/FunctionHooks.cpp new file mode 100644 index 0000000..88b6545 --- /dev/null +++ b/OmsiHookInvoker/FunctionHooks.cpp @@ -0,0 +1,63 @@ +#include "pch.h" +#include "FunctionHooks.h" + +FunctionHooks::FunctionHooks() +{ + DWORD oldProtect; + VirtualProtect(hookTrampolineFunc, sizeof(hookTrampolineFunc), PAGE_EXECUTE_READWRITE, &oldProtect); + ZeroMemory(hookTrampolineFunc, sizeof(hookTrampolineFunc)); +} + +FunctionHooks::~FunctionHooks() +{ + +} + +void FunctionHooks::InstallHook(UINT32 funcToHook) +{ + DWORD oldProtect; + VirtualProtect((LPVOID)funcToHook, 256, PAGE_EXECUTE_READWRITE, &oldProtect); + byte jmpInstruction[5] = { 0xE9, 0x0, 0x0, 0x0, 0x0 }; + + const UINT32 relAddr = (UINT32)hookTrampolineFunc - (funcToHook + sizeof(jmpInstruction)); + memcpy(jmpInstruction + 1, &relAddr, 4); + //https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-flushinstructioncache + memcpy((LPVOID)funcToHook, jmpInstruction, sizeof(jmpInstruction)); + VirtualProtect((LPVOID)funcToHook, 256, oldProtect, &oldProtect); +} + +#define AsLEBytes(addr) ((addr>>3)&0xff), ((addr>>2)&0xff), ((addr>>1)&0xff), ((addr>>0)&0xff) + +void FunctionHooks::OnTrigger(void(*callback)(LPCSTR trigger, int value)) +{ + // Bouncy bounce + UINT32 callbackRelAddr = (UINT32)callback - ((UINT32)hookTrampolineFunc + 11); + UINT32 returnRelAddr = (UINT32)0x007bae96 - ((UINT32)hookTrampolineFunc + 25); + // Setup the hook handler so that it calls the callback and then jumps back to the original function + byte hookTrampolineTemp[] = { + // Save the register args for later + 0x52, //push eax + 0x55, //push edx + 0x54, //push ecx + + // Convert a borland fastcall to stdcall + 0x52, //push eax + 0x55, //push edx + 0x54, //push ecx + 0xe8, AsLEBytes(callbackRelAddr), //call callback + + // Restore register args + 0x59, //pop ecx + 0x5a, //pop edx + 0x57, //pop eax + + // Now execute the prolog of the function we overwrote and jump back + 0x55, //push ebp + 0x8b, 0xec, //mov ebp, esp + 0x83, 0xc4, 0xf0,//add esp, -0x10 + 0xe9, AsLEBytes(returnRelAddr) //jmp 0x007bae96 + }; + memcpy(hookTrampolineFunc, hookTrampolineTemp, sizeof(hookTrampolineTemp)); + + InstallHook(0x007bae90); +} diff --git a/OmsiHookInvoker/FunctionHooks.h b/OmsiHookInvoker/FunctionHooks.h new file mode 100644 index 0000000..ce4c485 --- /dev/null +++ b/OmsiHookInvoker/FunctionHooks.h @@ -0,0 +1,14 @@ +#pragma once +class FunctionHooks +{ +public: + FunctionHooks(); + ~FunctionHooks(); + void OnTrigger(void (*callback) (LPCSTR trigger, int value)); + +private: + void InstallHook(UINT32 funcToHook); + + byte hookTrampolineFunc[256]; +}; + diff --git a/OmsiHookInvoker/OmsiHookInvoker.vcxproj b/OmsiHookInvoker/OmsiHookInvoker.vcxproj index e0a204d..05d662f 100644 --- a/OmsiHookInvoker/OmsiHookInvoker.vcxproj +++ b/OmsiHookInvoker/OmsiHookInvoker.vcxproj @@ -1,4 +1,4 @@ - + @@ -92,12 +92,14 @@ + + Create Create diff --git a/OmsiHookInvoker/dllmain.cpp b/OmsiHookInvoker/dllmain.cpp index 3e7f72a..e88dcb5 100644 --- a/OmsiHookInvoker/dllmain.cpp +++ b/OmsiHookInvoker/dllmain.cpp @@ -1,8 +1,10 @@ // dllmain.cpp : Defines the entry point for the DLL application. #include "pch.h" #include "DXHook.h" +#include "FunctionHooks.h" DXHook* m_dxHook; +FunctionHooks* m_functionHooks; BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, @@ -14,10 +16,13 @@ BOOL APIENTRY DllMain( HMODULE hModule, case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: + m_functionHooks = new FunctionHooks(); break; case DLL_PROCESS_DETACH: if (m_dxHook) delete m_dxHook; + if (m_functionHooks) + delete m_functionHooks; break; } return TRUE; @@ -206,3 +211,8 @@ extern "C" __declspec(dllexport) HRESULT IsTexture(IUnknown* Texture) { return DXHook::IsTexture(Texture); } + +extern "C" __declspec(dllexport) void OnTrigger(void (*callback) (LPCSTR trigger, int value)) +{ + +} diff --git a/OmsiHookPlugin/OmsiHookPlugin.cs b/OmsiHookPlugin/OmsiHookPlugin.cs index 51bd8c1..7d15fa0 100644 --- a/OmsiHookPlugin/OmsiHookPlugin.cs +++ b/OmsiHookPlugin/OmsiHookPlugin.cs @@ -4,27 +4,62 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using OmsiHook; -using System.Security; -using System.Diagnostics; namespace OmsiHookPlugin { public class OmsiHookPlugin { private static OmsiHook.OmsiHook hook; - private static bool spawned = false; private static void Log(object msg) => File.AppendAllText("omsiHookPluginLog.txt", $"[{DateTime.Now:dd/MM/yy HH:mm:ss:ff}] {msg}\n"); [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }, EntryPoint = nameof(PluginStart))] public static void PluginStart(IntPtr aOwner) { - File.Delete("omsiHookPluginLog.txt"); + try + { + File.Delete("omsiHookPluginLog.txt"); + } catch { } Log("PluginStart()"); Log("Loading OmsiHook..."); hook = new(); - _ = hook.AttachToOMSI(); - Log("Didn't crash!"); + try + { + hook.AttachToOMSI().Wait(); + } catch (Exception e) + { + Log($"Failed to attach to Omsi:\n{e}"); + } + hook.OnMapLoaded += Hook_OnMapLoaded; + hook.OnMapChange += Hook_OnMapChange; + hook.OnOmsiExited += Hook_OnOmsiExited; + hook.OnOmsiGotD3DContext += Hook_OnOmsiGotD3DContext; + hook.OnOmsiLostD3DContext += Hook_OnOmsiLostD3DContext; + } + + private static void Hook_OnOmsiLostD3DContext(object sender, EventArgs e) + { + Log($"Lost D3D context!"); + } + + private static void Hook_OnOmsiGotD3DContext(object sender, EventArgs e) + { + Log($"Got D3D context!"); + } + + private static void Hook_OnOmsiExited(object sender, EventArgs e) + { + Log($"Omsi exited!"); + } + + private static void Hook_OnMapChange(object sender, EventArgs e) + { + Log($"Map changed!"); + } + + private static void Hook_OnMapLoaded(object sender, bool e) + { + Log($"Map loaded! {e}"); } [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }, EntryPoint = nameof(PluginFinalize))] @@ -36,27 +71,7 @@ public static void PluginFinalize() [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }, EntryPoint = nameof(AccessVariable))] public static void AccessVariable(ushort variableIndex, [C99Type("float*")] IntPtr value, [C99Type("__crt_bool*")] IntPtr writeValue) { - var val = Marshal.PtrToStructure(value); - if (val > 0.9 && !spawned) - { - spawned = true; - Log("Spawning bus!"); - try - { - //hook.Globals.RemoteMethods.PlaceRandomBus(); - } catch (Exception e) - { - Log("Uh oh:"); - Log(e.Message); - Log(e.ToString()); - } - - Log("The bus might have spawned!"); - } else - { - if (val < 0.1) - spawned = false; - } + } [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }, EntryPoint = nameof(AccessTrigger))] diff --git a/OmsiHookPlugin/OmsiHookPlugin.opl b/OmsiHookPlugin/OmsiHookPlugin.opl index 81721a9..e6a6f8f 100644 --- a/OmsiHookPlugin/OmsiHookPlugin.opl +++ b/OmsiHookPlugin/OmsiHookPlugin.opl @@ -3,4 +3,4 @@ OmsiHookPluginNE.dll [varlist] 1 -door_0 +Throttle