From 3bbac58fdf9c44bb78f9491171c83e95fb38910c Mon Sep 17 00:00:00 2001 From: Thomas Mathieson Date: Sat, 18 Nov 2023 16:25:17 +0000 Subject: [PATCH] Implemented new UpdateSubresource method for updating textures: - Implemented UpdateSubresource in OmsiHookInvoker, OmsiHookRPC and OmsiHook - Removed shared resource support from create texture, it never would have worked - Other small tweaks --- OmsiExtensionsCLI/Program.cs | 37 ++++++++++------ OmsiHook/Memory.cs | 6 +++ OmsiHook/OmsiHookRPCMethods.cs | 6 ++- OmsiHook/OmsiRemoteMethods.cs | 60 ++++++++++++++++++++++---- OmsiHookInvoker/DXHook.cpp | 51 +++++++++++++++++++++- OmsiHookInvoker/DXHook.h | 4 +- OmsiHookInvoker/dllmain.cpp | 11 ++++- OmsiHookRPCPlugin/NativeImports.cs | 4 +- OmsiHookRPCPlugin/OmsiHookRPCPlugin.cs | 15 ++++++- 9 files changed, 163 insertions(+), 31 deletions(-) diff --git a/OmsiExtensionsCLI/Program.cs b/OmsiExtensionsCLI/Program.cs index 6238952..fd86abb 100644 --- a/OmsiExtensionsCLI/Program.cs +++ b/OmsiExtensionsCLI/Program.cs @@ -73,10 +73,15 @@ public class DXTests private const int texHeight = 256; private RGBA[] texBuffer = new RGBA[texWidth * texHeight]; - [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 4)] + [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 4)] private struct RGBA { - public byte r, g, b, a; + [FieldOffset(0)]public byte r; + [FieldOffset(1)]public byte g; + [FieldOffset(2)]public byte b; + [FieldOffset(3)]public byte a; + + [FieldOffset(0)]public uint data; } public void Init(OmsiHook.OmsiHook omsi) @@ -98,12 +103,12 @@ private void Hook() ready = true; } - public Texture2D CreateTexture() + public uint CreateTexture() { if(!ready) Hook(); - (uint hresult, uint texturePtr, uint textureHandle) = OmsiRemoteMethods.OmsiCreateTextureAsync(texWidth, texHeight, OmsiRemoteMethods.DXGI_FORMAT.R8G8B8A8_UNORM).Result; + (uint hresult, uint texturePtr) = OmsiRemoteMethods.OmsiCreateTextureAsync(texWidth, texHeight, OmsiRemoteMethods.DXGI_FORMAT.R8G8B8A8_UNORM).Result; if (hresult != 0) throw new Exception("Couldn't create D3D texture! Result: " + new SharpDX.Result(hresult)); @@ -119,28 +124,32 @@ public Texture2D CreateTexture() }; } - device = new SharpDX.Direct3D11.Device(SharpDX.Direct3D.DriverType.Hardware, DeviceCreationFlags.None); - return device.OpenSharedResource((IntPtr)textureHandle); + //device = new SharpDX.Direct3D11.Device(SharpDX.Direct3D.DriverType.Hardware, DeviceCreationFlags.None); + //return device.OpenSharedResource((IntPtr)textureHandle); + return texturePtr; } - public void UpdateTexture(Texture2D texture) + public void UpdateTexture(uint texturePtr) { - if (texture?.IsDisposed ?? true) - return; - - for(int y = 0; y < texHeight; y++) + uint texMemPtr = OmsiRemoteMethods.OmsiGetMem(texWidth * texHeight * 4).Result; + uint[] managedTextureBuffer = new uint[texWidth * texHeight * 4]; + for (int y = 0; y < texHeight; y++) for(int x = 0; x < texWidth; x++) { - texBuffer[x + y * texWidth] = new() + managedTextureBuffer[x + y * texWidth] = new RGBA() { r = (byte)((x * 4) % 256), g = (byte)((y * 4) % 256), b = (byte)(((x + y) * 4) % 256), a = 255 - }; + }.data; } - texture.Device.ImmediateContext.UpdateSubresource(texBuffer, texture, rowPitch:texWidth*Marshal.SizeOf()); + omsi.OmsiMemory.WriteMemory(texMemPtr, managedTextureBuffer); + + int hr = unchecked((int)OmsiRemoteMethods.OmsiUpdateTextureAsync(texturePtr, texMemPtr, texWidth, texHeight).Result); + if(hr != 0) + throw new SharpDXException(hr); } private void Omsi_OnOmsiGotD3DContext(object sender, EventArgs e) diff --git a/OmsiHook/Memory.cs b/OmsiHook/Memory.cs index 0e80b3c..af72cfe 100644 --- a/OmsiHook/Memory.cs +++ b/OmsiHook/Memory.cs @@ -119,6 +119,12 @@ public void WriteMemory(int address, T[] values) where T : unmanaged Imports.WriteProcessMemory((int)omsiProcessHandle, address, buffer, buffer.Length, out _); } + /// + public void WriteMemory(uint address, T[] values) where T : unmanaged + { + WriteMemory(unchecked((int)address), values); + } + /// /// Mildly quickly copies a block of data from one place to another in remote memory. /// Copies the data to a temp buffer before copying to the destination so it's not very fast. diff --git a/OmsiHook/OmsiHookRPCMethods.cs b/OmsiHook/OmsiHookRPCMethods.cs index 99bec58..1b4230d 100644 --- a/OmsiHook/OmsiHookRPCMethods.cs +++ b/OmsiHook/OmsiHookRPCMethods.cs @@ -18,7 +18,8 @@ internal enum RemoteMethod : int GetMem, FreeMem, HookD3D, - CreateTexture + CreateTexture, + UpdateSubresource } internal static readonly ReadOnlyDictionary RemoteMethodsArgsSizes = new(new Dictionary() @@ -29,7 +30,8 @@ internal enum RemoteMethod : int { RemoteMethod.GetMem, 4 }, { RemoteMethod.FreeMem, 4 }, { RemoteMethod.HookD3D, 0 }, - { RemoteMethod.CreateTexture, 20 }, + { RemoteMethod.CreateTexture, 16 }, + { RemoteMethod.UpdateSubresource, 36 }, }); } } diff --git a/OmsiHook/OmsiRemoteMethods.cs b/OmsiHook/OmsiRemoteMethods.cs index b40cf70..9c45a62 100644 --- a/OmsiHook/OmsiRemoteMethods.cs +++ b/OmsiHook/OmsiRemoteMethods.cs @@ -230,19 +230,19 @@ public static bool OmsiHookD3D() /// /// Attempts to create a new d3d texture which can be shared with an external D3D context. /// - public static async Task<(uint hresult, uint ppTexture, uint pSharedHandle)> OmsiCreateTextureAsync(uint width, uint height, DXGI_FORMAT format) + public static async Task<(uint hresult, uint ppTexture)> OmsiCreateTextureAsync(uint width, uint height, DXGI_FORMAT format) { #if OMSI_PLUGIN - return CreateTexture(width, height, (uint)format, ppTexture, pSharedHandle) != 0; + uint ppTexture = OmsiGetMem(4).Result; + uint hresult = unchecked((uint)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 - uint ppTexture = OmsiGetMem(8).Result; - uint pSharedHandle = ppTexture + 4; + uint ppTexture = OmsiGetMem(4).Result; memory.WriteMemory(ppTexture, 0); - memory.WriteMemory(pSharedHandle, 0); int argPos = 0; var method = OmsiHookRPCMethods.RemoteMethod.CreateTexture; @@ -256,10 +256,44 @@ public static bool OmsiHookD3D() BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], height); BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], (uint)format); BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], ppTexture); - BitConverter.TryWriteBytes(writeBuffer.AsSpan()[(argPos += 4)..], pSharedHandle); lock (pipeTX) pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); - return ((uint)await promise.Task, ppTexture, pSharedHandle); + return (unchecked((uint)await promise.Task), ppTexture); +#endif + } + + /// + /// Attempts to create a new d3d texture which can be shared with an external D3D context. + /// + public static async Task OmsiUpdateTextureAsync(uint texturePtr, uint textureDataPtr, uint width, uint height, Rectangle? updateRect = null) + { +#if OMSI_PLUGIN + return unchecked((uint)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 unchecked((uint)await promise.Task); #endif } @@ -282,7 +316,9 @@ private static extern int TProgManPlaceRandomBus(int progMan, int aityp, [DllImport("OmsiHookInvoker.dll")] internal static extern int HookD3D(); [DllImport("OmsiHookInvoker.dll")] - internal static extern int CreateTexture(uint Width, uint Height, uint Format, uint ppTexture, uint pSharedHandle); + internal static extern int CreateTexture(uint Width, uint Height, uint Format, uint ppTexture); + [DllImport("OmsiHookInvoker.dll")] + internal static extern int UpdateSubresource(uint Texture, uint TextureData, uint Width, uint Height, int UseRect, uint Left, uint Top, uint Right, uint Bottom); public enum DXGI_FORMAT : uint { @@ -290,5 +326,13 @@ public enum DXGI_FORMAT : uint R10G10B10A2_UNORM = 24, R8G8B8A8_UNORM = 28 } + + public struct Rectangle + { + public uint left; + public uint top; + public uint right; + public uint bottom; + } } } diff --git a/OmsiHookInvoker/DXHook.cpp b/OmsiHookInvoker/DXHook.cpp index 2c8d290..555dab3 100644 --- a/OmsiHookInvoker/DXHook.cpp +++ b/OmsiHookInvoker/DXHook.cpp @@ -17,20 +17,67 @@ BOOL DXHook::HookD3D() if (device->QueryInterface(&m_device) != S_OK) { m_device = (IDirect3DDevice9*) -2; + OutputDebugStringA("[OmsiHookRPC] Query interface for d3ddevice failed!"); return FALSE; } + + /*IDirect3D9Ex* d3d; + Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d); + d3d->CreateDeviceEx(0, D3DDEVTYPE::D3DDEVTYPE_HAL, )*/ + if (m_device == nullptr) return FALSE; m_device->AddRef(); return TRUE; } -HRESULT DXHook::CreateTexture(UINT Width, UINT Height, D3DFORMAT Format, IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle) +HRESULT DXHook::CreateTexture(UINT Width, UINT Height, D3DFORMAT Format, IDirect3DTexture9** ppTexture) { if (!m_device) return E_FAIL; if ((int)m_device == -2) return -2; - return m_device->CreateTexture(Width, Height, 1, D3DUSAGE_RENDERTARGET, Format, D3DPOOL_DEFAULT, ppTexture, pSharedHandle); + return m_device->CreateTexture(Width, Height, 1, D3DUSAGE_DYNAMIC, Format, D3DPOOL_DEFAULT, ppTexture, NULL); +} + +HRESULT DXHook::UpdateSubresource(IDirect3DTexture9* Texture, UINT8* TextureData, UINT Width, UINT Height, BOOL UseRect, LONG32 Left, LONG32 Top, LONG32 Right, LONG32 Bottom) +{ + // Check the arguments, users can never be trusted and this is annoying to debug... + if (Texture == nullptr || TextureData == nullptr) + return E_ABORT; + + HRESULT hr; + D3DSURFACE_DESC surfaceDesc; + hr = Texture->GetLevelDesc(0, &surfaceDesc); + if(FAILED(hr)) + return hr; + + if (surfaceDesc.Width < Width || surfaceDesc.Height < Height) + return E_INVALIDARG; + + if (UseRect) + if (Right - Left != Width || Bottom - Top != Height) + return E_INVALIDARG; + + const RECT rect { + Left, + Top, + Right, + Bottom + }; + D3DLOCKED_RECT lockedRect; + hr = Texture->LockRect(0, &lockedRect, UseRect ? &rect : NULL, D3DLOCK_DISCARD); + if (FAILED(hr)) + return hr; + + for (UINT y = 0; y < Height; y++) + { + void* dst = ((UINT8*)lockedRect.pBits) + (y * lockedRect.Pitch); + memcpy(dst, (TextureData + Width * y), Width); + } + + hr = Texture->UnlockRect(0); + + return hr; } diff --git a/OmsiHookInvoker/DXHook.h b/OmsiHookInvoker/DXHook.h index 8d48052..525bbed 100644 --- a/OmsiHookInvoker/DXHook.h +++ b/OmsiHookInvoker/DXHook.h @@ -14,5 +14,7 @@ class DXHook BOOL HookD3D(); - HRESULT CreateTexture(UINT Width, UINT Height, D3DFORMAT Format, IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle); + HRESULT CreateTexture(UINT Width, UINT Height, D3DFORMAT Format, IDirect3DTexture9** ppTexture); + + HRESULT UpdateSubresource(IDirect3DTexture9* Texture, UINT8* TextureData, UINT Width, UINT Height, BOOL UseRect, LONG32 Left, LONG32 Top, LONG32 Right, LONG32 Bottom); }; diff --git a/OmsiHookInvoker/dllmain.cpp b/OmsiHookInvoker/dllmain.cpp index ff1cb9b..947c6d1 100644 --- a/OmsiHookInvoker/dllmain.cpp +++ b/OmsiHookInvoker/dllmain.cpp @@ -178,9 +178,16 @@ extern "C" __declspec(dllexport) BOOL HookD3D() return m_dxHook->HookD3D(); } -extern "C" __declspec(dllexport) HRESULT CreateTexture(UINT Width, UINT Height, D3DFORMAT Format, IDirect3DTexture9** ppTexture, HANDLE* pSharedHandle) +extern "C" __declspec(dllexport) HRESULT CreateTexture(UINT Width, UINT Height, D3DFORMAT Format, IDirect3DTexture9** ppTexture) { if (!m_dxHook) return E_FAIL; - return m_dxHook->CreateTexture(Width, Height, Format, ppTexture, pSharedHandle); + return m_dxHook->CreateTexture(Width, Height, Format, ppTexture); +} + +extern "C" __declspec(dllexport) HRESULT UpdateSubresource(IDirect3DTexture9* Texture, UINT8* TextureData, UINT Width, UINT Height, BOOL UseRect, LONG32 Left, LONG32 Top, LONG32 Right, LONG32 Bottom) +{ + if (!m_dxHook) + return E_FAIL; + return m_dxHook->UpdateSubresource(Texture, TextureData, Width, Height, UseRect, Left, Top, Right, Bottom); } diff --git a/OmsiHookRPCPlugin/NativeImports.cs b/OmsiHookRPCPlugin/NativeImports.cs index 2ebb5d5..c1a9e21 100644 --- a/OmsiHookRPCPlugin/NativeImports.cs +++ b/OmsiHookRPCPlugin/NativeImports.cs @@ -23,6 +23,8 @@ internal static extern int TProgManPlaceRandomBus(int progMan, int aityp, [DllImport("OmsiHookInvoker.dll")] internal static extern int HookD3D(); [DllImport("OmsiHookInvoker.dll")] - internal static extern int CreateTexture(uint Width, uint Height, uint Format, uint ppTexture, uint pSharedHandle); + internal static extern int CreateTexture(uint Width, uint Height, uint Format, uint ppTexture); + [DllImport("OmsiHookInvoker.dll")] + internal static extern int UpdateSubresource(uint Texture, uint TextureData, uint Width, uint Height, int UseRect, uint Left, uint Top, uint Right, uint Bottom); } } diff --git a/OmsiHookRPCPlugin/OmsiHookRPCPlugin.cs b/OmsiHookRPCPlugin/OmsiHookRPCPlugin.cs index 1e96c3c..681eb1d 100644 --- a/OmsiHookRPCPlugin/OmsiHookRPCPlugin.cs +++ b/OmsiHookRPCPlugin/OmsiHookRPCPlugin.cs @@ -111,6 +111,7 @@ private static void ServerThreadStart(int threadId) using NamedPipeServerStream pipeRX = new(PIPE_NAME_RX, PipeDirection.In, MAX_CLIENTS, PipeTransmissionMode.Byte); pipeRX.WaitForConnection(); Log($"[RPC Server {threadId}] Client has connected to rx."); + // TODO: There's still a race condition here for some reason... Sometimes the client connects to the rx of one thread and the tx of another. using NamedPipeServerStream pipeTX = new(PIPE_NAME_TX, PipeDirection.Out, MAX_CLIENTS, PipeTransmissionMode.Byte); pipeTX.WaitForConnection(); Log($"[RPC Server {threadId}] Client has connected to tx."); @@ -256,10 +257,22 @@ private static void ProcessCall(MethodData methodData) BitConverter.ToUInt32(methodData.args, argInd), BitConverter.ToUInt32(methodData.args, argInd += 4), BitConverter.ToUInt32(methodData.args, argInd += 4), - BitConverter.ToUInt32(methodData.args, argInd += 4), BitConverter.ToUInt32(methodData.args, argInd += 4) ); break; + case RemoteMethod.UpdateSubresource: + ret = NativeImports.UpdateSubresource( + BitConverter.ToUInt32(methodData.args, argInd), + BitConverter.ToUInt32(methodData.args, argInd += 4), + BitConverter.ToUInt32(methodData.args, argInd += 4), + BitConverter.ToUInt32(methodData.args, argInd += 4), + BitConverter.ToInt32(methodData.args, argInd += 4), + BitConverter.ToUInt32(methodData.args, argInd += 4), + BitConverter.ToUInt32(methodData.args, argInd += 4), + BitConverter.ToUInt32(methodData.args, argInd += 4), + BitConverter.ToUInt32(methodData.args, argInd += 4) + ); + break; default: Log($"Unknown message type: {methodData.method} encountered!"); break;