Skip to content

Commit

Permalink
Merge pull request #98 from space928/remote-method-fixes
Browse files Browse the repository at this point in the history
Various bugfixes for remote methods and textures in local plugins:
 - OmsiRemoteMethods is no longer static and can be accessed through the instance of OmsiHook/Memory
 - Updated dependent classes to use the instance of OmsiRemoteMethods
 - D3DTexture implements IDisposable to prevent memory leaks
 - Memory and OmsiHook now implement IDisposable to tidy up resources
 - Implemented fast path for local plugins for all Memory read/write/allocate operations in Memory
 - Enabled unsafe compilation of OmsiHook to support fast path for Memory operations
 - Various other local plugin related bug fixes
 - Fixed dereferencing bug in OmsiCreateTextureAsync when using local plugins
 - Improved D3D hooking reliability
 - Implemented small test for D3DTexture usage in a local plugin
 - Updated DNNE
 - Added D3DTexture mipmap support
 - 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 (used for OmsiHook events) in local plugins
 - Disposing of OmsiRemoteMethods (which is done automatically when disposing of OmsiHook) now closes the RPC session correctly
 - CloseRPCSession() is now awaitable
 - Implemented MakeVehicle() and TempRVListCreate()
 - Added MemArray for TOList
 - Fixed some broken arrays in OmsiComplObj
 - Fixed an incorrectly parsed string in OmsiMap
 - Improved error reporting in Memory.cs
 - Improved safety/performance of some core methods in Memory.cs
 - Slightly improved the performance of string reading
 - Rewrote MarhsalStruct/UnMarshalStruct to make use of compiled Expression Trees to allow for much faster marshalling of data without the need for reflection every time. (A cached reflection based fallback has been implemented as well)
 - Fixed OmsiAmpelGroup marshalling
 - Fixed OmsiOFTTex marshalling
 - Some bugfixes to multiple sessions with OmsiHookRPCPlugin
  • Loading branch information
space928 authored Sep 18, 2024
2 parents 7b054e3 + 1e95b3e commit 3720035
Show file tree
Hide file tree
Showing 30 changed files with 2,760 additions and 908 deletions.
63 changes: 41 additions & 22 deletions OmsiExtensionsCLI/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Reflection;
Expand Down Expand Up @@ -54,37 +55,55 @@ static void Main(string[] args)
Console.WriteLine($"Read data: map:{map?.Name} path:{map?.Filename} friendly:{map?.FriendlyName}".PadRight(Console.WindowWidth - 1));
Console.WriteLine($"Time: {omsi.Globals.Time.Day}/{omsi.Globals.Time.Month}/{omsi.Globals.Time.Year} - {omsi.Globals.Time.Hour}:{omsi.Globals.Time.Minute}:{omsi.Globals.Time.Second:F2} ");
Console.WriteLine($"Camera pos: {cam.Pos} ".PadRight(Console.WindowWidth - 1));
Console.WriteLine($"{omsi.Globals.Drivers}".PadRight(Console.WindowWidth - 1));
//Console.WriteLine($"{playerVehicle.ComplObjInst.ComplObj.OFTTex.Count} ".PadRight(Console.WindowWidth - 1));
//Console.WriteLine($"{omsi.Globals.Drivers}".PadRight(Console.WindowWidth - 1));

/*if(!dXTests.IsReady)
dXTests.CreateTexture();
if(dXTests.IsReady)
dXTests.UpdateTexture();*/
///*if(!dXTests.IsReady)
// dXTests.CreateTexture();
//if(dXTests.IsReady)
// dXTests.UpdateTexture();*/

