Skip to content

Commit

Permalink
Some more fixes and mipmap support:
Browse files Browse the repository at this point in the history
 - Added D3DTexture mipmap support (fixes #92)
 - Added RPC method to get texture level count
 - Added a few null checks to the local plugin path for Memory Read/Write operations to prevent crashes
 - Fixed some bugs with MonitorStateTask in local plugins
 - Disposing of OmsiRemoteMethods (which is done automatically when disposing of OmsiHook) now closes the RPC session correctly
 - CloseRPCSession() is now awaitable
 - Various RPC/Remote method updates to support the new textrue API
 - Updated OmsiHookPlugin example to test a few more features in a local plugin -> seems to work...
  • Loading branch information
space928 committed May 14, 2024
1 parent abb3bcb commit 6d43a4a
Show file tree
Hide file tree
Showing 14 changed files with 372 additions and 124 deletions.
35 changes: 23 additions & 12 deletions OmsiHook/D3DTexture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public D3DTexture() : base() { }

private uint width, height;
private D3DFORMAT format;
private uint levels;
private int stagingBufferSize;
// private byte[] stagingBuffer;
private uint remoteStagingBufferPtr;
Expand All @@ -43,6 +44,10 @@ public D3DTexture() : base() { }
/// </summary>
public D3DFORMAT Format => format;
/// <summary>
/// Gets the number of mipmap levels in the texture.
/// </summary>
public uint Levels => levels;
/// <summary>
/// Returns true if this object is initialised.
/// </summary>
public bool IsValid => TextureAddress != 0 && isCreated;
Expand Down Expand Up @@ -75,19 +80,19 @@ public async Task CreateFromExisting(uint address, bool initialiseForWriting = t
if (Address == 0)
throw new NullReferenceException("Texture was already null!");

var desc = await Memory.RemoteMethods.OmsiGetTextureDescAsync(address);
var desc = await Memory.RemoteMethods.OmsiGetTextureDescAsync(address, 0);
if (HRESULTFailed(desc.hresult))
throw new Exception(desc.hresult.ToString());

width = desc.width;
height = desc.height;
format = desc.format;
levels = await Memory.RemoteMethods.OmsiGetTextureLevelCountAsync(address);
Address = unchecked((int)address);
stagingBufferSize = (int)width * (int)height * BitsPerPixel(format) / 8;
//stagingBuffer = new byte[stagingBufferSize];
if(initialiseForWriting)
remoteStagingBufferPtr = unchecked((uint)await Memory.AllocRemoteMemory(stagingBufferSize, fastAlloc: useFastAlloc));
isCreated = true;
if (initialiseForWriting)
await InitialiseForWriting();
}

/// <summary>
Expand All @@ -111,26 +116,27 @@ public async Task InitialiseForWriting()
/// <param name="width">the width of the texture</param>
/// <param name="height">the height of the texture</param>
/// <param name="format">the <see cref="D3DFORMAT"/> of the texture</param>
/// <param name="levels">the number of mipmap levels to create</param>
/// <param name="initialiseForWriting">whether to allow writing to this texture</param>
/// <exception cref="Exception"></exception>
public async Task CreateD3DTexture(uint width, uint height, D3DFORMAT format = D3DFORMAT.D3DFMT_A8R8G8B8, bool initialiseForWriting = true)
public async Task CreateD3DTexture(uint width, uint height, D3DFORMAT format = D3DFORMAT.D3DFMT_A8R8G8B8, uint levels = 1, bool initialiseForWriting = true)
{
if(Address != 0)
await ReleaseTexture();

var (hresult, pTexture) = await Memory.RemoteMethods.OmsiCreateTextureAsync(width, height, format);
var (hresult, pTexture) = await Memory.RemoteMethods.OmsiCreateTextureAsync(width, height, format, levels);
if (HRESULTFailed(hresult))
throw new Exception("Couldn't create D3D texture! Result: " + hresult);

this.width = width;
this.height = height;
this.format = format;
this.levels = levels;
Address = unchecked((int)pTexture);
stagingBufferSize = (int)width * (int)height * BitsPerPixel(format) / 8;
//stagingBuffer = new byte[stagingBufferSize];
if (initialiseForWriting)
remoteStagingBufferPtr = unchecked((uint)await Memory.AllocRemoteMemory(stagingBufferSize, fastAlloc: useFastAlloc));
isCreated = true;
if (initialiseForWriting)
await InitialiseForWriting();
}

/// <summary>
Expand All @@ -157,24 +163,29 @@ public async Task ReleaseTexture()
/// <summary>
/// Updates the contents of the <c>IDirect3DTexture9</c> object from a buffer.
/// </summary>
/// <remarks>
/// This method can cause crashes if the texture becomes invalidated by Omsi. This can in certain circumstances
/// happen if the script texture is updated in OmsiScript.
/// </remarks>
/// <typeparam name="T">The pixel element type</typeparam>
/// <param name="textureData">a buffer of memory containing the new texture data layed out sequentially in row-major order</param>
/// <param name="updateArea">optionally, a rectangle specifying the area of the texture to update; the <paramref name="textureData"/>
/// must match the size of this rectangle; pass in <see langword="null"/> to update the whole texture</param>
/// <param name="level">optionally, the mipmap level to update</param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="Exception"></exception>
public async Task UpdateTexture<T>(Memory<T> textureData, Rectangle? updateArea = null) where T : unmanaged
public async Task UpdateTexture<T>(Memory<T> textureData, Rectangle? updateArea = null, uint level = 0) where T : unmanaged
{
if (!Memory.RemoteMethods.IsInitialised || remoteStagingBufferPtr == 0)
throw new NotInitialisedException("D3DTexture object must be initialised for write before it can be updated! ");
if((textureData.Length * Marshal.SizeOf<T>()) > stagingBufferSize)
if((textureData.Length * Unsafe.SizeOf<T>()) > stagingBufferSize)
throw new ArgumentOutOfRangeException(nameof(textureData));
Memory.WriteMemory(remoteStagingBufferPtr, textureData);

uint dataWidth = (updateArea?.right - updateArea?.left) ?? width;
uint dataHeight = (updateArea?.bottom - updateArea?.top) ?? height;

HRESULT hr = await Memory.RemoteMethods.OmsiUpdateTextureAsync(TextureAddress, remoteStagingBufferPtr, dataWidth, dataHeight, updateArea);
HRESULT hr = await Memory.RemoteMethods.OmsiUpdateTextureAsync(TextureAddress, remoteStagingBufferPtr, dataWidth, dataHeight, updateArea, level);
if (HRESULTFailed(hr))
throw new Exception("Couldn't update D3D texture! Result: " + hr);
}
Expand Down
80 changes: 74 additions & 6 deletions OmsiHook/Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,16 @@ public void WriteMemory<T>(int address, T value) where T : unmanaged
{
unsafe
{
if (address == 0)
#if DEBUG && SILENCE_ACCESS_VIOLATION
{
Debug.WriteLine($"Couldn't write {byteSize} bytes of process memory @ 0x{address:X8}!\n{new System.Diagnostics.StackTrace(true)}");
return;
}
#else

throw new MemoryAccessException($"Couldn't write {sizeof(T)} bytes of process memory @ 0x{address:X8}!");
#endif
Unsafe.Copy((void*)address, ref value);
return;
}
Expand All @@ -117,6 +127,17 @@ public void WriteMemory<T>(uint address, T value) where T : unmanaged
{
unsafe
{
if (address == 0)
#if DEBUG && SILENCE_ACCESS_VIOLATION
{
Debug.WriteLine($"Couldn't write {byteSize} bytes of process memory @ 0x{address:X8}!\n{new System.Diagnostics.StackTrace(true)}");
return;
}
#else

throw new MemoryAccessException($"Couldn't write {sizeof(T)} bytes of process memory @ 0x{address:X8}!");
#endif

Unsafe.Copy((void*)address, ref value);
return;
}
Expand All @@ -142,10 +163,24 @@ public void WriteMemory<T>(uint address, T value) where T : unmanaged
/// correct data type to avoid memory corruption</param>
public void WriteMemory<T>(int address, T[] values) where T : unmanaged
{
if (values == null)
return;

if (isLocalPlugin)
{
unsafe
{
if (address == 0)
#if DEBUG && SILENCE_ACCESS_VIOLATION
{
Debug.WriteLine($"Couldn't write {byteSize} bytes of process memory @ 0x{address:X8}!\n{new System.Diagnostics.StackTrace(true)}");
return;
}
#else

throw new MemoryAccessException($"Couldn't write {sizeof(T) * values.Length} bytes of process memory @ 0x{address:X8}!");
#endif

if (values is byte[] valuesBytes)
{
Buffer.MemoryCopy(Unsafe.AsPointer(ref MemoryMarshal.AsRef<byte>(valuesBytes.AsSpan())), (void*)address, valuesBytes.Length, valuesBytes.Length);
Expand Down Expand Up @@ -188,10 +223,24 @@ public void WriteMemory<T>(uint address, T[] values) where T : unmanaged
/// <inheritdoc cref="WriteMemory{T}(int, T[])"/>
public void WriteMemory<T>(int address, Memory<T> values) where T : unmanaged
{
if (values.IsEmpty)
return;

if (isLocalPlugin)
{
unsafe
{
{
if(address == 0)
#if DEBUG && SILENCE_ACCESS_VIOLATION
{
Debug.WriteLine($"Couldn't write {byteSize} bytes of process memory @ 0x{address:X8}!\n{new System.Diagnostics.StackTrace(true)}");
return;
}
#else

throw new MemoryAccessException($"Couldn't write {sizeof(T) * values.Length} bytes of process memory @ 0x{address:X8}!");
#endif

fixed (T* valuesPtr = values.Span)
{
int size = sizeof(T) * values.Length;
Expand Down Expand Up @@ -225,16 +274,19 @@ internal void CopyMemory(int src, int dst, int length)
{
unsafe
{
if (src == 0 || dst == 0)
throw new MemoryAccessException($"Couldn't copy {length} bytes of process memory from 0x{src:X8} to 0x{dst:X8}!");

Buffer.MemoryCopy((void*)src, (void*)dst, length, length);
return;
}
}

byte[] buffer = new byte[length];
if(!Imports.ReadProcessMemory((int)omsiProcessHandle, src, buffer, length, ref length))
throw new MemoryAccessException($"Couldn't read {length} bytes of process memory @ {src:X}!");
throw new MemoryAccessException($"Couldn't read {length} bytes of process memory @ 0x{src:X8}!");
if(!Imports.WriteProcessMemory((int)omsiProcessHandle, dst, buffer, length, out _))
throw new MemoryAccessException($"Couldn't write {length} bytes to process memory @ {dst:X}!");
throw new MemoryAccessException($"Couldn't write {length} bytes to process memory @ 0x{dst:X8}!");
}

/// <inheritdoc cref="CopyMemory(int, int, int)"/>
Expand All @@ -244,16 +296,19 @@ internal void CopyMemory(uint src, uint dst, int length)
{
unsafe
{
if (src == 0 || dst == 0)
throw new MemoryAccessException($"Couldn't copy {length} bytes of process memory from 0x{src:X8} to 0x{dst:X8}!");

Buffer.MemoryCopy((void*)src, (void*)dst, length, length);
return;
}
}

byte[] buffer = new byte[length];
if (!Imports.ReadProcessMemory((int)omsiProcessHandle, unchecked((int)src), buffer, length, ref length))
throw new MemoryAccessException($"Couldn't read {length} bytes of process memory @ {src:X}!");
throw new MemoryAccessException($"Couldn't read {length} bytes of process memory @ 0x{src:X8}!");
if (!Imports.WriteProcessMemory((int)omsiProcessHandle, unchecked((int)dst), buffer, length, out _))
throw new MemoryAccessException($"Couldn't write {length} bytes to process memory @ {dst:X}!");
throw new MemoryAccessException($"Couldn't write {length} bytes to process memory @ 0x{dst:X8}!");
}

/// <summary>
Expand Down Expand Up @@ -529,6 +584,16 @@ public T ReadMemory<T>(int address) where T : unmanaged
{
unsafe
{
if (address == 0)
#if DEBUG && SILENCE_ACCESS_VIOLATION
{
Debug.WriteLine($"Couldn't read {byteSize} bytes of process memory @ 0x{address:X8}!\n{new System.Diagnostics.StackTrace(true)}");
return new T();
}
#else

throw new MemoryAccessException($"Couldn't read {sizeof(T)} bytes of process memory @ 0x{address:X8}!");
#endif
return *(T*)address;
}
}
Expand Down Expand Up @@ -693,13 +758,16 @@ public ArraySegment<byte> ReadMemory(int offset, int size, byte[] buffer)
throw new ArgumentException($"Couldn't read memory for object of type byte[] @ {offset}; it wouldn't fit in the read buffer (tried reading {size} bytes into {buffer.Length})!");
if (isLocalPlugin)
{
if(offset == 0)
throw new MemoryAccessException($"Couldn't read {size} bytes of process memory @ 0x{offset:X8}!");

Marshal.Copy((nint)offset, buffer, 0, size);
bytesRead = size;
}
else
{
if (!Imports.ReadProcessMemory((int)omsiProcessHandle, offset, buffer, size, ref bytesRead))
throw new MemoryAccessException($"Couldn't read {size} bytes of process memory @ {offset:X}!");
throw new MemoryAccessException($"Couldn't read {size} bytes of process memory @ 0x{offset:X8}!");
}

return new (buffer, 0, bytesRead);
Expand Down
28 changes: 21 additions & 7 deletions OmsiHook/OmsiHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,21 @@ public OmsiRoadVehicleInst GetRoadVehicleInst(int index)
[Obsolete("This API will be replaced once TMyOMSIList is wrapped!")]
private int GetListItem(int addr, int index)
{
if (index < 0)
return 0;
try
{
return omsiMemory.ReadMemory<int>(omsiMemory.ReadMemory<int>(omsiMemory.ReadMemory<int>(omsiMemory.ReadMemory<int>(addr) + 0x28) + 0x4) + index * 4);
int l = omsiMemory.ReadMemory<int>(addr);
if (l == 0)
return 0;
int l1 = omsiMemory.ReadMemory<int>(l + 0x28);
if (l1 == 0)
return 0;
int l2 = omsiMemory.ReadMemory<int>(l1 + 0x4);
if (l2 == 0)
return 0;
int item = omsiMemory.ReadMemory<int>(l2 + index * 4);
return item;
} catch
{
return 0;
Expand All @@ -218,10 +230,10 @@ private int GetListItem(int addr, int index)
/// </summary>
private void MonitorStateTask()
{
while(!OmsiProcess.HasExited)
while (isLocalPlugin || !OmsiProcess.HasExited)
{
int currentD3DState = omsiMemory.ReadMemory<int>(0x008627d0);
if(currentD3DState != lastD3DState)
if (currentD3DState != lastD3DState)
{
if (currentD3DState != 0)
OnOmsiGotD3DContext?.Invoke(this, new());
Expand All @@ -233,24 +245,26 @@ private void MonitorStateTask()
int currentMapAddr = omsiMemory.ReadMemory<int>(0x861588);
if (currentMapAddr != 0)
{
if(RemoteMethods.IsInitialised && !isD3DReady)
if (RemoteMethods.IsInitialised && !isD3DReady)
isD3DReady = RemoteMethods.OmsiHookD3D();

int currentMapName = omsiMemory.ReadMemory<int>(currentMapAddr + 0x154);
bool currentMapLoaded = omsiMemory.ReadMemory<bool>(currentMapAddr + 0x120);
if(lastMapState != currentMapName)
if (lastMapState != currentMapName)
{
if(currentMapName != 0)
if (currentMapName != 0)
OnMapChange?.Invoke(this, Globals.Map);
lastMapState = currentMapName;
}
if(lastMapLoaded != currentMapLoaded)
if (lastMapLoaded != currentMapLoaded)
{
OnMapLoaded?.Invoke(this, currentMapLoaded);
lastMapLoaded = currentMapLoaded;
}
}
#pragma warning disable CS0618 // Type or member is obsolete
var vehPtr = GetListItem(0x00861508, omsiMemory.ReadMemory<int>(0x00861740));
#pragma warning restore CS0618 // Type or member is obsolete
if (vehPtr != lastVehiclePtr)
{
lastVehiclePtr = vehPtr;
Expand Down
6 changes: 3 additions & 3 deletions OmsiHook/OmsiHook.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<Description>OmsiHook is a simple library for hooking into Omsi's memory for modding.</Description>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<AssemblyVersion>2.5.1.1</AssemblyVersion>
<FileVersion>2.5.1.1</FileVersion>
<Version>2.5.1</Version>
<AssemblyVersion>2.5.2.1</AssemblyVersion>
<FileVersion>2.5.2.1</FileVersion>
<Version>2.5.2</Version>
<PackageLicenseExpression>LGPL-3.0-only</PackageLicenseExpression>
<SignAssembly>False</SignAssembly>
<PackageReadmeFile>README.md</PackageReadmeFile>
Expand Down
8 changes: 5 additions & 3 deletions OmsiHook/OmsiHookRPCMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal enum RemoteMethod : int
UpdateSubresource,
ReleaseTexture,
GetTextureDesc,
GetTextureLevelCount,
IsTexture,
RVTriggerXML,
SoundTrigger
Expand All @@ -37,10 +38,11 @@ internal enum RemoteMethod : int
{ RemoteMethod.GetMem, 4 },
{ RemoteMethod.FreeMem, 4 },
{ RemoteMethod.HookD3D, 0 },
{ RemoteMethod.CreateTexture, 16 },
{ RemoteMethod.UpdateSubresource, 36 },
{ RemoteMethod.CreateTexture, 20 },
{ RemoteMethod.UpdateSubresource, 40 },
{ RemoteMethod.ReleaseTexture, 4 },
{ RemoteMethod.GetTextureDesc, 32 },
{ RemoteMethod.GetTextureDesc, 36 },
{ RemoteMethod.GetTextureLevelCount, 4 },
{ RemoteMethod.IsTexture, 4 },
{ RemoteMethod.RVTriggerXML, 12 },
{ RemoteMethod.SoundTrigger, 12 },
Expand Down
Loading

0 comments on commit 6d43a4a

Please sign in to comment.