Skip to content

Commit

Permalink
Implemented new UpdateSubresource method for updating textures:
Browse files Browse the repository at this point in the history
 - Implemented UpdateSubresource in OmsiHookInvoker, OmsiHookRPC and OmsiHook
 - Removed shared resource support from create texture, it never would have worked
 - Other small tweaks
  • Loading branch information
space928 committed Nov 18, 2023
1 parent 6581cee commit 3bbac58
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 31 deletions.
37 changes: 23 additions & 14 deletions OmsiExtensionsCLI/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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));

Expand All @@ -119,28 +124,32 @@ public Texture2D CreateTexture()
};
}

device = new SharpDX.Direct3D11.Device(SharpDX.Direct3D.DriverType.Hardware, DeviceCreationFlags.None);
return device.OpenSharedResource<SharpDX.Direct3D11.Texture2D>((IntPtr)textureHandle);
//device = new SharpDX.Direct3D11.Device(SharpDX.Direct3D.DriverType.Hardware, DeviceCreationFlags.None);
//return device.OpenSharedResource<SharpDX.Direct3D11.Texture2D>((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<RGBA>());
omsi.OmsiMemory.WriteMemory(texMemPtr, managedTextureBuffer);

Check failure on line 148 in OmsiExtensionsCLI/Program.cs

View workflow job for this annotation

GitHub Actions / build

'OmsiHook' does not contain a definition for 'OmsiMemory' and no accessible extension method 'OmsiMemory' accepting a first argument of type 'OmsiHook' could be found (are you missing a using directive or an assembly reference?)

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)
Expand Down
6 changes: 6 additions & 0 deletions OmsiHook/Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ public void WriteMemory<T>(int address, T[] values) where T : unmanaged
Imports.WriteProcessMemory((int)omsiProcessHandle, address, buffer, buffer.Length, out _);
}

/// <inheritdoc cref="WriteMemory{T}(int, T[])"/>
public void WriteMemory<T>(uint address, T[] values) where T : unmanaged
{
WriteMemory(unchecked((int)address), values);
}

/// <summary>
/// Mildly quickly copies a block of data from one place to another in remote memory. <para/>
/// Copies the data to a temp buffer before copying to the destination so it's not very fast.
Expand Down
6 changes: 4 additions & 2 deletions OmsiHook/OmsiHookRPCMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ internal enum RemoteMethod : int
GetMem,
FreeMem,
HookD3D,
CreateTexture
CreateTexture,
UpdateSubresource
}

internal static readonly ReadOnlyDictionary<RemoteMethod, int> RemoteMethodsArgsSizes = new(new Dictionary<RemoteMethod, int>()
Expand All @@ -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 },
});
}
}
60 changes: 52 additions & 8 deletions OmsiHook/OmsiRemoteMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,19 +230,19 @@ public static bool OmsiHookD3D()
/// <summary>
/// Attempts to create a new d3d texture which can be shared with an external D3D context.
/// </summary>
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;
Expand All @@ -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
}

/// <summary>
/// Attempts to create a new d3d texture which can be shared with an external D3D context.
/// </summary>
public static async Task<uint> 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<int> 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
}

Expand All @@ -282,13 +316,23 @@ 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
{
R16G16B16A16_FLOAT = 10,
R10G10B10A2_UNORM = 24,
R8G8B8A8_UNORM = 28
}

public struct Rectangle
{
public uint left;
public uint top;
public uint right;
public uint bottom;
}
}
}
51 changes: 49 additions & 2 deletions OmsiHookInvoker/DXHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,67 @@ BOOL DXHook::HookD3D()
if (device->QueryInterface<IDirect3DDevice9>(&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;
}
4 changes: 3 additions & 1 deletion OmsiHookInvoker/DXHook.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
11 changes: 9 additions & 2 deletions OmsiHookInvoker/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
4 changes: 3 additions & 1 deletion OmsiHookRPCPlugin/NativeImports.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
15 changes: 14 additions & 1 deletion OmsiHookRPCPlugin/OmsiHookRPCPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 3bbac58

Please sign in to comment.