Console.WriteLine($"[MOUSE] pos: {progMan.MausPos}".PadRight(Console.WindowWidth - 1));
Console.WriteLine($"[MOUSE] ray_pos: {progMan.MausLine3DPos} ray_dir: {progMan.MausLine3DDir}".PadRight(Console.WindowWidth - 1));
Console.WriteLine($"[MOUSE] mouse_mesh_event: {progMan.Maus_MeshEvent}".PadRight(Console.WindowWidth - 1));
CheckClickPos(progMan, meshes, meshInsts);
//Console.WriteLine($"[MOUSE] pos: {progMan.MausPos}".PadRight(Console.WindowWidth - 1));
//Console.WriteLine($"[MOUSE] ray_pos: {progMan.MausLine3DPos} ray_dir: {progMan.MausLine3DDir}".PadRight(Console.WindowWidth - 1));
//Console.WriteLine($"[MOUSE] mouse_mesh_event: {progMan.Maus_MeshEvent}".PadRight(Console.WindowWidth - 1));
//CheckClickPos(progMan, meshes, meshInsts);

Console.WriteLine($"Loaded textures: {textures.Count}");
Console.WriteLine($"Path id: {playerVehicle.PathInfo.path.path}");
//Console.WriteLine($"Loaded textures: {textures.Count}");
//Console.WriteLine($"Path id: {playerVehicle.PathInfo.path.path}");

/*Console.WriteLine("".PadRight(Console.WindowWidth-1));
try
{
if (omsi.Globals.PlayerVehicle != null)
{
Console.WriteLine($"INEO_PS_Matricule: {omsi.Globals.PlayerVehicle.GetStringVariable("INEO_Login")}".PadRight(Console.WindowWidth - 1));
///*Console.WriteLine("".PadRight(Console.WindowWidth-1));
//try
//{
// if (omsi.Globals.PlayerVehicle != null)
// {
// Console.WriteLine($"INEO_PS_Matricule: {omsi.Globals.PlayerVehicle.GetStringVariable("INEO_Login")}".PadRight(Console.WindowWidth - 1));


omsi.Globals.PlayerVehicle.SetStringVariable("INEO_Login", toggle ? "thomas" : "01234");
toggle = !toggle;
}
}
catch (Exception e) { Console.WriteLine(e.Message); }*/
// omsi.Globals.PlayerVehicle.SetStringVariable("INEO_Login", toggle ? "thomas" : "01234");
// toggle = !toggle;
// }
//}
//catch (Exception e) { Console.WriteLine(e.Message); }*/


/*var OMSIRM = omsi.RemoteMethods;
//OMSIRM.PlaceRandomBus();
Console.WriteLine("Placed");
OMSIRM.OmsiSetCriticalSectionLock(omsi.Globals.ProgamManager.CS_MakeVehiclePtr).ContinueWith((_) =>
{
OMSIRM.MakeVehicle(@"Vehicles\GPM_MAN_LionsCity_M\MAN_A47.bus", __copyToMainList: true).ContinueWith((id) =>
{
Console.WriteLine($"Spawned Vehicle ID: {id.Result}");
OMSIRM.OmsiReleaseCriticalSectionLock(omsi.Globals.ProgamManager.CS_MakeVehiclePtr).ContinueWith((_)=>Console.WriteLine($"Unlock"));
});
});
break;
Debugger.Break();*/

Thread.Sleep(20);
}
Console.ReadLine();
}

private static void CheckClickPos(OmsiProgMan progMan, MemArrayList<OmsiAnimSubMesh> meshes, MemArrayList<OmsiAnimSubMeshInst> meshInsts)
Expand Down
67 changes: 47 additions & 20 deletions OmsiHook/D3DTexture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace OmsiHook
{
public class D3DTexture : OmsiObject
public class D3DTexture : OmsiObject, IDisposable
{
internal D3DTexture(Memory omsiMemory, int baseAddress) : base(omsiMemory, baseAddress) { }
/// <summary>
Expand All @@ -21,9 +21,11 @@ public D3DTexture() : base() { }

private uint width, height;
private D3DFORMAT format;
private uint levels;
private int stagingBufferSize;
// private byte[] stagingBuffer;
private uint remoteStagingBufferPtr;
private const bool useFastAlloc = true;

/// <summary>
/// Gets the address of the native <c>IDirect3DTexture9</c> object.
Expand All @@ -42,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 @@ -74,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 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: true));
isCreated = true;
if (initialiseForWriting)
await InitialiseForWriting();
}

