diff --git a/OmsiExtensionsCLI/Program.cs b/OmsiExtensionsCLI/Program.cs index 9a59d3e..d208c9d 100644 --- a/OmsiExtensionsCLI/Program.cs +++ b/OmsiExtensionsCLI/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Numerics; using System.Reflection; @@ -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 meshes, MemArrayList meshInsts) diff --git a/OmsiHook/D3DTexture.cs b/OmsiHook/D3DTexture.cs index f04aeb9..fc0f427 100644 --- a/OmsiHook/D3DTexture.cs +++ b/OmsiHook/D3DTexture.cs @@ -10,7 +10,7 @@ namespace OmsiHook { - public class D3DTexture : OmsiObject + public class D3DTexture : OmsiObject, IDisposable { internal D3DTexture(Memory omsiMemory, int baseAddress) : base(omsiMemory, baseAddress) { } /// @@ -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; /// /// Gets the address of the native IDirect3DTexture9 object. @@ -42,6 +44,10 @@ public D3DTexture() : base() { } /// public D3DFORMAT Format => format; /// + /// Gets the number of mipmap levels in the texture. + /// + public uint Levels => levels; + /// /// Returns true if this object is initialised. /// public bool IsValid => TextureAddress != 0 && isCreated; @@ -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(); } /// @@ -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)); } /// @@ -105,26 +116,27 @@ public async Task InitialiseForWriting() /// the width of the texture /// the height of the texture /// the of the texture + /// the number of mipmap levels to create /// whether to allow writing to this texture /// - 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(); } /// @@ -134,15 +146,16 @@ public async Task CreateD3DTexture(uint width, uint height, D3DFORMAT format = D /// 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; } @@ -150,28 +163,42 @@ public async Task ReleaseTexture() /// /// Updates the contents of the IDirect3DTexture9 object from a buffer. /// + /// + /// 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. + /// /// The pixel element type /// a buffer of memory containing the new texture data layed out sequentially in row-major order /// optionally, a rectangle specifying the area of the texture to update; the /// must match the size of this rectangle; pass in to update the whole texture + /// optionally, the mipmap level to update /// /// - public async Task UpdateTexture(Memory textureData, Rectangle? updateArea = null) where T : unmanaged + public async Task UpdateTexture(Memory 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()) > stagingBufferSize) + if((textureData.Length * Unsafe.SizeOf()) > 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); } + /// + /// Disposes of this texture by calling and waiting synchronously. + /// + public void Dispose() + { + ReleaseTexture().Wait(); + } + /// /// A struct representing RGBA pixel data. Compatible with . /// diff --git a/OmsiHook/FastBinaryWriter.cs b/OmsiHook/FastBinaryWriter.cs new file mode 100644 index 0000000..489f7b7 --- /dev/null +++ b/OmsiHook/FastBinaryWriter.cs @@ -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 buffer, ref int pos, int data) + { + MemoryMarshal.Write(buffer[pos..], ref data); + pos += 4; + } + + public static void Write(Span buffer, ref int pos, uint data) + { + MemoryMarshal.Write(buffer[pos..], ref data); + pos += 4; + } + + public static void Write(Span buffer, ref int pos, short data) + { + MemoryMarshal.Write(buffer[pos..], ref data); + pos += 2; + } + public static void Write(Span buffer, ref int pos, ushort data) + { + MemoryMarshal.Write(buffer[pos..], ref data); + pos += 2; + } + + public static void Write(Span buffer, ref int pos, byte data, int advance = 1) + { + MemoryMarshal.Write(buffer[pos..], ref data); + pos += advance; + } + + public static void Write(Span buffer, ref int pos, sbyte data) + { + MemoryMarshal.Write(buffer[pos..], ref data); + pos += 1; + } + + public static void Write(Span buffer, ref int pos, bool data) + { + MemoryMarshal.Write(buffer[pos..], ref data); + pos += 1; + } + + public static void Write(Span 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; + } +} diff --git a/OmsiHook/MemArray/MemArrayObjList.cs b/OmsiHook/MemArray/MemArrayObjList.cs index e974cc8..2b7cc01 100644 --- a/OmsiHook/MemArray/MemArrayObjList.cs +++ b/OmsiHook/MemArray/MemArrayObjList.cs @@ -4,118 +4,234 @@ using System.Text; using System.Threading.Tasks; -namespace OmsiHook +namespace OmsiHook; + +/// +/// Wrapper for Lists in OMSI's Memory. +/// +/// +/// +/// +/// +public class MemArrayList : MemArrayBase where T : OmsiObject, new() { - /// - /// Wrapper for Lists in OMSI's Memory. - /// - /// - /// - /// - /// - public class MemArrayList : MemArrayBase where T : OmsiObject, new() + private T[] ReadList() { - private T[] ReadList() + uint arr = Memory.ReadMemory(Address); + if (arr == 0) + return Array.Empty(); + uint len = Memory.ReadMemory(arr + 8); + uint arrayData = Memory.ReadMemory(arr + 4); + T[] ret = new T[len]; + for (uint i = 0; i < len; i++) { - uint arr = Memory.ReadMemory(Address); - if (arr == 0) - return Array.Empty(); - uint len = Memory.ReadMemory(arr + 8); - uint arrayData = Memory.ReadMemory(arr + 4); - T[] ret = new T[len]; - for (uint i = 0; i < len; i++) + var objAddr = Memory.ReadMemory(arrayData + i * 4); + if (objAddr == 0) { - var objAddr = Memory.ReadMemory(arrayData + i * 4); - if (objAddr == 0) - { - ret[i] = null; - continue; - } - - var n = new T(); - n.InitObject(Memory, (int)objAddr); - ret[i] = n; + ret[i] = null; + continue; } - return ret; + var n = new T(); + n.InitObject(Memory, (int)objAddr); + ret[i] = n; } - private T ReadListItem(int index) - { - int arr = Memory.ReadMemory(Address); - if (arr == 0) - return null; + return ret; + } - int len = Memory.ReadMemory(arr + 8); - int arrayData = Memory.ReadMemory(arr + 4); + private T ReadListItem(int index) + { + int arr = Memory.ReadMemory(Address); + if (arr == 0) + return null; - if (index >= len || index < 0) - throw new ArgumentOutOfRangeException(nameof(index)); + int len = Memory.ReadMemory(arr + 8); + int arrayData = Memory.ReadMemory(arr + 4); - var objAddr = Memory.ReadMemory(arrayData + index * 4); - if (objAddr == 0) - return null; + if (index >= len || index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); - var n = new T(); - n.InitObject(Memory, objAddr); + var objAddr = Memory.ReadMemory(arrayData + index * 4); + if (objAddr == 0) + return null; + + var n = new T(); + n.InitObject(Memory, objAddr); - return n; + return n; + } + + /// + /// Gets the current contents of the wrapped array. On non-cached arrays this is slow. + /// + public override T[] WrappedArray => cached ? arrayCache : ReadList(); + + public MemArrayList() : base() { } + + internal MemArrayList(Memory memory, int address, bool cached = true) : base(memory, address, cached) { } + + public override void UpdateFromHook(int index = -1) + { + if (cached) + { + if (index < 0) + arrayCache = ReadList(); + else + arrayCache[index] = ReadListItem(index); } + } + + public override T this[int index] + { + get => cached ? arrayCache[index] : ReadListItem(index); + set => throw new NotSupportedException(); + } + + /// + /// TODO: Implement efficient enumerator for non-cached arrays. + /// + /// + public override IEnumerator GetEnumerator() => ((IEnumerable)WrappedArray).GetEnumerator(); + + public override void Add(T item) + { + throw new NotSupportedException(); + } + + public override void Clear() + { + base.Clear(); + if (cached) + arrayCache = Array.Empty(); + } + + public override bool Remove(T item) => throw new NotImplementedException(); - /// - /// Gets the current contents of the wrapped array. On non-cached arrays this is slow. - /// - public override T[] WrappedArray => cached ? arrayCache : ReadList(); + public override void Insert(int index, T item) => throw new NotImplementedException(); - public MemArrayList() : base() { } + public override void RemoveAt(int index) => throw new NotImplementedException(); - internal MemArrayList(Memory memory, int address, bool cached = true) : base(memory, address, cached) { } + public override int IndexOf(T item) => Array.IndexOf(WrappedArray, item); - public override void UpdateFromHook(int index = -1) + public override bool Contains(T item) => WrappedArray.Contains(item); + + public override void CopyTo(T[] array, int arrayIndex) => WrappedArray.CopyTo(array, arrayIndex); +} + +/// +/// Wrapper for OLists in OMSI's Memory. +/// +/// +/// +/// +/// +public class MemArrayOList : MemArrayBase where T : OmsiObject, new() +{ + public int IntCount => Memory.ReadMemory(Address + 0x8); + //public int Count => Memory.ReadMemory(Address + 0xc); + public int MaxStepCount => Memory.ReadMemory(Address + 0x10); + + private T[] ReadList() + { + uint arr = Memory.ReadMemory(Address); + if (arr == 0) + return Array.Empty(); + uint len = Memory.ReadMemory(arr + 0xc); + uint arrayData = Memory.ReadMemory(arr + 4); + T[] ret = new T[len]; + for (uint i = 0; i < len; i++) { - if (cached) + var objAddr = Memory.ReadMemory(arrayData + i * 4); + if (objAddr == 0) { - if (index < 0) - arrayCache = ReadList(); - else - arrayCache[index] = ReadListItem(index); + ret[i] = null; + continue; } - } - public override T this[int index] - { - get => cached ? arrayCache[index] : ReadListItem(index); - set => throw new NotSupportedException(); + var n = new T(); + n.InitObject(Memory, (int)objAddr); + ret[i] = n; } - /// - /// TODO: Implement efficient enumerator for non-cached arrays. - /// - /// - public override IEnumerator GetEnumerator() => ((IEnumerable)WrappedArray).GetEnumerator(); + return ret; + } - public override void Add(T item) - { - throw new NotSupportedException(); - } + private T ReadListItem(int index) + { + int arr = Memory.ReadMemory(Address); + if (arr == 0) + return null; + + int len = Memory.ReadMemory(arr + 0xc); + int arrayData = Memory.ReadMemory(arr + 4); + + if (index >= len || index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + + var objAddr = Memory.ReadMemory(arrayData + index * 4); + if (objAddr == 0) + return null; + + var n = new T(); + n.InitObject(Memory, objAddr); + + return n; + } + + /// + /// Gets the current contents of the wrapped array. On non-cached arrays this is slow. + /// + public override T[] WrappedArray => cached ? arrayCache : ReadList(); + + public MemArrayOList() : base() { } + + internal MemArrayOList(Memory memory, int address, bool cached = true) : base(memory, address, cached) { } - public override void Clear() + public override void UpdateFromHook(int index = -1) + { + if (cached) { - base.Clear(); - if (cached) - arrayCache = Array.Empty(); + if (index < 0) + arrayCache = ReadList(); + else + arrayCache[index] = ReadListItem(index); } + } + + public override T this[int index] + { + get => cached ? arrayCache[index] : ReadListItem(index); + set => throw new NotSupportedException(); + } + + /// + /// TODO: Implement efficient enumerator for non-cached arrays. + /// + /// + public override IEnumerator GetEnumerator() => ((IEnumerable)WrappedArray).GetEnumerator(); - public override bool Remove(T item) => throw new NotImplementedException(); + public override void Add(T item) + { + throw new NotSupportedException(); + } - public override void Insert(int index, T item) => throw new NotImplementedException(); + public override void Clear() + { + base.Clear(); + if (cached) + arrayCache = Array.Empty(); + } - public override void RemoveAt(int index) => throw new NotImplementedException(); + public override bool Remove(T item) => throw new NotImplementedException(); - public override int IndexOf(T item) => Array.IndexOf(WrappedArray, item); + public override void Insert(int index, T item) => throw new NotImplementedException(); - public override bool Contains(T item) => WrappedArray.Contains(item); + public override void RemoveAt(int index) => throw new NotImplementedException(); - public override void CopyTo(T[] array, int arrayIndex) => WrappedArray.CopyTo(array, arrayIndex); - } + public override int IndexOf(T item) => Array.IndexOf(WrappedArray, item); + + public override bool Contains(T item) => WrappedArray.Contains(item); + + public override void CopyTo(T[] array, int arrayIndex) => WrappedArray.CopyTo(array, arrayIndex); } diff --git a/OmsiHook/MemArray/MemArrayStructList.cs b/OmsiHook/MemArray/MemArrayStructList.cs index bbd0068..d68e7a4 100644 --- a/OmsiHook/MemArray/MemArrayStructList.cs +++ b/OmsiHook/MemArray/MemArrayStructList.cs @@ -1,122 +1,236 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; -namespace OmsiHook +namespace OmsiHook; + +/// +/// Wrapper for Lists in OMSI's Memory. +/// +/// +/// +/// +/// +/// Internal struct type to marshal Struct from +public class MemArrayList : MemArrayBase + where InternalStruct : unmanaged + where Struct : struct { - /// - /// Wrapper for Lists in OMSI's Memory. - /// - /// - /// - /// - /// - /// Internal struct type to marshal Struct from - public class MemArrayList : MemArrayBase - where InternalStruct : unmanaged - where Struct : struct + private Struct[] ReadList() { - private Struct[] ReadList() + uint arr = Memory.ReadMemory(Address); + if (arr == 0) + return Array.Empty(); + uint len = Memory.ReadMemory(arr + 8); + uint arrayData = Memory.ReadMemory(arr + 4); + Struct[] ret = new Struct[len]; + for (uint i = 0; i < len; i++) { - uint arr = Memory.ReadMemory(Address); - if (arr == 0) - return Array.Empty(); - uint len = Memory.ReadMemory(arr + 8); - uint arrayData = Memory.ReadMemory(arr + 4); - Struct[] ret = new Struct[len]; - for (uint i = 0; i < len; i++) + var objAddr = Memory.ReadMemory(arrayData + i * 4); + if (objAddr == 0) { - var objAddr = Memory.ReadMemory(arrayData + i * 4); - if (objAddr == 0) - { - ret[i] = default; - continue; - } - - var n = Memory.MarshalStruct(Memory.ReadMemory(objAddr)); - ret[i] = n; + ret[i] = default; + continue; } - return ret; + var n = Memory.MarshalStruct(Memory.ReadMemory(objAddr)); + ret[i] = n; } - private Struct ReadListItem(int index) - { - int arr = Memory.ReadMemory(Address); - if (arr == 0) - return default; + return ret; + } - int len = Memory.ReadMemory(arr + 8); - int arrayData = Memory.ReadMemory(arr + 4); + private Struct ReadListItem(int index) + { + int arr = Memory.ReadMemory(Address); + if (arr == 0) + return default; - if (index >= len || index < 0) - throw new ArgumentOutOfRangeException(nameof(index)); + int len = Memory.ReadMemory(arr + 8); + int arrayData = Memory.ReadMemory(arr + 4); - var objAddr = Memory.ReadMemory(arrayData + index * 4); - if (objAddr == 0) - return default; + if (index >= len || index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); - var n = Memory.MarshalStruct(Memory.ReadMemory(objAddr)); + var objAddr = Memory.ReadMemory(arrayData + index * 4); + if (objAddr == 0) + return default; - return n; + var n = Memory.MarshalStruct(Memory.ReadMemory(objAddr)); + + return n; + } + + /// + /// Gets the current contents of the wrapped array. On non-cached arrays this is slow. + /// + public override Struct[] WrappedArray => cached ? arrayCache : ReadList(); + + public MemArrayList() : base() { } + + internal MemArrayList(Memory memory, int address, bool cached = true) : base(memory, address, cached) { } + + public override void UpdateFromHook(int index = -1) + { + if (cached) + { + if (index < 0) + arrayCache = ReadList(); + else + arrayCache[index] = ReadListItem(index); } + } + + public override Struct this[int index] + { + get => cached ? arrayCache[index] : ReadListItem(index); + set => throw new NotSupportedException(); + } + + /// + /// TODO: Implement efficient enumerator for non-cached arrays. + /// + /// + public override IEnumerator GetEnumerator() => ((IEnumerable)WrappedArray).GetEnumerator(); + + public override void Add(Struct item) + { + throw new NotSupportedException(); + } + + public override void Clear() + { + base.Clear(); + if (cached) + arrayCache = Array.Empty(); + } - /// - /// Gets the current contents of the wrapped array. On non-cached arrays this is slow. - /// - public override Struct[] WrappedArray => cached ? arrayCache : ReadList(); + public override bool Remove(Struct item) => throw new NotImplementedException(); - public MemArrayList() : base() { } + public override void Insert(int index, Struct item) => throw new NotImplementedException(); - internal MemArrayList(Memory memory, int address, bool cached = true) : base(memory, address, cached) { } + public override void RemoveAt(int index) => throw new NotImplementedException(); + + public override int IndexOf(Struct item) => Array.IndexOf(WrappedArray, item); + + public override bool Contains(Struct item) => WrappedArray.Contains(item); + + public override void CopyTo(Struct[] array, int arrayIndex) => WrappedArray.CopyTo(array, arrayIndex); +} - public override void UpdateFromHook(int index = -1) +/// +/// Wrapper for Lists in OMSI's Memory. +/// +/// +/// +/// +/// +/// Internal struct type to marshal Struct from +public class MemArrayOList : MemArrayBase + where InternalStruct : unmanaged + where Struct : struct +{ + private Struct[] ReadList() + { + uint arr = Memory.ReadMemory(Address); + if (arr == 0) + return Array.Empty(); + uint len = Memory.ReadMemory(arr + 0xc); + uint arrayData = Memory.ReadMemory(arr + 4); + Struct[] ret = new Struct[len]; + for (uint i = 0; i < len; i++) { - if (cached) + var objAddr = arrayData + i * (uint)Unsafe.SizeOf(); + if (objAddr == 0) { - if (index < 0) - arrayCache = ReadList(); - else - arrayCache[index] = ReadListItem(index); + ret[i] = default; + continue; } - } - public override Struct this[int index] - { - get => cached ? arrayCache[index] : ReadListItem(index); - set => throw new NotSupportedException(); + var n = Memory.MarshalStruct(Memory.ReadMemory(objAddr)); + ret[i] = n; } - /// - /// TODO: Implement efficient enumerator for non-cached arrays. - /// - /// - public override IEnumerator GetEnumerator() => ((IEnumerable)WrappedArray).GetEnumerator(); + return ret; + } - public override void Add(Struct item) - { - throw new NotSupportedException(); - } + private Struct ReadListItem(int index) + { + int arr = Memory.ReadMemory(Address); + if (arr == 0) + return default; + + int len = Memory.ReadMemory(arr + 0xc); + int arrayData = Memory.ReadMemory(arr + 4); + + if (index >= len || index < 0) + throw new ArgumentOutOfRangeException(nameof(index)); + + var objAddr = arrayData + index * Unsafe.SizeOf(); + if (objAddr == 0) + return default; + + var n = Memory.MarshalStruct(Memory.ReadMemory(objAddr)); + + return n; + } + + /// + /// Gets the current contents of the wrapped array. On non-cached arrays this is slow. + /// + public override Struct[] WrappedArray => cached ? arrayCache : ReadList(); + + public MemArrayOList() : base() { } + + internal MemArrayOList(Memory memory, int address, bool cached = true) : base(memory, address, cached) { } - public override void Clear() + public override void UpdateFromHook(int index = -1) + { + if (cached) { - base.Clear(); - if (cached) - arrayCache = Array.Empty(); + if (index < 0) + arrayCache = ReadList(); + else + arrayCache[index] = ReadListItem(index); } + } + + public override Struct this[int index] + { + get => cached ? arrayCache[index] : ReadListItem(index); + set => throw new NotSupportedException(); + } + + /// + /// TODO: Implement efficient enumerator for non-cached arrays. + /// + /// + public override IEnumerator GetEnumerator() => ((IEnumerable)WrappedArray).GetEnumerator(); - public override bool Remove(Struct item) => throw new NotImplementedException(); + public override void Add(Struct item) + { + throw new NotSupportedException(); + } - public override void Insert(int index, Struct item) => throw new NotImplementedException(); + public override void Clear() + { + base.Clear(); + if (cached) + arrayCache = Array.Empty(); + } - public override void RemoveAt(int index) => throw new NotImplementedException(); + public override bool Remove(Struct item) => throw new NotImplementedException(); - public override int IndexOf(Struct item) => Array.IndexOf(WrappedArray, item); + public override void Insert(int index, Struct item) => throw new NotImplementedException(); - public override bool Contains(Struct item) => WrappedArray.Contains(item); + public override void RemoveAt(int index) => throw new NotImplementedException(); - public override void CopyTo(Struct[] array, int arrayIndex) => WrappedArray.CopyTo(array, arrayIndex); - } + public override int IndexOf(Struct item) => Array.IndexOf(WrappedArray, item); + + public override bool Contains(Struct item) => WrappedArray.Contains(item); + + public override void CopyTo(Struct[] array, int arrayIndex) => WrappedArray.CopyTo(array, arrayIndex); } diff --git a/OmsiHook/Memory.cs b/OmsiHook/Memory.cs index 87f0bf3..0e2137b 100644 --- a/OmsiHook/Memory.cs +++ b/OmsiHook/Memory.cs @@ -16,13 +16,23 @@ namespace OmsiHook /// Memory management class for accessing and marshaling OMSI's memory /// #if DEBUG - public class Memory + public class Memory : IDisposable #else - internal class Memory + internal class Memory : IDisposable #endif { private Process omsiProcess; private IntPtr omsiProcessHandle; + private bool isLocalPlugin; + private OmsiRemoteMethods remoteMethods; + + public Process OmsiProcess => omsiProcess; + public IntPtr OmsiProcessHandle => omsiProcessHandle; + public OmsiRemoteMethods RemoteMethods + { + get => remoteMethods; + internal set => remoteMethods = value; + } /// /// Buffer to read remote memory into. 4k should be large enough for any structs we encounter as they @@ -39,11 +49,11 @@ internal class Memory /// Attempts to attach to a given process as a debugger. /// /// The name of the process to attach to eg: "OMSI.exe" - /// A tuple containing whether or not the process attached successfully and the Process instance - public (bool, Process proc) Attach(string procName) + /// Whether the process attached successfully + public bool Attach(string procName) { - if (Process.GetProcessesByName(procName).Length <= 0) - return (false, null); + if (Process.GetProcessesByName(procName).Length <= 0) + return false; omsiProcess = Process.GetProcessesByName(procName)[0]; try @@ -60,13 +70,19 @@ internal class Memory #else catch { } #endif - omsiProcessHandle = - Imports.OpenProcess( - (int) (Imports.OpenProcessFlags.PROCESS_VM_OPERATION | - Imports.OpenProcessFlags.PROCESS_VM_READ | Imports.OpenProcessFlags.PROCESS_VM_WRITE), - false, omsiProcess.Id); + isLocalPlugin = Process.GetCurrentProcess().ProcessName == omsiProcess.ProcessName; + if (!isLocalPlugin) + omsiProcessHandle = + Imports.OpenProcess( + (int)(Imports.OpenProcessFlags.PROCESS_VM_OPERATION | + Imports.OpenProcessFlags.PROCESS_VM_READ | Imports.OpenProcessFlags.PROCESS_VM_WRITE), + false, omsiProcess.Id); +#if DEBUG + if (!isLocalPlugin && omsiProcessHandle == IntPtr.Zero) + Console.WriteLine("Couldn't open a handle to the process! Some memory operations may fail!"); +#endif - return (true, omsiProcess); + return true; } #region Memory Writing Methods @@ -78,19 +94,61 @@ internal class Memory /// The address of the data to set /// The new value to set; this must be of the /// correct data type to avoid memory corruption + /// public void WriteMemory(int address, T value) where T : unmanaged { + 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)} bytes of process memory @ 0x{address:X8}!", address); +#endif + Unsafe.Copy((void*)address, ref value); + return; + } + } + var size = StructureToByteArray(value, writeBuffer.Value, 0); - Imports.WriteProcessMemory((int)omsiProcessHandle, address, writeBuffer.Value, size, out _); + if (!Imports.WriteProcessMemory((int)omsiProcessHandle, address, writeBuffer.Value, size, out _)) + throw new MemoryAccessException($"Couldn't write {Unsafe.SizeOf()} bytes of process memory @ 0x{address:X8}!", address); } /// public void WriteMemory(uint address, T value) where T : unmanaged { + 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)} bytes of process memory @ 0x{address:X8}!", address); +#endif + + Unsafe.Copy((void*)address, ref value); + return; + } + } + var size = StructureToByteArray(value, writeBuffer.Value, 0); - Imports.WriteProcessMemory((int)omsiProcessHandle, unchecked((int)address), writeBuffer.Value, size, out _); + if (!Imports.WriteProcessMemory((int)omsiProcessHandle, unchecked((int)address), writeBuffer.Value, size, out _)) + throw new MemoryAccessException($"Couldn't write {Unsafe.SizeOf()} bytes of process memory @ 0x{address:X8}!", address); } /// @@ -106,22 +164,10 @@ public void WriteMemory(uint address, T value) where T : unmanaged /// The address of the data to set /// The array of new values to set; this must be of the /// correct data type to avoid memory corruption + /// public void WriteMemory(int address, T[] values) where T : unmanaged { - if(typeof(T) == typeof(byte)) - Imports.WriteProcessMemory((int)omsiProcessHandle, address, Unsafe.As(values), values.Length, out _); - - // Not very safe, but avoids copying memory if we don't need to - var memory = values.AsMemory(); - using var handle = memory.Pin(); - var bytes = MemoryMarshal.AsBytes(memory.Span); - Imports.WriteProcessMemory((int)omsiProcessHandle, address, ref MemoryMarshal.GetReference(bytes), bytes.Length, out _); - - // Safer method - //int tSize = Marshal.SizeOf(); - //byte[] buffer = new byte[tSize * values.Length]; - //Buffer.BlockCopy(values, 0, buffer, 0, buffer.Length); - //Imports.WriteProcessMemory((int)omsiProcessHandle, address, buffer, buffer.Length, out _); + WriteMemory(address, values.AsSpan()); } /// @@ -131,15 +177,49 @@ public void WriteMemory(uint address, T[] values) where T : unmanaged } /// - public void WriteMemory(int address, Memory values) where T : unmanaged + public void WriteMemory(int address, Span values) where T : unmanaged { - using var handle = values.Pin(); - var bytes = MemoryMarshal.AsBytes(values.Span); - Imports.WriteProcessMemory((int)omsiProcessHandle, address, ref MemoryMarshal.GetReference(bytes), bytes.Length, out _); + 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}!", address); +#endif + + fixed (T* valuesPtr = values) + { + int size = sizeof(T) * values.Length; + Buffer.MemoryCopy(valuesPtr, (void*)address, size, size); + } + return; + } + } + + var bytes = MemoryMarshal.AsBytes(values); + unsafe + { + fixed (byte* bytesPtr = bytes) + { + if (!Imports.WriteProcessMemory((int)omsiProcessHandle, address, ref Unsafe.AsRef(bytesPtr), bytes.Length, out _)) + throw new MemoryAccessException($"Couldn't write {bytes.Length} bytes of process memory @ 0x{address:X8}!", address); + } + } + //Imports.WriteProcessMemory((int)omsiProcessHandle, address, ref MemoryMarshal.GetReference(bytes), bytes.Length, out _); } /// - public void WriteMemory(uint address, Memory values) where T : unmanaged + public void WriteMemory(uint address, Span values) where T : unmanaged { WriteMemory(unchecked((int)address), values); } @@ -151,23 +231,48 @@ public void WriteMemory(uint address, Memory values) where T : unmanaged /// Address to copy from /// Address to copy to /// Number of bytes to copy + /// internal void CopyMemory(int src, int dst, int length) { + if (isLocalPlugin) + { + 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}!"); - if(!Imports.WriteProcessMemory((int)omsiProcessHandle, dst, buffer, length, out _)) - throw new MemoryAccessException($"Couldn't write {length} bytes to process memory @ {dst:X}!"); + if (!Imports.ReadProcessMemory((int)omsiProcessHandle, src, buffer, length, ref length)) + throw new MemoryAccessException($"Couldn't read {length} bytes of process memory @ 0x{src:X8}!", src); + if (!Imports.WriteProcessMemory((int)omsiProcessHandle, dst, buffer, length, out _)) + throw new MemoryAccessException($"Couldn't write {length} bytes to process memory @ 0x{dst:X8}!", dst); } /// internal void CopyMemory(uint src, uint dst, int length) { + if (isLocalPlugin) + { + 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}!", src); 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}!", dst); } /// @@ -179,6 +284,8 @@ internal void CopyMemory(uint src, uint dst, int length) /// The value of the new element /// The index of the element to write to /// Whether to write to the item pointed to by array (ie the array is an array of pointers) + /// + /// public void WriteMemoryArrayItemSafe(int address, T value, int index, bool itemPointer = false) where T : unmanaged { int arr = ReadMemory(address); @@ -192,7 +299,7 @@ public void WriteMemoryArrayItemSafe(int address, T value, int index, bool it } else { - WriteMemory(arr + index * (Marshal.SizeOf()), value); + WriteMemory(arr + index * (Unsafe.SizeOf()), value); } } @@ -205,6 +312,7 @@ public void WriteMemoryArrayItemSafe(int address, T value, int index, bool it /// The value of the new element /// The index of the element to write to /// Whether to write to the item pointed to by array (ie the array is an array of pointers) + /// public void WriteMemoryArrayItem(int address, T value, int index, bool itemPointer = false) where T : unmanaged { int arr = ReadMemory(address); @@ -215,7 +323,7 @@ public void WriteMemoryArrayItem(int address, T value, int index, bool itemPo } else { - WriteMemory(arr + index * (Marshal.SizeOf()), value); + WriteMemory(arr + index * (Unsafe.SizeOf()), value); } } @@ -234,6 +342,7 @@ public void WriteMemoryArrayItem(int address, T value, int index, bool itemPo /// If , treat the address as the pointer to the first element /// of the array instead of as a pointer to the array. /// The pointer to the first item of the newly allocated array + /// public async Task AllocateAndInitStructArray(T[] data, int references = 1, bool raw = false) where T : unmanaged { if (data == null) @@ -245,6 +354,12 @@ public async Task AllocateAndInitStructArray(T[] data, int references = return ptr; } + // Used via reflection by MarshalStruct in ReflectionCache.cs + internal int AllocateAndInitStructArray(object data, int references = 1, bool raw = false) where T : unmanaged + { + return AllocateAndInitStructArray(data as T[], references, raw).Result; + } + /// /// Allocates shared memory for an array of structs. /// Does not zero the allocated memory! @@ -260,6 +375,8 @@ public async Task AllocateAndInitStructArray(T[] data, int references = /// If , treat the address as the pointer to the first element /// of the array instead of as a pointer to the array. /// The pointer to the first item of the newly allocated array + /// + /// Writing to raw struct arrays is not yet supported. public async Task AllocateStructArray(int capacity, int references = 1, bool raw = false) where T : unmanaged { /* @@ -281,7 +398,7 @@ public async Task AllocateStructArray(int capacity, int references = 1, if (raw) throw new NotImplementedException("Writing to raw struct arrays is not yet supported."); - int itemSize = Marshal.SizeOf(); + int itemSize = Unsafe.SizeOf(); //var ptr = Marshal.AllocCoTaskMem(capacity * itemSize + 8).ToInt32(); int ptr = await AllocRemoteMemory(capacity * itemSize + 8); // Write the array metadata @@ -306,6 +423,7 @@ public async Task AllocateStructArray(int capacity, int references = 1, /// Used when overwriting existing arrays to prevent the GC from clearing a array referenced /// by multiple objects when one is destroyed. /// The pointer to the start of the newly allocated struct + /// public async Task AllocateStruct(T value, int references = 1) where T : unmanaged { /* @@ -318,7 +436,7 @@ public async Task AllocateStruct(T value, int references = 1) where T : * ... */ - int structSize = Marshal.SizeOf(); + int structSize = Unsafe.SizeOf(); int ptr = await AllocRemoteMemory(structSize + 4); // Write the array metadata WriteMemory(ptr, references); @@ -327,6 +445,12 @@ public async Task AllocateStruct(T value, int references = 1) where T : return ptr + 4; } + // Used via reflection by MarshalStruct in ReflectionCache.cs + internal int AllocateStruct(object value, int references = 1) where T : unmanaged + { + return AllocateStruct((T)value, references).Result; + } + /// /// Allocates shared memory for a string and copies it accross. /// @@ -341,6 +465,8 @@ public async Task AllocateStruct(T value, int references = 1) where T : /// Treat the address as a pointer to the first character /// (char *) rather than a pointer to a pointer. /// The pointer to the newly allocated string + /// + /// Writing to raw strings is not yet supported. public async Task AllocateString(string value, bool wide = false, int references = 1, bool raw = false) { /* @@ -373,12 +499,12 @@ public async Task AllocateString(string value, bool wide = false, int refer buffer = Encoding.ASCII.GetBytes(value); var ptr = await AllocRemoteMemory(buffer.Length + 13); - int strStart = ptr+12; + int strStart = ptr + 12; WriteMemory(strStart, buffer); //Marshal.Copy(buffer, 0, strStart, buffer.Length); // Write the string metadata WriteMemory(ptr, (short)1252); - WriteMemory(ptr + 0x2, (short)(wide?2:1)); + WriteMemory(ptr + 0x2, (short)(wide ? 2 : 1)); WriteMemory(ptr + 0x4, references); WriteMemory(ptr + 0x8, value.Length); // Write null terminator @@ -397,14 +523,15 @@ public async Task AllocateString(string value, bool wide = false, int refer /// Copy the number of references from the string /// at the address given. Used when overwriting existing strings to prevent the /// GC from clearing a string referenced by multiple objects when one is destroyed. + /// + /// Writing to raw strings is not yet supported. public void WriteMemory(int address, string value, bool wide = false, bool copyReferences = true) { int refs = 1; - if(copyReferences) + if (copyReferences) refs = ReadMemory(ReadMemory(address) - 0x8); WriteMemory(address, AllocateString(value, wide, refs).Result); - //Imports.WriteProcessMemory((int)omsiProcessHandle, address, buffer, buffer.Length, out _); } /// @@ -414,6 +541,8 @@ public void WriteMemory(int address, string value, bool wide = false, bool copyR /// The new value of the string to set; this must be of the /// correct encoding to avoid memory corruption /// The type of string to copy to + /// + /// Writing to raw strings is not yet supported. public void WriteMemory(int address, string value, StrPtrType strType) { WriteMemory(address, value, @@ -438,9 +567,28 @@ public async Task WriteMemoryAsync(int address, string value, bool wide = false, /// The type of the value/struct to read /// The address to read from /// The value of the struct/value at the given address. + /// public T ReadMemory(int address) where T : unmanaged { - int byteSize = Marshal.SizeOf(typeof(T)); + if (isLocalPlugin) + { + 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}!", address); +#endif + return Unsafe.ReadUnaligned((void*)address); + } + } + + int byteSize = Unsafe.SizeOf(); if (byteSize > readBuffer.Value.Length) throw new ArgumentException($"Couldn't read memory for object of type {typeof(T).Name} @ 0x{address:X8}; it wouldn't fit in the read buffer!"); int bytesRead = -1; @@ -452,7 +600,7 @@ public T ReadMemory(int address) where T : unmanaged return new T(); } #else - throw new MemoryAccessException($"Couldn't read {byteSize} bytes of process memory @ 0x{address:X8}!"); + throw new MemoryAccessException($"Couldn't read {byteSize} bytes of process memory @ 0x{address:X8}!", address); #endif return ByteArrayToStructure(readBuffer.Value); @@ -476,6 +624,7 @@ public T ReadMemory(uint address) where T : unmanaged /// The type of OmsiObject to construct /// The address of the pointer to the OmsiObject /// A new OmsiObject. + /// public T ReadMemoryObject(int address) where T : OmsiObject, new() { if (address == 0) @@ -500,7 +649,8 @@ public T ReadMemory(uint address) where T : unmanaged /// The offset from the base address of the object. /// When false, dereferences the object pointer again before construcing the new object. /// A new OmsiObject. - public T ReadMemoryObject(int address, int offset, bool raw=true) where T : OmsiObject, new() + /// + public T ReadMemoryObject(int address, int offset, bool raw = true) where T : OmsiObject, new() { if (address == 0) return null; @@ -524,9 +674,10 @@ public T ReadMemory(uint address) where T : unmanaged /// The address to read from /// Flags specifying how to decode the string. /// The value of the string at the given address. + /// public string ReadMemoryString(int address, StrPtrType strType) { - return ReadMemoryString(address, + return ReadMemoryString(address, (strType & StrPtrType.Wide) != 0, (strType & StrPtrType.Raw) != 0, (strType & StrPtrType.Pascal) != 0); @@ -541,48 +692,55 @@ public string ReadMemoryString(int address, StrPtrType strType) /// Whether the string can be treated as a length prefixed (pascal) /// string, which is much faster to read /// The value of the string at the given address. + /// Only in cases where an unusually long string is + /// attempted to be read. For null pointers, this method returns null public string ReadMemoryString(int address, bool wide = false, bool raw = false, bool pascalString = true) { - var sb = new StringBuilder(); + string ret; int i = address; if (!raw) i = ReadMemory(address); - if(i == 0) + if (i == 0) return null; if (pascalString) { uint strLen = ReadMemory(i - 4); if (strLen > 4096) - throw new MemoryAccessException($"Tried reading a very long string ({strLen} > 4096 characters long). This is probably not a valid string"); - if(wide) + throw new MemoryAccessException($"Tried reading a very long string ({strLen} > 4096 characters long). This is probably not a valid string", i); + if (wide) strLen *= 2; var bytes = ReadMemory(i, (int)strLen, readBuffer.Value); - sb.Append(wide ? Encoding.Unicode.GetString(bytes) : Encoding.ASCII.GetString(bytes)); + ret = wide ? new string(MemoryMarshal.Cast(bytes)) : Encoding.ASCII.GetString(bytes); } else { + var sb = new StringBuilder(); try { // Cache the read buffer to save a few checks (that the compiler would probably have hoisted out anyway) var readBuff = readBuffer.Value; + //int readSize = 16; + // TODO: Rewrite this to read chunks of memory all at once instead of one char at a time... while (true) { var bytes = ReadMemory(i, wide ? 2 : 1, readBuff); if (bytes.Count == 0 || (wide ? (bytes[0] | bytes[1]) : bytes[0]) == 0) break; - sb.Append(wide ? Encoding.Unicode.GetString(bytes) : Encoding.ASCII.GetString(bytes)); + //sb.Append(wide ? Encoding.Unicode.GetString(bytes) : Encoding.ASCII.GetString(bytes)); + sb.Append(wide ? MemoryMarshal.Cast(bytes)[0] : (char)bytes[0]); i++; if (wide) i++; } } catch (MemoryAccessException) { return null; } + ret = sb.ToString(); } - return sb.ToString(); + return ret; } /// @@ -591,6 +749,8 @@ public string ReadMemoryString(int address, bool wide = false, bool raw = false, /// The address to start reading from /// The number of bytes to read /// The buffer to read into + /// + /// public ArraySegment ReadMemory(int offset, int size, byte[] buffer) { int bytesRead = 0; @@ -598,12 +758,23 @@ public ArraySegment ReadMemory(int offset, int size, byte[] buffer) return new(buffer, 0, 0); if (size > buffer.Length) 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 (!Imports.ReadProcessMemory((int)omsiProcessHandle, offset, buffer, size, ref bytesRead)) - throw new MemoryAccessException($"Couldn't read {size} bytes of process memory @ {offset:X}!"); + if (isLocalPlugin) + { + if (offset == 0) + throw new MemoryAccessException($"Couldn't read {size} bytes of process memory @ 0x{offset:X8}!", offset); + + 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 @ 0x{offset:X8}!", offset); + } - return new (buffer, 0, bytesRead); + return new(buffer, 0, bytesRead); } -#endregion + #endregion #region Memory Array Item Reading Methods /// @@ -613,6 +784,7 @@ public ArraySegment ReadMemory(int offset, int size, byte[] buffer) /// The type of the to read /// The address of the array to read from /// The index of the element to read from + /// public T ReadMemoryArrayItemObjSafe(int address, int index) where T : OmsiObject, new() { int arr = ReadMemory(address); @@ -629,6 +801,7 @@ public ArraySegment ReadMemory(int offset, int size, byte[] buffer) /// The type of the to read /// The address of the array to read from /// The index of the element to read from + /// public T ReadMemoryArrayItemObj(int address, int index) where T : OmsiObject, new() { int arr = ReadMemory(address); @@ -644,7 +817,10 @@ public ArraySegment ReadMemory(int offset, int size, byte[] buffer) /// The type of the struct to read /// The address of the array to read from /// The index of the element to read from - /// Whether the item in the array is a pointer to the desired item (ie and array of pointers to ) + /// Whether the item in the array is a pointer to the desired item + /// (ie and array of pointers to ) + /// + /// public T ReadMemoryArrayItemSafe(int address, int index, bool itemPointer = false) where T : unmanaged { int arr = ReadMemory(address); @@ -661,14 +837,16 @@ public T ReadMemoryArrayItemSafe(int address, int index, bool itemPointer = f /// The type of the struct to read /// The address of the array to read from /// The index of the element to read from - /// Whether the item in the array is a pointer to the desired item (ie and array of pointers to ) + /// Whether the item in the array is a pointer to the desired item + /// (ie and array of pointers to ) + /// public T ReadMemoryArrayItem(int address, int index, bool itemPointer = false) where T : unmanaged { int arr = ReadMemory(address); - if(itemPointer) + if (itemPointer) return ReadMemory(ReadMemory(arr + index * 4)); else - return ReadMemory(arr + index * Marshal.SizeOf()); + return ReadMemory(arr + index * Unsafe.SizeOf()); } /// @@ -678,6 +856,8 @@ public T ReadMemoryArrayItem(int address, int index, bool itemPointer = false /// The type of the string to read /// The address of the array to read from /// The index of the element to read from + /// + /// public string ReadMemoryArrayItemStringSafe(int address, int index, bool wide = false) { int arr = ReadMemory(address); @@ -694,6 +874,7 @@ public string ReadMemoryArrayItemStringSafe(int address, int index, bool wide = /// The type of the string to read /// The address of the array to read from /// The index of the element to read from + /// public string ReadMemoryArrayItemString(int address, int index, bool wide = false) { int arr = ReadMemory(address); @@ -708,10 +889,11 @@ public string ReadMemoryArrayItemString(int address, int index, bool wide = fals /// The type of the OmsiObject to return /// The address of the array to read from /// The parsed array of OmsiObjects. + /// public T[] ReadMemoryObjArray(int address) where T : OmsiObject, new() { int arr = ReadMemory(address); - if(arr == 0) + if (arr == 0) return Array.Empty(); int len = ReadMemory(arr - 4); T[] ret = new T[len]; @@ -744,17 +926,18 @@ public string ReadMemoryArrayItemString(int address, int index, bool wide = fals /// If , treat the address as the pointer to the first element /// of the array instead of as a pointer to the array. /// The parsed array of structs. + /// public T[] ReadMemoryStructArray(int address, bool raw = false) where T : unmanaged { int arr = address; - if(!raw && address != 0) + if (!raw && address != 0) arr = ReadMemory(arr); - if(arr == 0) + if (arr == 0) return null; int len = ReadMemory(arr - 4); T[] ret = new T[len]; for (int i = 0; i < len; i++) - ret[i] = ReadMemory(arr + i * Marshal.SizeOf()); + ret[i] = ReadMemory(arr + i * Unsafe.SizeOf()); return ret; } @@ -765,6 +948,7 @@ public T[] ReadMemoryStructArray(int address, bool raw = false) where T : unm /// The type of the struct to return /// The address of the array to read from /// The parsed array of structs. + /// public T[] ReadMemoryStructPtrArray(int address) where T : unmanaged { int arr = ReadMemory(address); @@ -773,7 +957,7 @@ public T[] ReadMemoryStructPtrArray(int address) where T : unmanaged int len = ReadMemory(arr - 4); T[] ret = new T[len]; for (int i = 0; i < len; i++) - ret[i] = ReadMemory(ReadMemory(arr + i * Marshal.SizeOf())); + ret[i] = ReadMemory(ReadMemory(arr + i * Unsafe.SizeOf())); return ret; } @@ -787,6 +971,7 @@ public T[] ReadMemoryStructPtrArray(int address) where T : unmanaged /// Whether the string can be treated as a length prefixed (pascal) /// string, which is much faster to read /// The parsed array of strings. + /// public string[] ReadMemoryStringArray(int address, bool wide = false, bool raw = false, bool pascal = true) { int arr = address; @@ -797,7 +982,7 @@ public string[] ReadMemoryStringArray(int address, bool wide = false, bool raw = int len = ReadMemory(arr - 4); string[] ret = new string[len]; for (int i = 0; i < len; i++) - ret[i] = ReadMemoryString(arr + i * 4, wide, raw:false, pascal); + ret[i] = ReadMemoryString(arr + i * 4, wide, raw: false, pascal); return ret; } @@ -818,7 +1003,7 @@ public OutStruct[] MarshalStructs(InStruct[] obj) where OutStruct : struct where InStruct : unmanaged { - if(obj == null) return null; + if (obj == null) return null; OutStruct[] ret = new OutStruct[obj.Length]; for (int i = 0; i < obj.Length; i++) ret[i] = MarshalStruct(obj[i]); @@ -826,6 +1011,14 @@ public OutStruct[] MarshalStructs(InStruct[] obj) return ret; } + // Used via reflection by MarshalStruct in ReflectionCache.cs + internal object MarshalStructs(object obj) + where OutStruct : struct + where InStruct : unmanaged + { + return MarshalStructs(obj as InStruct[]); + } + /// /// Unmarshals any data in a struct which can't be automatically marshalled by Marshal.StructToPtr. /// This method uses reflection and as such isn't very fast. @@ -846,6 +1039,30 @@ public OutStruct[] UnMarshalStructs(InStruct[] obj) return ret; } + // Used via reflection by MarshalStruct in ReflectionCache.cs + internal object UnMarshalStructs(object obj) + where OutStruct : unmanaged + where InStruct : struct + { + return UnMarshalStructs(obj as InStruct[]); + } + + // Used via reflection by MarshalStruct in ReflectionCache.cs + internal object MarshalStruct(object obj) + where OutStruct : struct + where InStruct : unmanaged + { + return MarshalStruct((InStruct)obj); + } + + // Used via reflection by MarshalStruct in ReflectionCache.cs + internal object UnMarshalStruct(object obj) + where OutStruct : unmanaged + where InStruct : struct + { + return UnMarshalStruct((InStruct)obj); + } + /// /// Marshals any data in a struct which couldn't be automatically marshalled by Marshal.PtrToStruct. /// This method uses reflection and as such isn't very fast. @@ -859,105 +1076,46 @@ public OutStruct MarshalStruct(InStruct obj) where OutStruct : struct where InStruct : unmanaged { - object ret = new OutStruct(); - foreach (var field in obj.GetType().GetFields()) + if (typeof(InStruct) == typeof(OutStruct)) + return Unsafe.As(ref obj); + + if (ReflectionCacheExpression.Supported) { -#if DEBUG - try + if (!ReflectionCacheExpression.Compiled) + ReflectionCacheExpression.Compile(this); + + var ret1 = ReflectionCacheExpression.MarshalStructFunc(obj); + return ret1; + } + else + { + var refl = ReflectionCache.GetOrBuildReflectionCache(this, typeof(InStruct), typeof(OutStruct)); + var ret1 = new OutStruct(); + var objRef = __makeref(obj); + var retRef = __makeref(ret1); + foreach (var f in refl.fieldMapping) { +#if DEBUG + try + { #endif - object val = field.GetValue(obj); - foreach (var attr in field.GetCustomAttributes(false)) + var native = f.native.GetValueDirect(objRef); + if (f.toLocal != null) + f.local.SetValueDirect(retRef, f.toLocal(native)); + else + f.local.SetValueDirect(retRef, native); +#if DEBUG + } + catch (Exception ex) { - // Based on which kind of attribute the field has, perform special marshalling operations - switch (attr) - { - case OmsiStructAttribute a: - if (a.RequiresExtraMarshalling) - { - if (a.InternalType == typeof(InStruct)) - throw new ArgumentException($"Struct of type {typeof(InStruct).Name} tried to marshall one of it's fields as {typeof(InStruct).Name}, recursive data types are not allowed!"); - val = typeof(Memory).GetMethod(nameof(MarshalStruct)) - .MakeGenericMethod(a.ObjType, a.InternalType) - .Invoke(this, new object[] { val }); - } - break; - - case OmsiStrPtrAttribute a: - val = ReadMemoryString((int)val, a.Wide, a.Raw, a.Pascal); - break; - - case OmsiPtrAttribute: - val = new IntPtr((int)val); - break; - - case OmsiStructPtrAttribute a: - val = typeof(Memory).GetMethod(nameof(ReadMemory), new Type[] { typeof(int) }) - .MakeGenericMethod(a.InternalType) - .Invoke(this, new object[] { val }); - // Perform extra marshalling if needed - if (a.RequiresExtraMarshalling) - { - if (a.InternalType == typeof(InStruct)) - throw new ArgumentException($"Struct of type {typeof(InStruct).Name} tried to marshall one of it's fields as {typeof(InStruct).Name}, recursive data types are not allowed!"); - val = typeof(Memory).GetMethod(nameof(MarshalStruct)) - .MakeGenericMethod(a.ObjType, a.InternalType) - .Invoke(this, new object[] { val }); - } - break; - - case OmsiObjPtrAttribute a: - int addr = (int)val; - val = Activator.CreateInstance(a.ObjType, true); - ((OmsiObject)val).InitObject(this, addr); - break; - - case OmsiStructArrayPtrAttribute a: - val = typeof(Memory).GetMethod(nameof(ReadMemoryStructArray)) - .MakeGenericMethod(a.InternalType) - .Invoke(this, new object[] { val, a.Raw }); - // Perform extra marshalling if needed - if (a.RequiresExtraMarshalling) - { - if (a.InternalType == typeof(InStruct)) - throw new ArgumentException($"Struct of type {typeof(InStruct).Name} tried to marshall one of it's fields as {typeof(InStruct).Name}[], recursive data types are not allowed!"); - val = typeof(Memory).GetMethod(nameof(MarshalStructs)) - .MakeGenericMethod(a.ObjType, a.InternalType) - .Invoke(this, new object[] { val }); - } - break; - - case OmsiObjArrayPtrAttribute a: - val = typeof(Memory).GetMethod(nameof(ReadMemoryObjArray)) - .MakeGenericMethod(a.ObjType) - .Invoke(this, new object[] { val }); - break; - - case OmsiStrArrayPtrAttribute a: - val = ReadMemoryStringArray((int)val, a.Wide, a.Raw, a.Pascal); - break; - - case OmsiMarshallerAttribute a: - throw new NotImplementedException($"Attribute {attr.GetType().FullName} is not yet supported by the marshaller!"); - - default: - break; - } + throw new FieldAccessException($"Failed to marshal field '{f.native.Name}' in '{f.native.DeclaringType.Name}'. \n" + + $"Attributes:\n{GetCustomAttributeDebugString(f.native)}\n" + + $"Failed with internal exception:", ex); } - - // Match fields by name, setting the destination fields to the corresponding source fields - typeof(OutStruct).GetField(field.Name).SetValue(ret, val); -#if DEBUG - } catch(Exception ex) - { - throw new FieldAccessException($"Failed to marshal field '{field.Name}' in '{field.ReflectedType.FullName}'. \n" + - $"Attributes:\n{GetCustomAttributeDebugString(field)}\n" + - $"Failed with internal exception:", ex); - } #endif + } + return ret1; } - - return (OutStruct)ret; } private static string GetCustomAttributeDebugString(FieldInfo field) @@ -965,7 +1123,7 @@ private static string GetCustomAttributeDebugString(FieldInfo field) return string.Join("\n\t", field.GetCustomAttributes(false) .Select(x => string.Format(" {0}: {1}", x.GetType().Name, string.Join(", ", x.GetType() .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly) - .Select(attProp=>$"{attProp.Name}={attProp.GetValue(x)}"))))); + .Select(attProp => $"{attProp.Name}={attProp.GetValue(x)}"))))); } /// @@ -981,84 +1139,55 @@ public OutStruct UnMarshalStruct(InStruct obj) where OutStruct : unmanaged where InStruct : struct { - // TODO: This whole method could be made async to better take advantage of asynchrounous memory allocation - object ret = new OutStruct(); - foreach (var field in ret.GetType().GetFields()) + if (typeof(InStruct) == typeof(OutStruct)) + return Unsafe.As(ref obj); + + if (ReflectionCacheExpression.Supported) { - // Match fields by name, setting the destination fields to the corresponding source fields - object val = typeof(InStruct).GetField(field.Name).GetValue(obj); + if (!ReflectionCacheExpression.Compiled) + ReflectionCacheExpression.Compile(this); - // The OutStruct should be annotated with the conversion metadata - foreach (var attr in field.GetCustomAttributes(false)) + var ret1 = ReflectionCacheExpression.UnmarshalStructFunc(obj); + return ret1; + } + else + { + var refl = ReflectionCache.GetOrBuildReflectionCache(this, typeof(OutStruct), typeof(InStruct)); + var ret1 = new OutStruct(); + var objRef = __makeref(obj); + var retRef = __makeref(ret1); + foreach (var f in refl.fieldMapping) { - // Based on which kind of attribute the field has, perform special marshalling operations - switch (attr) +#if DEBUG + try { - case OmsiStructAttribute a: - if (a.RequiresExtraMarshalling) - val = typeof(Memory).GetMethod(nameof(UnMarshalStruct)) - .MakeGenericMethod(a.InternalType, a.ObjType) - .Invoke(this, new object[] { val }); - break; - - case OmsiStrPtrAttribute a: - val = AllocateString((string)val, a.Wide).Result; - break; - - case OmsiPtrAttribute: - val = ((IntPtr)val).ToInt32(); - break; - - case OmsiStructPtrAttribute a: - // Perform extra marshalling if needed - if (a.RequiresExtraMarshalling) - val = typeof(Memory).GetMethod(nameof(UnMarshalStruct)) - .MakeGenericMethod(a.ObjType, a.InternalType) - .Invoke(this, new object[] { val }); - - val = ((Task)typeof(Memory).GetMethod(nameof(AllocateStruct)) - .MakeGenericMethod(a.InternalType) - .Invoke(this, new object[] { val, 1 })).Result; - break; - - case OmsiObjPtrAttribute a: - val = ((OmsiObject)val).Address; - break; - - case OmsiStructArrayPtrAttribute a: - // Perform extra marshalling if needed - if (a.RequiresExtraMarshalling) - val = typeof(Memory).GetMethod(nameof(UnMarshalStructs)) - .MakeGenericMethod(a.ObjType, a.InternalType) - .Invoke(this, new object[] { val }); - - val = ((Task)typeof(Memory).GetMethod(nameof(AllocateAndInitStructArray)) - .MakeGenericMethod(a.InternalType) - .Invoke(this, new object[] { val, 1, a.Raw })).Result; - break; - - case OmsiObjArrayPtrAttribute a: - val = AllocateAndInitStructArray(((OmsiObject[])val).Select(x => x.Address).ToArray()).Result; - break; - - case OmsiStrArrayPtrAttribute a: - // TODO: I might add a dedicated method for allocating string arrays - var stringTasks = ((string[])val).Select(x => AllocateString(x, a.Wide, 1, a.Raw)); - var strings = Task.WhenAll(stringTasks); - val = AllocateAndInitStructArray(strings.Result).Result; - break; - - default: - throw new NotImplementedException($"Attribute {attr.GetType().FullName} is not yet supported by the marhsaller!"); +#endif + var local = f.local.GetValueDirect(objRef); + if (f.toLocal != null) + f.native.SetValueDirect(retRef, f.toNative(local)); + else + f.native.SetValueDirect(retRef, local); +#if DEBUG + } + catch (Exception ex) + { + throw new FieldAccessException($"Failed to unmarshal field '{f.native.Name}' in '{f.native.DeclaringType.Name}'. \n" + + $"Attributes:\n{GetCustomAttributeDebugString(f.native)}\n" + + $"Failed with internal exception:", ex); } +#endif } - - field.SetValue(ret, val); + return ret1; } - - return (OutStruct)ret; } + /// + /// Converts a byte array into an unmanaged structure. + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T ByteArrayToStructure(byte[] bytes) where T : unmanaged { return MemoryMarshal.Read(bytes); @@ -1071,6 +1200,7 @@ private static T ByteArrayToStructure(byte[] bytes) where T : unmanaged /// Byte array to copy to /// Index in the destination array to copy to /// The number of bytes written to the array + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int StructureToByteArray(T obj, byte[] dst, int startIndex) where T : unmanaged { MemoryMarshal.Write(dst.AsSpan()[startIndex..], ref obj); @@ -1099,18 +1229,19 @@ public async Task AllocRemoteMemory(int bytes, bool fastAlloc = false) int addr; if (fastAlloc) { - addr = Imports.VirtualAllocEx((int)omsiProcessHandle, 0, bytes, - Imports.AllocationType.MEM_COMMIT | Imports.AllocationType.MEM_RESERVE, - Imports.MemoryProtectionType.PAGE_READWRITE); + if (isLocalPlugin) + addr = (int)Marshal.AllocHGlobal(bytes); + else + addr = Imports.VirtualAllocEx((int)omsiProcessHandle, 0, bytes, + Imports.AllocationType.MEM_COMMIT | Imports.AllocationType.MEM_RESERVE, + Imports.MemoryProtectionType.PAGE_READWRITE); } else { -#if !OMSI_PLUGIN - if (!OmsiRemoteMethods.IsInitialised) + if (!remoteMethods.IsInitialised) throw new Exception("OmsiRemoteMethods are not initialised, remote memory allocator cannot be used!"); -#endif - addr = unchecked((int)await OmsiRemoteMethods.OmsiGetMem(bytes)); + addr = unchecked((int)await remoteMethods.OmsiGetMem(bytes)); } if (addr == 0) @@ -1138,16 +1269,17 @@ public void FreeRemoteMemory(int address, bool fastAlloc = false) if (fastAlloc) { - Imports.VirtualFreeEx((int)omsiProcessHandle, address, 0, Imports.FreeType.MEM_RELEASE); + if (isLocalPlugin) + Marshal.FreeHGlobal((nint)address); + else + Imports.VirtualFreeEx((int)omsiProcessHandle, address, 0, Imports.FreeType.MEM_RELEASE); } else { -#if !OMSI_PLUGIN - if (!OmsiRemoteMethods.IsInitialised) + if (!remoteMethods.IsInitialised) throw new Exception("OmsiRemoteMethods are not initialised, remote memory allocator cannot be used!"); -#endif - OmsiRemoteMethods.OmsiFreeMemAsync(address); + remoteMethods.OmsiFreeMemAsync(address); } } @@ -1157,6 +1289,14 @@ public void FreeRemoteMemory(uint address, bool fastAlloc = false) FreeRemoteMemory(unchecked((int)address), fastAlloc); } + public void Dispose() + { + omsiProcess.Dispose(); + remoteMethods.Dispose(); + if (omsiProcessHandle != IntPtr.Zero) + Imports.CloseHandle(omsiProcessHandle); + } + #endregion } @@ -1166,6 +1306,8 @@ internal static class Imports [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); + [DllImport("kernel32.dll")] + public static extern IntPtr CloseHandle(IntPtr handle); [DllImport("kernel32.dll")] public static extern bool ReadProcessMemory(int hProcess, int lpBaseAddress, byte[] buffer, int size, ref int lpNumberOfBytesRead); @@ -1246,7 +1388,18 @@ internal enum OpenProcessFlags /// public class MemoryAccessException : Exception { + public int Address { get; init; } + public MemoryAccessException() : base() { } public MemoryAccessException(string message) : base(message) { } + public MemoryAccessException(string message, int address) : base(message) + { + Address = address; + } + + public MemoryAccessException(string message, uint address) : this(message) + { + Address = (int)address; + } } } diff --git a/OmsiHook/OmsiHook.cs b/OmsiHook/OmsiHook.cs index 3e04aa7..3d4735a 100644 --- a/OmsiHook/OmsiHook.cs +++ b/OmsiHook/OmsiHook.cs @@ -11,13 +11,13 @@ namespace OmsiHook /// Base OmsiHook class - use this to hook into OMSI's memory and access its data, all the recognised OMSI globals are defined in the Globals property. /// For example usage see our docs. /// - public class OmsiHook + public class OmsiHook : IDisposable { private Memory omsiMemory; - private Process process; private OmsiGlobals globals; private Task stateMonitorTask; private bool isD3DReady; + private bool isLocalPlugin; /// /// Gets the object storing all of Omsi's global variables. @@ -27,7 +27,12 @@ public class OmsiHook /// /// Gets the currently hooked Omsi process. /// - public Process OmsiProcess => process; + public Process OmsiProcess => omsiMemory?.OmsiProcess; + + /// + /// Gets the instance of OmsiRemoteMethods. + /// + public OmsiRemoteMethods RemoteMethods => omsiMemory?.RemoteMethods; #if DEBUG /// @@ -109,13 +114,15 @@ public async Task AttachToOMSI(bool initialiseRemoteMethods = true) try { cursorLine = Console.CursorTop; - } catch { } + } + catch { } DateTime startTime = DateTime.Now; var found = false; while (!found) { - (found, process) = omsiMemory.Attach("omsi"); - if (!found) { + found = omsiMemory.Attach("omsi"); + if (!found) + { if (cursorLine != int.MinValue) { Console.WriteLine($"Waiting for OMSI.exe (waited for {(DateTime.Now - startTime).TotalSeconds:0} seconds)..."); @@ -125,9 +132,24 @@ public async Task AttachToOMSI(bool initialiseRemoteMethods = true) } } - if(initialiseRemoteMethods) + isLocalPlugin = Process.GetCurrentProcess().ProcessName == OmsiProcess.ProcessName; + + if (initialiseRemoteMethods) { - await OmsiRemoteMethods.InitRemoteMethods(omsiMemory, isLocalPlugin: Process.GetCurrentProcess().ProcessName == process.ProcessName); + var remoteMethods = new OmsiRemoteMethods(); + if (cursorLine != int.MinValue) + { + Console.WriteLine($"Initialising remote methods..."); + Console.SetCursorPosition(0, cursorLine); + } + await remoteMethods.InitRemoteMethods(omsiMemory, isLocalPlugin: isLocalPlugin); + omsiMemory.RemoteMethods = remoteMethods; + if (cursorLine != int.MinValue) + { + Console.WriteLine($"Hooking D3D methods..."); + Console.SetCursorPosition(0, cursorLine); + } + isD3DReady = remoteMethods.OmsiHookD3D(); } stateMonitorTask = new(MonitorStateTask); @@ -136,6 +158,12 @@ public async Task AttachToOMSI(bool initialiseRemoteMethods = true) OnOmsiGotD3DContext += OmsiHook_OnOmsiGotD3DContext; OnOmsiLostD3DContext += OmsiHook_OnOmsiLostD3DContext; OnMapChange += OmsiHook_OnMapChange; + + if (cursorLine != int.MinValue) + { + Console.WriteLine($"Connected to OMSI!"); + Console.SetCursorPosition(0, cursorLine); + } } /// @@ -149,11 +177,17 @@ public D3DTexture CreateTextureObject() return new D3DTexture(omsiMemory, 0); } + public void Dispose() + { + omsiMemory.Dispose(); + } + private void OmsiHook_OnMapChange(object sender, OmsiMap e) { - Task.Run(() => { - while(!isD3DReady) - isD3DReady = OmsiRemoteMethods.OmsiHookD3D(); + Task.Run(() => + { + while (RemoteMethods.IsInitialised && !isD3DReady) + isD3DReady = RemoteMethods.OmsiHookD3D(); }); } @@ -164,9 +198,10 @@ private void OmsiHook_OnOmsiLostD3DContext(object sender, EventArgs e) private void OmsiHook_OnOmsiGotD3DContext(object sender, EventArgs e) { - Task.Run(() => { - while (!isD3DReady) - isD3DReady = OmsiRemoteMethods.OmsiHookD3D(); + Task.Run(() => + { + while (RemoteMethods.IsInitialised && !isD3DReady) + isD3DReady = RemoteMethods.OmsiHookD3D(); }); } @@ -189,10 +224,23 @@ 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(omsiMemory.ReadMemory(omsiMemory.ReadMemory(omsiMemory.ReadMemory(addr) + 0x28) + 0x4) + index * 4); - } catch + int l = omsiMemory.ReadMemory(addr); + if (l == 0) + return 0; + int l1 = omsiMemory.ReadMemory(l + 0x28); + if (l1 == 0) + return 0; + int l2 = omsiMemory.ReadMemory(l1 + 0x4); + if (l2 == 0) + return 0; + int item = omsiMemory.ReadMemory(l2 + index * 4); + return item; + } + catch { return 0; } @@ -203,10 +251,10 @@ private int GetListItem(int addr, int index) /// private void MonitorStateTask() { - while(!process.HasExited) + while (isLocalPlugin || !OmsiProcess.HasExited) { int currentD3DState = omsiMemory.ReadMemory(0x008627d0); - if(currentD3DState != lastD3DState) + if (currentD3DState != lastD3DState) { if (currentD3DState != 0) OnOmsiGotD3DContext?.Invoke(this, new()); @@ -218,21 +266,26 @@ private void MonitorStateTask() int currentMapAddr = omsiMemory.ReadMemory(0x861588); if (currentMapAddr != 0) { + if (RemoteMethods.IsInitialised && !isD3DReady) + isD3DReady = RemoteMethods.OmsiHookD3D(); + int currentMapName = omsiMemory.ReadMemory(currentMapAddr + 0x154); bool currentMapLoaded = omsiMemory.ReadMemory(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(0x00861740)); +#pragma warning restore CS0618 // Type or member is obsolete if (vehPtr != lastVehiclePtr) { lastVehiclePtr = vehPtr; @@ -249,7 +302,7 @@ private void MonitorStateTask() /// /// Indicates that OmsiHook was not connected to the remote application when the method was called. /// - public class NotInitialisedException : Exception + public class NotInitialisedException : Exception { public NotInitialisedException() : base() { } public NotInitialisedException(string message) : base(message) { } diff --git a/OmsiHook/OmsiHook.csproj b/OmsiHook/OmsiHook.csproj index b97d414..0f0cbba 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.4.4.1 - 2.4.4.1 - 2.4.4 + 2.5.3.1 + 2.5.3.1 + 2.5.3 LGPL-3.0-only False README.md @@ -23,6 +23,7 @@ x86 False Debug;Release;ReleaseAndDocs + True @@ -61,25 +62,40 @@ True \ + PreserveNewest True - \lib\net6.0 + \lib\net6.0-windows7.0 PreserveNewest True - \lib\net6.0 + \lib\net6.0-windows7.0 + + + PreserveNewest + True + \lib\net6.0-windows7.0 + + + PreserveNewest + True + \lib\net6.0-windows7.0 PreserveNewest True - \lib\net6.0 + \lib\net6.0-windows7.0 PreserveNewest True - \lib\net6.0 + \lib\net6.0-windows7.0 @@ -95,8 +111,13 @@ false + True + True + + + True + True -