/// <summary>
Expand All @@ -96,7 +102,12 @@ public async Task InitialiseForWriting()
{
if (!IsValid)
throw new NotInitialisedException("The texture has not been created yet! Make sure to call CreateD3DTexture().");
remoteStagingBufferPtr = unchecked((uint)await Memory.AllocRemoteMemory(stagingBufferSize, fastAlloc: true));
if(remoteStagingBufferPtr != 0)
{
Memory.FreeRemoteMemory(remoteStagingBufferPtr, fastAlloc: useFastAlloc);
remoteStagingBufferPtr = 0;
}
remoteStagingBufferPtr = unchecked((uint)await Memory.AllocRemoteMemory(stagingBufferSize, fastAlloc: useFastAlloc));
}

/// <summary>
Expand All @@ -105,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 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: true));
isCreated = true;
if (initialiseForWriting)
await InitialiseForWriting();
}

/// <summary>
Expand All @@ -134,44 +146,59 @@ public async Task CreateD3DTexture(uint width, uint height, D3DFORMAT format = D
/// <exception cref="Exception"></exception>
public async Task ReleaseTexture()
{
if(Address == 0)
throw new NullReferenceException("Texture was already null!");
if (Address == 0)
return;
// throw new NullReferenceException("Texture was already null!");

isCreated = false;
HRESULT hr = await OmsiReleaseTextureAsync(unchecked((uint)Address));
HRESULT hr = await Memory.RemoteMethods.OmsiReleaseTextureAsync(TextureAddress);
if(HRESULTFailed(hr))
throw new Exception(hr.ToString());
Address = 0;
Memory.FreeRemoteMemory(remoteStagingBufferPtr, fastAlloc: true);
Memory.FreeRemoteMemory(remoteStagingBufferPtr, fastAlloc: useFastAlloc);
//stagingBuffer = null;
remoteStagingBufferPtr = 0;
}

/// <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 (!IsInitialised || remoteStagingBufferPtr == 0)
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);

Memory.WriteMemory(remoteStagingBufferPtr, textureData.Span);

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

HRESULT hr = await 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);
}

/// <summary>
/// Disposes of this texture by calling <see cref="ReleaseTexture"/> and waiting synchronously.
/// </summary>
public void Dispose()
{
ReleaseTexture().Wait();
}

/// <summary>
/// A struct representing RGBA pixel data. Compatible with <see cref="D3DFORMAT.D3DFMT_A8R8G8B8"/>.
/// </summary>
Expand Down
64 changes: 64 additions & 0 deletions OmsiHook/FastBinaryWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace OmsiHook;

internal static class FastBinaryWriter
{
public static void Write(Span<byte> buffer, ref int pos, int data)
{
MemoryMarshal.Write(buffer[pos..], ref data);
pos += 4;
}

public static void Write(Span<byte> buffer, ref int pos, uint data)
{
MemoryMarshal.Write(buffer[pos..], ref data);
pos += 4;
}

public static void Write(Span<byte> buffer, ref int pos, short data)
{
MemoryMarshal.Write(buffer[pos..], ref data);
pos += 2;
}
public static void Write(Span<byte> buffer, ref int pos, ushort data)
{
MemoryMarshal.Write(buffer[pos..], ref data);
pos += 2;
}

public static void Write(Span<byte> buffer, ref int pos, byte data, int advance = 1)
{
MemoryMarshal.Write(buffer[pos..], ref data);
pos += advance;
}

public static void Write(Span<byte> buffer, ref int pos, sbyte data)
{
MemoryMarshal.Write(buffer[pos..], ref data);
pos += 1;
}

public static void Write(Span<byte> buffer, ref int pos, bool data)
{
MemoryMarshal.Write(buffer[pos..], ref data);
pos += 1;
}

public static void Write(Span<byte> buffer, ref int pos, float data)
{
MemoryMarshal.Write(buffer[pos..], ref data);
pos += 4;
}

public static void Write(byte[] buffer, ref int pos, int data)
{
MemoryMarshal.Write(buffer.AsSpan()[pos..], ref data);
pos += 4;
}
}
Loading

0 comments on commit 3720035

Please sign in to comment.