diff --git a/OmsiExtensions.sln b/OmsiExtensions.sln index 61137bc..e93e7ab 100644 --- a/OmsiExtensions.sln +++ b/OmsiExtensions.sln @@ -25,7 +25,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TriggersSample", "_OmsiHook EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VideoDemo", "_OmsiHookExamples\VideoDemo\VideoDemo.csproj", "{D94FF6D3-08AA-41CB-B9B9-F82E860E6E96}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClickablePlaneDemo", "_OmsiHookExamples\ClickablePlaneDemo\ClickablePlaneDemo.csproj", "{49428923-732C-4541-8C97-C6D9C004D726}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClickablePlaneDemo", "_OmsiHookExamples\ClickablePlaneDemo\ClickablePlaneDemo.csproj", "{49428923-732C-4541-8C97-C6D9C004D726}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -97,8 +97,8 @@ Global {CBCB99EF-DD1A-4D4D-A9A8-9BF251FDCD1B}.ReleaseAndDocs|Any CPU.Build.0 = Release|Win32 {CBCB99EF-DD1A-4D4D-A9A8-9BF251FDCD1B}.ReleaseAndDocs|x86.ActiveCfg = Release|Win32 {CBCB99EF-DD1A-4D4D-A9A8-9BF251FDCD1B}.ReleaseAndDocs|x86.Build.0 = Release|Win32 - {CDB17143-5653-48BE-AAC8-8419D5B4FD2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CDB17143-5653-48BE-AAC8-8419D5B4FD2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDB17143-5653-48BE-AAC8-8419D5B4FD2C}.Debug|Any CPU.ActiveCfg = Debug|x86 + {CDB17143-5653-48BE-AAC8-8419D5B4FD2C}.Debug|Any CPU.Build.0 = Debug|x86 {CDB17143-5653-48BE-AAC8-8419D5B4FD2C}.Debug|x86.ActiveCfg = Debug|x86 {CDB17143-5653-48BE-AAC8-8419D5B4FD2C}.Debug|x86.Build.0 = Debug|x86 {CDB17143-5653-48BE-AAC8-8419D5B4FD2C}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/OmsiHook/FastBinaryWriter.cs b/OmsiHook/FastBinaryWriter.cs index 489f7b7..cf8c233 100644 --- a/OmsiHook/FastBinaryWriter.cs +++ b/OmsiHook/FastBinaryWriter.cs @@ -7,6 +7,8 @@ namespace OmsiHook; +#pragma warning disable CS9191 + internal static class FastBinaryWriter { public static void Write(Span buffer, ref int pos, int data) diff --git a/OmsiHook/MemArray/MemArrayString.cs b/OmsiHook/MemArray/MemArrayString.cs index 7ed5ae4..212a4ff 100644 --- a/OmsiHook/MemArray/MemArrayString.cs +++ b/OmsiHook/MemArray/MemArrayString.cs @@ -24,7 +24,7 @@ public override string this[int index] { if (cached) arrayCache[index] = value; - Memory.WriteMemoryArrayItemSafe(Address, Memory.AllocateString(value).Result, index); + Memory.WriteMemoryArrayItemSafe(Address, Memory.AllocateString(value, wide).Result, index); } } diff --git a/OmsiHook/MemArray/MemArrayStringDict.cs b/OmsiHook/MemArray/MemArrayStringDict.cs index 93de102..c3e0624 100644 --- a/OmsiHook/MemArray/MemArrayStringDict.cs +++ b/OmsiHook/MemArray/MemArrayStringDict.cs @@ -14,7 +14,7 @@ namespace OmsiHook /// public class MemArrayStringDict : MemArrayString { - private Dictionary indexDictionary = new(); + private readonly Dictionary indexDictionary = []; public Dictionary IndexDictionary => indexDictionary; @@ -58,8 +58,8 @@ public override void UpdateFromHook(int index = -1) public override int IndexOf(string item) => indexDictionary[item]; public override bool Remove(string item) { - if (indexDictionary.ContainsKey(item)) - base.RemoveAt(indexDictionary[item]); + if (indexDictionary.TryGetValue(item, out var ind)) + base.RemoveAt(ind); else return false; return true; diff --git a/OmsiHook/Memory.cs b/OmsiHook/Memory.cs index 0e2137b..e5c0c8e 100644 --- a/OmsiHook/Memory.cs +++ b/OmsiHook/Memory.cs @@ -10,6 +10,8 @@ using System.Threading; using System.Threading.Tasks; +#pragma warning disable CS9191 + namespace OmsiHook { /// @@ -118,7 +120,7 @@ public void WriteMemory(int address, T value) where T : unmanaged var size = StructureToByteArray(value, writeBuffer.Value, 0); - if (!Imports.WriteProcessMemory((int)omsiProcessHandle, address, writeBuffer.Value, size, out _)) + if (!Imports.WriteProcessMemory(unchecked((int)omsiProcessHandle), address, writeBuffer.Value, size, out _)) throw new MemoryAccessException($"Couldn't write {Unsafe.SizeOf()} bytes of process memory @ 0x{address:X8}!", address); } @@ -469,6 +471,10 @@ internal int AllocateStruct(object value, int references = 1) where T : unman /// Writing to raw strings is not yet supported. public async Task AllocateString(string value, bool wide = false, int references = 1, bool raw = false) { + // TODO: What does "raw" actually mean in this context, should it return a pointer to (string + 0xc)? + // Previously the raw paramter was completely ignored and nothing needed it so we need to do some + // research to find out if it's actually needed. + // Once raw string allocation is implemented, remember to update callers of this method to use it. /* * AnsiString/UnicodeString struct layout: * 0 - / Code page (short) @@ -712,18 +718,55 @@ public string ReadMemoryString(int address, bool wide = false, bool raw = false, if (wide) strLen *= 2; var bytes = ReadMemory(i, (int)strLen, readBuffer.Value); - ret = wide ? new string(MemoryMarshal.Cast(bytes)) : Encoding.ASCII.GetString(bytes); + ret = wide ? new(MemoryMarshal.Cast(bytes)) : Encoding.ASCII.GetString(bytes.Array, bytes.Offset, bytes.Count); } else { - var sb = new StringBuilder(); + using var sb = new PooledStringBuilder(); 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... + int readSize = 16; + int endPos = i | readSize; while (true) + { + // Read aligned chunks of bytes, we're unlikely to cause an illegal access if we align our reads + int len = Math.Max(i - endPos + 1, wide ? 2 : 1); + var bytes = ReadMemory(i, len, readBuff).AsSpan(); + if (bytes.IsEmpty) + break; + int nullPos = len; + if (wide) + { + // Wide strings are blittable to c# strings + var chars = MemoryMarshal.Cast(bytes); + // Check if we've reached the end of the string + int p = chars.IndexOf((char)0); + if (p > 0) + nullPos = p; + + sb.Append(chars[0..p]); + } + else + { + int p = bytes.IndexOf((byte)0); + if (p > 0) + nullPos = p; + + // Expand each ascii char into a utf16 char (this is not necessarily a correct conversion) + for (int j = 0; j < nullPos; j++) + sb.Append((char)bytes[j]); + } + if (nullPos != len) + break; + + i += len; + endPos = i | readSize; + } + + // 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) @@ -734,7 +777,7 @@ public string ReadMemoryString(int address, bool wide = false, bool raw = false, i++; if (wide) i++; - } + }*/ } catch (MemoryAccessException) { return null; } ret = sb.ToString(); @@ -1099,11 +1142,11 @@ public OutStruct MarshalStruct(InStruct obj) try { #endif - var native = f.native.GetValueDirect(objRef); - if (f.toLocal != null) - f.local.SetValueDirect(retRef, f.toLocal(native)); - else - f.local.SetValueDirect(retRef, native); + 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) @@ -1162,11 +1205,11 @@ public OutStruct UnMarshalStruct(InStruct obj) try { #endif - var local = f.local.GetValueDirect(objRef); - if (f.toLocal != null) - f.native.SetValueDirect(retRef, f.toNative(local)); - else - f.native.SetValueDirect(retRef, local); + 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) @@ -1300,10 +1343,38 @@ public void Dispose() #endregion } - internal static class Imports + internal static partial class Imports { #region DllImports +#if NET7_0_OR_GREATER + [LibraryImport("kernel32.dll")] + public static partial IntPtr OpenProcess(int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId); + [LibraryImport("kernel32.dll")] + public static partial IntPtr CloseHandle(IntPtr handle); + + [LibraryImport("kernel32.dll")] + [return:MarshalAs(UnmanagedType.Bool)] public static partial bool ReadProcessMemory(int hProcess, int lpBaseAddress, byte[] buffer, int size, ref int lpNumberOfBytesRead); + + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] public static partial bool WriteProcessMemory(int hProcess, int lpBaseAddress, byte[] buffer, int size, out int lpNumberOfBytesWritten); + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] public static partial bool WriteProcessMemory(int hProcess, int lpBaseAddress, ref byte buffer, int size, out int lpNumberOfBytesWritten); + + /// + /// Allocates memory in a remote process's memory space. + /// + /// The pointer to the process to allocate memory in + /// The desired starting address to allocate memory at (leave at 0 for default) + /// How many bytes of memory to allocate + /// The type of allocation + /// The type of memory protection for the regions to be allocated + /// The address of the allocated memory. Returns 0 if the allocation failed. + [LibraryImport("kernel32.dll")] + public static partial int VirtualAllocEx(int hProcess, int lpAddress, int dwSize, AllocationType flAllocationType, MemoryProtectionType flProtect); + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] public static partial bool VirtualFreeEx(int hProcess, int lpAddress, int dwSize, FreeType dwFreeType); +#else [DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll")] @@ -1330,7 +1401,8 @@ internal static class Imports public static extern int VirtualAllocEx(int hProcess, int lpAddress, int dwSize, AllocationType flAllocationType, MemoryProtectionType flProtect); [DllImport("kernel32.dll")] public static extern bool VirtualFreeEx(int hProcess, int lpAddress, int dwSize, FreeType dwFreeType); - #endregion +#endif +#endregion [Flags] internal enum AllocationType : int diff --git a/OmsiHook/OmsiHook.csproj b/OmsiHook/OmsiHook.csproj index 0f0cbba..f845c58 100644 --- a/OmsiHook/OmsiHook.csproj +++ b/OmsiHook/OmsiHook.csproj @@ -1,7 +1,9 @@  - net6.0-windows + + net6.0-windows;net7.0-windows;net8.0-windows + win-x86 Thomas Mathieson et al Copyright Thomas Mathieson 2022-2024 all rights reserved https://github.com/space928/Omsi-Extensions @@ -10,15 +12,16 @@ OmsiHook is a simple library for hooking into Omsi's memory for modding. true true - 2.5.3.1 - 2.5.3.1 - 2.5.3 + 2.5.4.1 + 2.5.4.1 + 2.5.4 LGPL-3.0-only False README.md Logo.png true snupkg + 12 disable x86 False @@ -70,32 +73,32 @@ PreserveNewest True - \lib\net6.0-windows7.0 + \lib\$(TargetFramework) - + PreserveNewest True - \lib\net6.0-windows7.0 + \lib\$(TargetFramework) - + PreserveNewest True - \lib\net6.0-windows7.0 + \lib\$(TargetFramework) - + + PreserveNewest True - \lib\net6.0-windows7.0 + \lib\$(TargetFramework) - + PreserveNewest True - \lib\net6.0-windows7.0 + \lib\$(TargetFramework) @@ -106,6 +109,8 @@ True + + diff --git a/OmsiHook/OmsiRemoteMethods.cs b/OmsiHook/OmsiRemoteMethods.cs index 19a2a7a..aa59607 100644 --- a/OmsiHook/OmsiRemoteMethods.cs +++ b/OmsiHook/OmsiRemoteMethods.cs @@ -19,15 +19,22 @@ public class OmsiRemoteMethods : IDisposable { private NamedPipeClientStream pipeRX; private NamedPipeClientStream pipeTX; - private ConcurrentDictionary> resultPromises; + private readonly ConcurrentDictionary> resultPromises; private Task resultReaderThread; private Memory memory; private bool localPlugin; + private readonly Random random; private readonly ThreadLocal asyncWriteBuff = new(() => new byte[256]); public bool IsInitialised => localPlugin || (pipeRX?.IsConnected ?? false) && (pipeTX?.IsConnected ?? false); + public OmsiRemoteMethods() + { + random = new(); + resultPromises = []; + } + internal async Task InitRemoteMethods(Memory omsiMemory, bool inifiniteTimeout = false, bool isLocalPlugin = false) { memory = omsiMemory; @@ -36,7 +43,7 @@ internal async Task InitRemoteMethods(Memory omsiMemory, bool inifiniteTimeout = if (localPlugin) return; - resultPromises = new(); + resultPromises.Clear(); // We swap rx and tx here so that it makes semantic sense (since the tx of the client goes to the rx of the server) pipeTX = new(".", OmsiHookRPCMethods.PIPE_NAME_RX, PipeDirection.Out); @@ -84,7 +91,7 @@ public void Dispose() int resultHash; do { - resultHash = Random.Shared.Next(); + resultHash = random.Next(); } while (!resultPromises.TryAdd(resultHash, resultPromise)); return (resultHash, resultPromise); } @@ -132,7 +139,7 @@ private async Task TempRVListCreate(uint classAddr, int capacity) FastBinaryWriter.Write(writeBuffer, ref argPos, capacity); int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer); return await promise.Task; } } @@ -159,7 +166,7 @@ private async Task CopyTempListIntoMainList_(uint rvList, uint tmpList) FastBinaryWriter.Write(writeBuffer, ref argPos, tmpList); int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); return await promise.Task; } } @@ -232,7 +239,7 @@ public async Task MakeVehicle(string path, int licensePlateIndex = -1, bool FastBinaryWriter.Write(writeBuffer, ref argPos, pathAddr); int writeBufferSize = OmsiHookRPCMethods.RemoteMethodsArgsSizes[method] + 8; lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); var ret = await promise.Task; if (__copyToMainList) @@ -258,7 +265,7 @@ public async Task CloseRPCSession(bool killAllConnections) FastBinaryWriter.Write(writeBuffer, ref argPos, 0); FastBinaryWriter.Write(writeBuffer, ref argPos, killAllConnections ? 1 : 0); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); await promise.Task; // promise.Task.Wait(); } @@ -338,7 +345,7 @@ public async Task OmsiGetMem(int length) FastBinaryWriter.Write(writeBuffer, ref argPos, resultPromise); FastBinaryWriter.Write(writeBuffer, ref argPos, length); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); return (uint)await promise.Task; /*lock (pipeRX) pipeRX.Read(readBuffer); @@ -431,7 +438,7 @@ public bool OmsiHookD3D() FastBinaryWriter.Write(writeBuffer, ref argPos, levels); FastBinaryWriter.Write(writeBuffer, ref argPos, ppTexture); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); HRESULT result = (HRESULT)await promise.Task; uint pTexture = memory.ReadMemory(ppTexture); return (result, pTexture); @@ -481,7 +488,7 @@ public async Task OmsiUpdateTextureAsync(uint texturePtr, uint textureD FastBinaryWriter.Write(writeBuffer, ref argPos, updateRect?.bottom ?? 0); FastBinaryWriter.Write(writeBuffer, ref argPos, level); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); return (HRESULT)await promise.Task; } } @@ -511,7 +518,7 @@ public async Task OmsiReleaseTextureAsync(uint texturePtr) FastBinaryWriter.Write(writeBuffer, ref argPos, resultPromise); FastBinaryWriter.Write(writeBuffer, ref argPos, texturePtr); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); return (HRESULT)await promise.Task; } } @@ -556,7 +563,7 @@ public async Task OmsiReleaseTextureAsync(uint texturePtr) FastBinaryWriter.Write(writeBuffer, ref argPos, descPtr + 4); FastBinaryWriter.Write(writeBuffer, ref argPos, descPtr + 8); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); HRESULT res = (HRESULT)await promise.Task; uint width = memory.ReadMemory(descPtr); @@ -593,7 +600,7 @@ public async Task OmsiGetTextureLevelCountAsync(uint texturePtr) FastBinaryWriter.Write(writeBuffer, ref argPos, resultPromise); FastBinaryWriter.Write(writeBuffer, ref argPos, texturePtr); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); return unchecked((uint)await promise.Task); } } @@ -625,7 +632,7 @@ public async Task OmsiIsTextureAsync(uint texturePtr) FastBinaryWriter.Write(writeBuffer, ref argPos, resultPromise); FastBinaryWriter.Write(writeBuffer, ref argPos, texturePtr); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); return !HRESULTFailed((HRESULT)await promise.Task); } } @@ -672,7 +679,7 @@ public async Task OmsiSetTrigger(OmsiRoadVehicleInst roadVehicle, int triggerPtr FastBinaryWriter.Write(writeBuffer, ref argPos, triggerPtr); FastBinaryWriter.Write(writeBuffer, ref argPos, enabled ? 1 : 0); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); await promise.Task; } } @@ -721,7 +728,7 @@ public async Task OmsiSoundTrigger(OmsiComplMapObjInst mapObj, int triggerPtr, i FastBinaryWriter.Write(writeBuffer, ref argPos, triggerPtr); FastBinaryWriter.Write(writeBuffer, ref argPos, filenamePtr); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); await promise.Task; } } @@ -749,7 +756,7 @@ public async Task OmsiSetCriticalSectionLock(IntPtr CS) FastBinaryWriter.Write(writeBuffer, ref argPos, resultPromise); FastBinaryWriter.Write(writeBuffer, ref argPos, (int)CS); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); await promise.Task; } } @@ -777,7 +784,7 @@ public async Task OmsiReleaseCriticalSectionLock(IntPtr CS) FastBinaryWriter.Write(writeBuffer, ref argPos, resultPromise); FastBinaryWriter.Write(writeBuffer, ref argPos, (int)CS); lock (pipeTX) - pipeTX.Write(writeBuffer.AsSpan()[..writeBufferSize]); + pipeTX.Write(writeBuffer, 0, writeBufferSize); await promise.Task; } } diff --git a/OmsiHook/PooledStringBuilder.cs b/OmsiHook/PooledStringBuilder.cs new file mode 100644 index 0000000..4910cc5 --- /dev/null +++ b/OmsiHook/PooledStringBuilder.cs @@ -0,0 +1,156 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace OmsiHook; + +#nullable enable +#pragma warning disable CS9191 + +/// +/// A simple and fast string builder, which avoids memory allocations by using pooled arrays. +/// +public struct PooledStringBuilder : IDisposable +{ + private readonly ArrayPool arrayPool; + private char[] buff; + private int pos; + + public PooledStringBuilder(int capacity = 16, ArrayPool? arrayPool = null) + { + this.arrayPool = arrayPool ?? ArrayPool.Shared; + buff = this.arrayPool.Rent(capacity); + pos = 0; + } + + private void GrowIfNeeded(int charsToAdd) + { + if (charsToAdd + pos <= buff.Length) + return; + + uint nsize = BitOperations.RoundUpToPowerOf2((uint)(charsToAdd + pos)); + var nbuff = arrayPool.Rent((int)nsize); + Array.Copy(buff, nbuff, buff.Length); + arrayPool.Return(buff); + buff = nbuff; + } + + /// + /// Creates and returns a string containing the contents of this string builder. + /// + /// + public override readonly string ToString() + { + return new string(buff, 0, pos); + } + + public void Append(char c) + { + GrowIfNeeded(1); + buff[pos++] = c; + } + + public void Append(ReadOnlySpan c) + { + GrowIfNeeded(c.Length); + c.CopyTo(buff.AsSpan(pos)); + pos += c.Length; + } + + public void Append(string c) + { + GrowIfNeeded(c.Length); + c.CopyTo(0, buff, pos, c.Length); + pos += c.Length; + } + + public void AppendLine(ReadOnlySpan c) + { + Append(c); + Append(Environment.NewLine); + } + + public void AppendLine(string c) + { + Append(c); + Append(Environment.NewLine); + } + + public void Append(float c) + { + Append(c, default); + } + + public void Append(double c) + { + Append(c, default); + } + + public void Append(nint c) + { + Append(c, default); + } + + public void Append(long c) + { + Append(c, default); + } + + public void Append(int c) + { + Append(c, default); + } + + public void Append(short c) + { + Append(c, default); + } + + public void Append(sbyte c) + { + Append(c, default); + } + + public void Append(ulong c) + { + Append(c, default); + } + + public void Append(uint c) + { + Append(c, default); + } + + public void Append(ushort c) + { + Append(c, default); + } + + public void Append(byte c) + { + Append(c, default); + } + + public void Append(ISpanFormattable c, ReadOnlySpan format, IFormatProvider? formatProvider = null) + { + for (int i = 0; i < 16; i++) + { + bool res = c.TryFormat(buff.AsSpan(pos), out int written, format, formatProvider); + if (res) + { + pos += written; + break; + } + GrowIfNeeded(buff.Length - pos + 1); + } + } + + public readonly void Dispose() + { + arrayPool.Return(buff); + } +} diff --git a/OmsiHook/ReflectionCache.cs b/OmsiHook/ReflectionCache.cs index 6daa4ec..b9a10d0 100644 --- a/OmsiHook/ReflectionCache.cs +++ b/OmsiHook/ReflectionCache.cs @@ -98,11 +98,11 @@ public static ReflectionCache BuildReflectionCache(Memory mem, Type nativeType, if (a.InternalType == nativeType) throw new ArgumentException($"Struct of type {nativeType.Name} tried to marshal one of it's fields as {nativeType.Name}, recursive data types are not allowed!"); - map.toLocal = typeof(Memory).GetMethod(nameof(Memory.MarshalStruct), BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(object) }) + map.toLocal = typeof(Memory).GetMethod(nameof(Memory.MarshalStruct), BindingFlags.NonPublic | BindingFlags.Instance, [typeof(object)]) .MakeGenericMethod(a.ObjType, a.InternalType) .CreateDelegate>(mem); - map.toNative = typeof(Memory).GetMethod(nameof(Memory.UnMarshalStruct), BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(object) }) + map.toNative = typeof(Memory).GetMethod(nameof(Memory.UnMarshalStruct), BindingFlags.NonPublic | BindingFlags.Instance, [typeof(object)]) .MakeGenericMethod(a.ObjType, a.InternalType) .CreateDelegate>(mem); } @@ -119,10 +119,10 @@ public static ReflectionCache BuildReflectionCache(Memory mem, Type nativeType, break; case OmsiStructPtrAttribute a: - var readStructFunc = typeof(Memory).GetMethod(nameof(Memory.ReadMemory), new Type[] { typeof(int) }) + var readStructFunc = typeof(Memory).GetMethod(nameof(Memory.ReadMemory), [typeof(int)]) .MakeGenericMethod(a.InternalType) .CreateDelegate>(mem); - var writeStructFunc = typeof(Memory).GetMethod(nameof(Memory.AllocateStruct), BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(object) }) + var writeStructFunc = typeof(Memory).GetMethod(nameof(Memory.AllocateStruct), BindingFlags.NonPublic | BindingFlags.Instance, [typeof(object)]) .MakeGenericMethod(a.InternalType) .CreateDelegate>(mem); @@ -132,11 +132,11 @@ public static ReflectionCache BuildReflectionCache(Memory mem, Type nativeType, if (a.InternalType == nativeType) throw new ArgumentException($"Struct of type {nativeType.Name} tried to marshal one of it's fields as {nativeType.Name}, recursive data types are not allowed!"); - var marshalFunc = typeof(Memory).GetMethod(nameof(Memory.MarshalStruct), BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(object) }) + var marshalFunc = typeof(Memory).GetMethod(nameof(Memory.MarshalStruct), BindingFlags.NonPublic | BindingFlags.Instance, [typeof(object)]) .MakeGenericMethod(a.ObjType, a.InternalType) .CreateDelegate>(mem); - var unMarshalFunc = typeof(Memory).GetMethod(nameof(Memory.UnMarshalStruct), BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(object) }) + var unMarshalFunc = typeof(Memory).GetMethod(nameof(Memory.UnMarshalStruct), BindingFlags.NonPublic | BindingFlags.Instance, [typeof(object)]) .MakeGenericMethod(a.ObjType, a.InternalType) .CreateDelegate>(mem); @@ -165,7 +165,7 @@ public static ReflectionCache BuildReflectionCache(Memory mem, Type nativeType, var readStructsFunc = typeof(Memory).GetMethod(nameof(Memory.ReadMemoryStructArray)) .MakeGenericMethod(a.InternalType) .CreateDelegate>(mem); - var writeStructsFunc = typeof(Memory).GetMethod(nameof(Memory.AllocateAndInitStructArray), BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(object), typeof(int), typeof(bool) }) + var writeStructsFunc = typeof(Memory).GetMethod(nameof(Memory.AllocateAndInitStructArray), BindingFlags.NonPublic | BindingFlags.Instance, [typeof(object), typeof(int), typeof(bool)]) .MakeGenericMethod(a.InternalType) .CreateDelegate>(mem); @@ -175,11 +175,11 @@ public static ReflectionCache BuildReflectionCache(Memory mem, Type nativeType, if (a.InternalType == nativeType) throw new ArgumentException($"Struct of type {nativeType.Name} tried to marshal one of it's fields as {nativeType.Name}[], recursive data types are not allowed!"); - var marshalFunc = typeof(Memory).GetMethod(nameof(Memory.MarshalStructs), BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(object) }) + var marshalFunc = typeof(Memory).GetMethod(nameof(Memory.MarshalStructs), BindingFlags.NonPublic | BindingFlags.Instance, [typeof(object)]) .MakeGenericMethod(a.ObjType, a.InternalType) .CreateDelegate>(mem); - var unMarshalFunc = typeof(Memory).GetMethod(nameof(Memory.UnMarshalStructs), BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(object) }) + var unMarshalFunc = typeof(Memory).GetMethod(nameof(Memory.UnMarshalStructs), BindingFlags.NonPublic | BindingFlags.Instance, [typeof(object)]) .MakeGenericMethod(a.InternalType, a.ObjType) .CreateDelegate>(mem); @@ -207,7 +207,7 @@ public static ReflectionCache BuildReflectionCache(Memory mem, Type nativeType, map.toNative = val => { // TODO: I might add a dedicated method for allocating string arrays - var stringTasks = ((string[])val).Select(x => mem.AllocateString(x, a.Wide, 1, a.Raw)); + var stringTasks = ((string[])val).Select(x => mem.AllocateString(x, a.Wide, 1, false/*a.Raw*/)); var strings = Task.WhenAll(stringTasks); return mem.AllocateAndInitStructArray(strings.Result).Result; }; @@ -266,8 +266,8 @@ public static void Compile(Memory mem) var localType = typeof(LocalStruct); var nativeType = typeof(NativeStruct); - DynamicMethod marshalFunc = new($"Marshal{localType.Name}", localType, new Type[] { nativeType.MakeByRefType() }, localType, true); - DynamicMethod unmarshalFunc = new($"UnMarshal{localType.Name}", nativeType, new Type[] { localType.MakeByRefType() }, localType, true); + DynamicMethod marshalFunc = new($"Marshal{localType.Name}", localType, [nativeType.MakeByRefType()], localType, true); + DynamicMethod unmarshalFunc = new($"UnMarshal{localType.Name}", nativeType, [localType.MakeByRefType()], localType, true); var mil = marshalFunc.GetILGenerator(); var uil = unmarshalFunc.GetILGenerator(); mil.DeclareLocal(localType); @@ -556,24 +556,24 @@ public static void Compile(Memory mem) { //Expression> toLocal = val => mem.ReadMemoryString(val, a.Wide, a.Raw, a.Pascal); //Expression> toNative = val => mem.AllocateString(val, a.Wide, 1, a.Raw).Result; - var toLocal = typeof(Memory).GetMethod(nameof(Memory.ReadMemoryString), new[] { typeof(int), typeof(bool), typeof(bool), typeof(bool) }); - var toNative = typeof(Memory).GetMethod(nameof(Memory.AllocateString), new[] { typeof(string), typeof(bool), typeof(int), typeof(bool) }); + var toLocal = typeof(Memory).GetMethod(nameof(Memory.ReadMemoryString), [typeof(int), typeof(bool), typeof(bool), typeof(bool)]); + var toNative = typeof(Memory).GetMethod(nameof(Memory.AllocateString), [typeof(string), typeof(bool), typeof(int), typeof(bool)]); mvalSrc = Expression.Call(memInst, toLocal, mvalSrc, Expression.Constant(a.Wide), Expression.Constant(a.Raw), Expression.Constant(a.Pascal)); - uvalSrc = Expression.Call(memInst, toNative, uvalSrc, Expression.Constant(a.Wide), Expression.Constant(1), Expression.Constant(a.Raw)); + uvalSrc = Expression.Call(memInst, toNative, uvalSrc, Expression.Constant(a.Wide), Expression.Constant(1), Expression.Constant(false/*a.Raw*/)); uvalSrc = Expression.Property(uvalSrc, nameof(Task.Result)); break; } case OmsiPtrAttribute: { - mvalSrc = Expression.New(typeof(IntPtr).GetConstructor(new[] { typeof(int) }), mvalSrc); + mvalSrc = Expression.New(typeof(IntPtr).GetConstructor([typeof(int)]), mvalSrc); uvalSrc = Expression.Call(uvalSrc, typeof(IntPtr).GetMethod(nameof(IntPtr.ToInt32))); break; } case OmsiStructPtrAttribute a: { - var readStructFunc = typeof(Memory).GetMethod(nameof(Memory.ReadMemory), new Type[] { typeof(int) }) + var readStructFunc = typeof(Memory).GetMethod(nameof(Memory.ReadMemory), [typeof(int)]) .MakeGenericMethod(a.InternalType); var writeStructFunc = typeof(Memory).GetMethod(nameof(Memory.AllocateStruct)) .MakeGenericMethod(a.InternalType); @@ -607,7 +607,7 @@ public static void Compile(Memory mem) } case OmsiObjPtrAttribute a: { - var ctor = a.ObjType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, new[] { typeof(Memory), typeof(int) }); + var ctor = a.ObjType.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, [typeof(Memory), typeof(int)]); mvalSrc = Expression.New(ctor, memInst, mvalSrc); uvalSrc = Expression.Property(uvalSrc, nameof(OmsiObject.Address)); break; @@ -705,8 +705,8 @@ public static void Compile(Memory mem) //mblocks.Add(Expression.Call(null, typeof(Debugger).GetMethod(nameof(Debugger.Break)))); mblocks.Add(mret); ublocks.Add(uret); - var mblock = Expression.Block(localType, new[] { mret }, mblocks); - var ublock = Expression.Block(nativeType, new[] { uret }, ublocks); + var mblock = Expression.Block(localType, [mret], mblocks); + var ublock = Expression.Block(nativeType, [uret], ublocks); var mfunc = Expression.Lambda(mblock, mobj); var ufunc = Expression.Lambda(ublock, uobj); @@ -730,7 +730,7 @@ private static string GetCustomAttributeDebugString(FieldInfo field) private static int AllocateStrings(Memory mem, string[] val, bool wide, bool raw) { // TODO: I might add a dedicated method for allocating string arrays - var stringTasks = val.Select(x => mem.AllocateString(x, wide, 1, raw)); + var stringTasks = val.Select(x => mem.AllocateString(x, wide, 1, false/*raw*/)); var strings = Task.WhenAll(stringTasks); return mem.AllocateAndInitStructArray(strings.Result).Result; } diff --git a/OmsiHook/WrappedOmsiClasses/OmsiComplMapObjInst.cs b/OmsiHook/WrappedOmsiClasses/OmsiComplMapObjInst.cs index 16d4125..bdb610b 100644 --- a/OmsiHook/WrappedOmsiClasses/OmsiComplMapObjInst.cs +++ b/OmsiHook/WrappedOmsiClasses/OmsiComplMapObjInst.cs @@ -15,7 +15,7 @@ public class OmsiComplMapObjInst : OmsiPhysObjInst private MemArrayStringDict funcsStrings; private OmsiFuncClass[] funcs; private float[] consts; - private MemArray stringVars; + private MemArrayString stringVars; internal OmsiComplMapObjInst(Memory omsiMemory, int baseAddress) : base(omsiMemory, baseAddress) { } public OmsiComplMapObjInst() : base() { } @@ -196,7 +196,7 @@ public string GetStringVariable(string varName) if (index >= stringVars.Count || index < 0) throw new KeyNotFoundException($"String Variable '{varName}' not found in object. - Index Out Of Bounds"); stringVars.UpdateFromHook(index); - return stringVars[index].String; + return stringVars[index]; } /// diff --git a/OmsiHook/WrappedOmsiClasses/OmsiComplObjInst.cs b/OmsiHook/WrappedOmsiClasses/OmsiComplObjInst.cs index e20331e..307e557 100644 --- a/OmsiHook/WrappedOmsiClasses/OmsiComplObjInst.cs +++ b/OmsiHook/WrappedOmsiClasses/OmsiComplObjInst.cs @@ -77,9 +77,9 @@ public MemArrayPtr PublicVars { get => new(Memory, Memory.ReadMemory(Address + 0x28)); } - public MemArray StringVars + public MemArrayString StringVars { - get => new(Memory, Address + 0x2c, false); + get => new(Memory, Address + 0x2c, true, false); } /* TODO: public OmsiChangeTex[] ChangeTexs diff --git a/OmsiHook/docs/docfx.json b/OmsiHook/docs/docfx.json index fd8cc9f..0941de3 100644 --- a/OmsiHook/docs/docfx.json +++ b/OmsiHook/docs/docfx.json @@ -29,7 +29,7 @@ "_enableNewTab": true, "_disableContribution": true, "_disableBreadcrumb": false, - "_DocumentationVersion": "2.5.3" + "_DocumentationVersion": "2.5.4" }, "content": [ { diff --git a/OmsiHookPlugin/OmsiHookPlugin.csproj b/OmsiHookPlugin/OmsiHookPlugin.csproj index 9fd3d6b..4a6453f 100644 --- a/OmsiHookPlugin/OmsiHookPlugin.csproj +++ b/OmsiHookPlugin/OmsiHookPlugin.csproj @@ -13,7 +13,7 @@ OMSI_PLUGIN - + diff --git a/OmsiHookRPCPlugin/FastBinaryReader.cs b/OmsiHookRPCPlugin/FastBinaryReader.cs index 98493be..b0ef771 100644 --- a/OmsiHookRPCPlugin/FastBinaryReader.cs +++ b/OmsiHookRPCPlugin/FastBinaryReader.cs @@ -2,6 +2,7 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; @@ -11,28 +12,28 @@ internal static class FastBinaryReader { public static int ReadI32(Span buffer, ref int pos) { - var ret = BitConverter.ToInt32(buffer[pos..]); + var ret = MemoryMarshal.Read(buffer[pos..]); pos += 4; return ret; } public static uint ReadU32(Span buffer, ref int pos) { - var ret = BitConverter.ToUInt32(buffer[pos..]); + var ret = MemoryMarshal.Read(buffer[pos..]); pos += 4; return ret; } public static short ReadI16(Span buffer, ref int pos) { - var ret = BitConverter.ToInt16(buffer[pos..]); + var ret = MemoryMarshal.Read(buffer[pos..]); pos += 2; return ret; } public static ushort ReadU16(Span buffer, ref int pos) { - var ret = BitConverter.ToUInt16(buffer[pos..]); + var ret = MemoryMarshal.Read(buffer[pos..]); pos += 2; return ret; } @@ -53,14 +54,14 @@ public static byte ReadU8(Span buffer, ref int pos) public static bool ReadBool(Span buffer, ref int pos) { - var ret = BitConverter.ToBoolean(buffer[pos..]); + var ret = MemoryMarshal.Read(buffer[pos..]); pos += 1; return ret; } public static float ReadFloat(Span buffer, ref int pos) { - var ret = BitConverter.ToSingle(buffer[pos..]); + var ret = MemoryMarshal.Read(buffer[pos..]); pos += 4; return ret; } diff --git a/OmsiHookRPCPlugin/OmsiHookRPCPlugin.cs b/OmsiHookRPCPlugin/OmsiHookRPCPlugin.cs index 378b1f3..e4cdcd8 100644 --- a/OmsiHookRPCPlugin/OmsiHookRPCPlugin.cs +++ b/OmsiHookRPCPlugin/OmsiHookRPCPlugin.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -12,471 +11,462 @@ using System.Buffers; using System.Threading.Tasks; using System.Reflection; -using System.Linq; -namespace OmsiHookRPCPlugin +namespace OmsiHookRPCPlugin; + +public class OmsiHookRPCPlugin { - public class OmsiHookRPCPlugin - { - public const int MAX_CLIENTS = 8; - /// - /// Forces all RPC to occur in the main thread. Has a slight performance impact but is usually much more stable. - /// - public const bool SINGLE_THREADED_EXECUTION = true; + public const int MAX_CLIENTS = 8; + /// + /// Forces all RPC to occur in the main thread. Has a slight performance impact but is usually much more stable. + /// + public const bool SINGLE_THREADED_EXECUTION = true; - private static List threadPool; - private static ConcurrentQueue callQueue; - private static ConcurrentBag pipes; - private static ArrayPool argumentArrayPool; - private static List returnPool; - private static readonly object logLock = new(); - private static readonly object writeMutex = new(); + private static readonly List threadPool; + private static readonly ConcurrentQueue callQueue; + private static readonly ConcurrentBag pipes; + private static readonly ArrayPool argumentArrayPool; + private static readonly List returnPool; + private static readonly object logLock = new(); + private static readonly object writeMutex = new(); - internal record struct ReturnPromise(int Val, int Promise); + static OmsiHookRPCPlugin() + { + threadPool = []; + callQueue = []; + pipes = []; + returnPool = new(MAX_CLIENTS); + argumentArrayPool = ArrayPool.Create(256, 8); + } - private struct ReturnData + private static void Log(object msg) + { + lock (logLock) { - public readonly SemaphoreSlim isReady; - public ConcurrentQueue values; - - public ReturnData() - { - this.isReady = new(0); - this.values = new(); - } + File.AppendAllText("omsiHookRPCPluginLog.txt", $"[{DateTime.Now:dd/MM/yy HH:mm:ss:ff}] {msg}\n"); } + } - private readonly struct MethodData + /// + /// Plugin entry point. + /// + /// + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)], EntryPoint = nameof(PluginStart))] + // ReSharper disable once UnusedMember.Global + public static void PluginStart(nint aOwner) + { + try { - public readonly RemoteMethod method; - public readonly byte[] args; - public readonly int threadId; - public readonly int returnPromise; - - public MethodData(RemoteMethod method, byte[] args, int threadId, int returnPromise) - { - this.method = method; - this.args = args; - this.threadId = threadId; - this.returnPromise = returnPromise; - } + File.Delete("omsiHookRPCPluginLog.txt"); } + catch { } + Log($"################################# Omsi Hook RPC Plugin ##################################"); + Log($"~ version: {Assembly.GetExecutingAssembly().GetName().Version}"); + Log($"~ copyright Thomas Mathieson 2023-2024"); + Log($"~ Omsi Hook RPC plugin is a simple plugin allowing OMSI mods which use the OmsiHook SDK ~"); + Log($"~ to interact with OMSI from an external process. The source code is available at: ~"); + Log($"~ https://github.com/space928/Omsi-Extensions ~"); + Log($"#########################################################################################"); + Log($""); + Log($@"Starting RPC server on named pipe: \\.\pipe\{PIPE_NAME_RX} and \\.\pipe\{PIPE_NAME_TX} with {MAX_CLIENTS} threads..."); - private static void Log(object msg) + // We expect server threads to have a relatively long lifetime so for now, no need to have a complicated thread pool implementation. + threadPool.Clear(); + returnPool.Clear(); + for (int i = 0; i < MAX_CLIENTS; i++) { - lock (logLock) - { - File.AppendAllText("omsiHookRPCPluginLog.txt", $"[{DateTime.Now:dd/MM/yy HH:mm:ss:ff}] {msg}\n"); - } + Thread thread = new((id) => ServerThreadStart((int)id)); + threadPool.Add(thread); + lock (returnPool) + returnPool.Add(new()); + thread.Start(i); } + } - /// - /// Plugin entry point. - /// - /// - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }, EntryPoint = nameof(PluginStart))] - // ReSharper disable once UnusedMember.Global - public static void PluginStart(IntPtr aOwner) + private static void ServerThreadStart(int threadId) + { + while (true) { try { - File.Delete("omsiHookRPCPluginLog.txt"); - } - catch { } - Log($"################################# Omsi Hook RPC Plugin ##################################"); - Log($"~ version: {Assembly.GetExecutingAssembly().GetName().Version}"); - Log($"~ copyright Thomas Mathieson 2023-2024"); - Log($"~ Omsi Hook RPC plugin is a simple plugin allowing OMSI mods which use the OmsiHook SDK ~"); - Log($"~ to interact with OMSI from an external process. The source code is available at: ~"); - Log($"~ https://github.com/space928/Omsi-Extensions ~"); - Log($"#########################################################################################"); - Log($""); - Log($@"Starting RPC server on named pipe: \\.\pipe\{PIPE_NAME_RX} and \\.\pipe\{PIPE_NAME_TX} with {MAX_CLIENTS} threads..."); - - argumentArrayPool = ArrayPool.Create(256, 8); - pipes = new(); - - if (SINGLE_THREADED_EXECUTION) - { - callQueue = new(); - returnPool = new(MAX_CLIENTS); - } - - // We expect server threads to have a relatively long lifetime so for now, no need to have a complicated thread pool implementation. - threadPool = new(); - for (int i = 0; i < MAX_CLIENTS; i++) - { - Thread thread = new((id) => ServerThreadStart((int)id)); - threadPool.Add(thread); - lock (returnPool) - returnPool.Add(new()); - thread.Start(i); - } - } - - private static void ServerThreadStart(int threadId) - { - while (true) - { - try - { #if DEBUG - Log($"[RPC Server {threadId}] Init."); + Log($"[RPC Server {threadId}] Init."); #endif - using NamedPipeServerStream pipeRX = new(PIPE_NAME_RX, PipeDirection.In, MAX_CLIENTS, PipeTransmissionMode.Byte); - pipeRX.WaitForConnection(); - Log($"[RPC Server {threadId}] Client has connected to rx."); - // TODO: There's still a race condition here for some reason... Sometimes the client connects to the rx of one thread and the tx of another. - using NamedPipeServerStream pipeTX = new(PIPE_NAME_TX, PipeDirection.Out, MAX_CLIENTS, PipeTransmissionMode.Byte); - pipeTX.WaitForConnection(); - Log($"[RPC Server {threadId}] Client has connected to tx."); - pipes.Add(pipeRX); - pipes.Add(pipeTX); + using NamedPipeServerStream pipeRX = new(PIPE_NAME_RX, PipeDirection.In, MAX_CLIENTS, PipeTransmissionMode.Byte); + pipeRX.WaitForConnection(); + Log($"[RPC Server {threadId}] Client has connected to rx."); + // TODO: There's still a race condition here for some reason... Sometimes the client connects to the rx of one thread and the tx of another. + using NamedPipeServerStream pipeTX = new(PIPE_NAME_TX, PipeDirection.Out, MAX_CLIENTS, PipeTransmissionMode.Byte); + pipeTX.WaitForConnection(); + Log($"[RPC Server {threadId}] Client has connected to tx."); + pipes.Add(pipeRX); + pipes.Add(pipeTX); - var readTask = new Task(() => ReadTask(threadId, pipeRX, pipeTX)); - var writeTask = new Task(() => WriteTask(threadId, pipeTX)); - readTask.Start(); - writeTask.Start(); + var readTask = new Task(() => ReadTask(threadId, pipeRX, pipeTX)); + var writeTask = new Task(() => WriteTask(threadId, pipeTX)); + readTask.Start(); + writeTask.Start(); - Task.WaitAny(readTask, writeTask); - try - { - pipeRX.Disconnect(); - } - catch { } - try - { - pipeTX.Disconnect(); - } - catch { } - readTask.Dispose(); - writeTask.Dispose(); - pipeRX.Dispose(); - pipeTX.Dispose(); - Thread.Sleep(500); - } - catch (AggregateException ex) + Task.WaitAny(readTask, writeTask); + try { - // Pipe closed normally, probably... - // if (ex.InnerException.GetType() != typeof(EndOfStreamException)) - // Log(ex.InnerException); - //if (!ex.InnerExceptions.All(x => x.GetType() == typeof(EndOfStreamException))) - Log(ex); + pipeRX.Disconnect(); } - catch (Exception ex) + catch { } + try { - Log(ex); + pipeTX.Disconnect(); } - Log($"[RPC Server {threadId}] Client has disconnected."); + catch { } + readTask.Dispose(); + writeTask.Dispose(); + pipeRX.Dispose(); + pipeTX.Dispose(); + Thread.Sleep(500); } + catch (AggregateException ex) + { + // Pipe closed normally, probably... + // if (ex.InnerException.GetType() != typeof(EndOfStreamException)) + // Log(ex.InnerException); + //if (!ex.InnerExceptions.All(x => x.GetType() == typeof(EndOfStreamException))) + Log(ex); + } + catch (Exception ex) + { + Log(ex); + } + Log($"[RPC Server {threadId}] Client has disconnected."); } + } - private static void WriteTask(int threadId, NamedPipeServerStream pipeTX) + private static void WriteTask(int threadId, NamedPipeServerStream pipeTX) + { + try { - try + using BinaryWriter writer = new(pipeTX); + while (pipeTX.IsConnected) { - using BinaryWriter writer = new(pipeTX); - while (pipeTX.IsConnected) + returnPool[threadId].isReady.Wait(200); + lock (writeMutex) { - returnPool[threadId].isReady.Wait(200); - lock (writeMutex) + if (returnPool[threadId].values.TryDequeue(out ReturnPromise ret)) { - if (returnPool[threadId].values.TryDequeue(out ReturnPromise ret)) - { - //Log($"[RPC Server {threadId}] writing: {ret.Val:X} for promise: 0x{ret.Promise:X8}"); - writer.Write(ret.Promise); - writer.Write(ret.Val); - } - //else - // Log($"[RPC Server {threadId}] tried to return a result, but no result was available!"); + //Log($"[RPC Server {threadId}] writing: {ret.Val:X} for promise: 0x{ret.Promise:X8}"); + writer.Write(ret.Promise); + writer.Write(ret.Val); } - //Log($"[RPC Server {threadId}] Done!"); + //else + // Log($"[RPC Server {threadId}] tried to return a result, but no result was available!"); } + //Log($"[RPC Server {threadId}] Done!"); } - catch(ObjectDisposedException) - { - Log($"[RPC Server {threadId}] Couldn't write to pipe: Pipe was disposed!"); - } - catch (Exception ex) - { - Log($"[RPC Server {threadId}] Couldn't write to pipe: \n{ex}"); - throw; - } } + catch(ObjectDisposedException) + { + Log($"[RPC Server {threadId}] Couldn't write to pipe: Pipe was disposed!"); + } + catch (Exception ex) + { + Log($"[RPC Server {threadId}] Couldn't write to pipe: \n{ex}"); + throw; + } + } - private static void ReadTask(int threadId, NamedPipeServerStream pipeRX, NamedPipeServerStream pipeTX) + private static void ReadTask(int threadId, NamedPipeServerStream pipeRX, NamedPipeServerStream pipeTX) + { + try { - try + using BinaryReader reader = new(pipeRX); + while (pipeRX.IsConnected) { - using BinaryReader reader = new(pipeRX); - while (pipeRX.IsConnected) - { - // Read the message type from the pipe - RemoteMethod method = (RemoteMethod)reader.ReadInt32(); - int returnPromise = reader.ReadInt32(); + // Read the message type from the pipe + RemoteMethod method = (RemoteMethod)reader.ReadInt32(); + int returnPromise = reader.ReadInt32(); - int argBytes = RemoteMethodsArgsSizes[method]; - byte[] args = argumentArrayPool.Rent(argBytes); + int argBytes = RemoteMethodsArgsSizes[method]; + byte[] args = argumentArrayPool.Rent(argBytes); #if DEBUG - Log($"[RPC Server {threadId}] Remote method execute: '{method}' promise: 0x{returnPromise:X8}; reading {argBytes} bytes of arguments..."); + Log($"[RPC Server {threadId}] Remote method execute: '{method}' promise: 0x{returnPromise:X8}; reading {argBytes} bytes of arguments..."); #endif - // Read all the arguments into a byte array - int read = reader.Read(args, 0, argBytes); - if (read < argBytes) - { - Log($"Only read {read} out of {argBytes} bytes of arguments for {method} call!"); - argumentArrayPool.Return(args); - continue; - } + // Read all the arguments into a byte array + int read = reader.Read(args, 0, argBytes); + if (read < argBytes) + { + Log($"Only read {read} out of {argBytes} bytes of arguments for {method} call!"); + argumentArrayPool.Return(args); + continue; + } + + if (method == RemoteMethod.None) + { + argumentArrayPool.Return(args); + continue; + } - if (method == RemoteMethod.None) + if (method == RemoteMethod.CloseRPCConnection) + { + if (BitConverter.ToUInt32(args, 0) != 0) { - argumentArrayPool.Return(args); - continue; + foreach (var pipe in pipes) + pipe.Close(); } - - if (method == RemoteMethod.CloseRPCConnection) + else { - if (BitConverter.ToUInt32(args, 0) != 0) - { - foreach (var pipe in pipes) - pipe.Close(); - } - else - { - pipeRX.Close(); - pipeTX.Close(); - } - argumentArrayPool.Return(args); - return; + pipeRX.Close(); + pipeTX.Close(); } + argumentArrayPool.Return(args); + return; + } - callQueue.Enqueue(new(method, args, threadId, returnPromise)); + callQueue.Enqueue(new(method, args, threadId, returnPromise)); #if DEBUG - Log($"[RPC Server {threadId}] method enqueued..."); + Log($"[RPC Server {threadId}] method enqueued..."); #endif - } - } - catch (EndOfStreamException) - { - //throw; - } - catch (Exception ex) - { - Log($"[RPC Server {threadId}] Couldn't read from pipe: \n{ex}"); - throw; } } - - /// - /// Parses the method arguments and executes the native method. - /// - /// - private static void ProcessCall(MethodData methodData) + catch (EndOfStreamException) { - int ret = -1; - int argInd = 0; + //throw; + } + catch (Exception ex) + { + Log($"[RPC Server {threadId}] Couldn't read from pipe: \n{ex}"); + throw; + } + } + + /// + /// Parses the method arguments and executes the native method. + /// + /// + private static void ProcessCall(MethodData methodData) + { + int ret = -1; + int argInd = 0; #if DEBUG - Log($"Processing call: {methodData.method} args[{methodData.args.Length}] promise:{methodData.returnPromise}"); + Log($"Processing call: {methodData.method} args[{methodData.args.Length}] promise:{methodData.returnPromise}"); #endif - switch (methodData.method) - { - case RemoteMethod.TProgManMakeVehicle: - ret = NativeImports.TProgManMakeVehicle( - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadFloat(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadU8(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.TTempRVListCreate: - ret = NativeImports.TTempRVListCreate( - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.CopyTempListIntoMainList: - ret = NativeImports.CopyTempListIntoMainList( - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.TProgManPlaceRandomBus: - ret = NativeImports.TProgManPlaceRandomBus( - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadFloat(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadBool(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.GetMem: - //Log($"[RPC Main Thread] Executing GetMem(size={BitConverter.ToInt32(methodData.args, argInd)})"); - ret = NativeImports.GetMem( - FastBinaryReader.ReadI32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.FreeMem: - ret = NativeImports.FreeMem( - FastBinaryReader.ReadI32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.HookD3D: - ret = NativeImports.HookD3D(); - break; - case RemoteMethod.CreateTexture: - ret = NativeImports.CreateTexture( - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.UpdateSubresource: - ret = NativeImports.UpdateSubresource( - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.ReleaseTexture: - ret = NativeImports.ReleaseTexture( - FastBinaryReader.ReadU32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.GetTextureDesc: - ret = NativeImports.GetTextureDesc( - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd), - FastBinaryReader.ReadU32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.GetTextureLevelCount: - ret = unchecked((int)NativeImports.GetTextureLevelCount( - FastBinaryReader.ReadU32(methodData.args, ref argInd) - )); - break; - case RemoteMethod.IsTexture: - ret = NativeImports.IsTexture( - FastBinaryReader.ReadU32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.RVTriggerXML: - ret = 0; - NativeImports.RVTriggerXML( - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.SoundTrigger: - ret = 0; - NativeImports.SoundTrigger( - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd), - FastBinaryReader.ReadI32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.SetCriticalSectionLock: - ret = 0; - NativeImports.SetCriticalSectionLock( - FastBinaryReader.ReadI32(methodData.args, ref argInd) - ); - break; - case RemoteMethod.ReleaseCriticalSectionLock: - ret = 0; - NativeImports.ReleaseCriticalSectionLock( - FastBinaryReader.ReadI32(methodData.args, ref argInd) - ); - break; - default: - Log($"Unknown message type: {methodData.method} encountered!"); - break; - } + switch (methodData.method) + { + case RemoteMethod.TProgManMakeVehicle: + ret = NativeImports.TProgManMakeVehicle( + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadFloat(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadU8(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.TTempRVListCreate: + ret = NativeImports.TTempRVListCreate( + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.CopyTempListIntoMainList: + ret = NativeImports.CopyTempListIntoMainList( + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.TProgManPlaceRandomBus: + ret = NativeImports.TProgManPlaceRandomBus( + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadFloat(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadBool(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.GetMem: + //Log($"[RPC Main Thread] Executing GetMem(size={BitConverter.ToInt32(methodData.args, argInd)})"); + ret = NativeImports.GetMem( + FastBinaryReader.ReadI32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.FreeMem: + ret = NativeImports.FreeMem( + FastBinaryReader.ReadI32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.HookD3D: + ret = NativeImports.HookD3D(); + break; + case RemoteMethod.CreateTexture: + ret = NativeImports.CreateTexture( + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.UpdateSubresource: + ret = NativeImports.UpdateSubresource( + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.ReleaseTexture: + ret = NativeImports.ReleaseTexture( + FastBinaryReader.ReadU32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.GetTextureDesc: + ret = NativeImports.GetTextureDesc( + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd), + FastBinaryReader.ReadU32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.GetTextureLevelCount: + ret = unchecked((int)NativeImports.GetTextureLevelCount( + FastBinaryReader.ReadU32(methodData.args, ref argInd) + )); + break; + case RemoteMethod.IsTexture: + ret = NativeImports.IsTexture( + FastBinaryReader.ReadU32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.RVTriggerXML: + ret = 0; + NativeImports.RVTriggerXML( + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.SoundTrigger: + ret = 0; + NativeImports.SoundTrigger( + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd), + FastBinaryReader.ReadI32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.SetCriticalSectionLock: + ret = 0; + NativeImports.SetCriticalSectionLock( + FastBinaryReader.ReadI32(methodData.args, ref argInd) + ); + break; + case RemoteMethod.ReleaseCriticalSectionLock: + ret = 0; + NativeImports.ReleaseCriticalSectionLock( + FastBinaryReader.ReadI32(methodData.args, ref argInd) + ); + break; + default: + Log($"Unknown message type: {methodData.method} encountered!"); + break; + } #if DEBUG - Log($" returning {ret} to {methodData.returnPromise}..."); + Log($" returning {ret} to {methodData.returnPromise}..."); #endif - returnPool[methodData.threadId].values.Enqueue(new(ret, methodData.returnPromise)); - returnPool[methodData.threadId].isReady.Release(); - argumentArrayPool.Return(methodData.args); - } + returnPool[methodData.threadId].values.Enqueue(new(ret, methodData.returnPromise)); + returnPool[methodData.threadId].isReady.Release(); + argumentArrayPool.Return(methodData.args); + } - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }, EntryPoint = nameof(PluginFinalize))] - // ReSharper disable once UnusedMember.Global - public static void PluginFinalize() - { - Log("Exiting OmsiHookRPCPlugin..."); - } + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }, EntryPoint = nameof(PluginFinalize))] + // ReSharper disable once UnusedMember.Global + public static void PluginFinalize() + { + Log("Exiting OmsiHookRPCPlugin..."); + } - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }, EntryPoint = nameof(AccessVariable))] - public static void AccessVariable(ushort variableIndex, [C99Type("float*")] IntPtr value, - [C99Type("__crt_bool*")] IntPtr writeValue) + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }, EntryPoint = nameof(AccessVariable))] + public static void AccessVariable(ushort variableIndex, [C99Type("float*")] IntPtr value, + [C99Type("__crt_bool*")] IntPtr writeValue) + { + if (SINGLE_THREADED_EXECUTION) { - if (SINGLE_THREADED_EXECUTION) + while (!callQueue.IsEmpty) { - while (!callQueue.IsEmpty) + if (callQueue.TryDequeue(out MethodData call)) { - if (callQueue.TryDequeue(out MethodData call)) + try + { + ProcessCall(call); + } + catch (Exception ex) { - try - { - ProcessCall(call); - } - catch (Exception ex) - { - Log($"Exception while processing call {call.returnPromise} for thread {call.threadId} method={call.method} args={call.args.Length}:\n{ex}"); - } + Log($"Exception while processing call {call.returnPromise} for thread {call.threadId} method={call.method} args={call.args.Length}:\n{ex}"); } } } } + } - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) }, EntryPoint = nameof(AccessTrigger))] - public static void AccessTrigger(ushort variableIndex, [C99Type("__crt_bool*")] IntPtr triggerScript) { } + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)], EntryPoint = nameof(AccessTrigger))] + public static void AccessTrigger(ushort variableIndex, [C99Type("__crt_bool*")] nint triggerScript) { } - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] - public static void AccessStringVariable(ushort variableIndex, [C99Type("char*")] IntPtr firstCharacterAddress, [C99Type("__crt_bool*")] IntPtr writeValue) { } + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + public static void AccessStringVariable(ushort variableIndex, [C99Type("char*")] nint firstCharacterAddress, [C99Type("__crt_bool*")] nint writeValue) { } - [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] - public static void AccessSystemVariable(ushort variableIndex, [C99Type("float*")] IntPtr value, [C99Type("__crt_bool*")] IntPtr writeValue) { } + [UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])] + public static void AccessSystemVariable(ushort variableIndex, [C99Type("float*")] nint value, [C99Type("__crt_bool*")] nint writeValue) { } + + internal record struct ReturnPromise(int Val, int Promise); + + private struct ReturnData + { + public readonly SemaphoreSlim isReady; + public ConcurrentQueue values; + + public ReturnData() + { + this.isReady = new(0); + this.values = new(); + } + } + + private readonly struct MethodData(RemoteMethod method, byte[] args, int threadId, int returnPromise) + { + public readonly RemoteMethod method = method; + public readonly byte[] args = args; + public readonly int threadId = threadId; + public readonly int returnPromise = returnPromise; } } diff --git a/OmsiHookRPCPlugin/OmsiHookRPCPlugin.csproj b/OmsiHookRPCPlugin/OmsiHookRPCPlugin.csproj index 435cb31..d8b6a23 100644 --- a/OmsiHookRPCPlugin/OmsiHookRPCPlugin.csproj +++ b/OmsiHookRPCPlugin/OmsiHookRPCPlugin.csproj @@ -1,18 +1,23 @@  - net6.0-windows - AnyCPU;x86 + netstandard2.0 + win-x86 + x86 + x86 + True + 12.0 + enable Thomas Mathieson - 1.3.11 + 1.3.12 Thomas Mathieson - - - True - - - x86 + OmsiHook RPC Plugin + Logo.png + README.md + LGPL-3.0-only + False + True @@ -22,9 +27,16 @@ True \ + + True + \ + + + + @@ -102,11 +114,6 @@ valid self-contained install environment. --> false - OmsiHook RPC Plugin - Logo.png - LGPL-3.0-only - False - True diff --git a/OmsiHookRPCPlugin/README.md b/OmsiHookRPCPlugin/README.md new file mode 100644 index 0000000..a3564b1 --- /dev/null +++ b/OmsiHookRPCPlugin/README.md @@ -0,0 +1,4 @@ +# OmsiHook RPC Plugin + +This is a plugin for Omsi, which allows applications using +[OmsiHook](https://www.nuget.org/packages/OmsiHook/) to interact with Omsi. diff --git a/OmsiHookRPCPlugin/UnmanagedCallersOnlyAttribute.cs b/OmsiHookRPCPlugin/UnmanagedCallersOnlyAttribute.cs new file mode 100644 index 0000000..a72e88f --- /dev/null +++ b/OmsiHookRPCPlugin/UnmanagedCallersOnlyAttribute.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.InteropServices +{ + /// + /// Any method marked with can be directly called from + /// native code. The function token can be loaded to a local variable using the address-of operator + /// in C# and passed as a callback to a native method. + /// + /// + /// Methods marked with this attribute have the following restrictions: + /// * Method must be marked "static". + /// * Must not be called from managed code. + /// * Must only have blittable arguments. + /// + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + internal sealed class UnmanagedCallersOnlyAttribute : Attribute + { + public UnmanagedCallersOnlyAttribute() + { + } + + /// + /// Optional. If omitted, the runtime will use the default platform calling convention. + /// + /// + /// Supplied types must be from the official "System.Runtime.CompilerServices" namespace and + /// be of the form "CallConvXXX". + /// + public Type[]? CallConvs; + + /// + /// Optional. If omitted, no named export is emitted during compilation. + /// + public string? EntryPoint; + } +} diff --git a/_OmsiHookExamples/BasicCLI/BasicCLI.csproj b/_OmsiHookExamples/BasicCLI/BasicCLI.csproj index c66fed0..366e4a0 100644 --- a/_OmsiHookExamples/BasicCLI/BasicCLI.csproj +++ b/_OmsiHookExamples/BasicCLI/BasicCLI.csproj @@ -10,7 +10,7 @@ - + diff --git a/_OmsiHookExamples/ClickablePlaneDemo/ClickablePlaneDemo.csproj b/_OmsiHookExamples/ClickablePlaneDemo/ClickablePlaneDemo.csproj index 12bb377..57a0e39 100644 --- a/_OmsiHookExamples/ClickablePlaneDemo/ClickablePlaneDemo.csproj +++ b/_OmsiHookExamples/ClickablePlaneDemo/ClickablePlaneDemo.csproj @@ -10,7 +10,7 @@ - + diff --git a/_OmsiHookExamples/EventSample/EventSample.csproj b/_OmsiHookExamples/EventSample/EventSample.csproj index 1b40390..dc1df40 100644 --- a/_OmsiHookExamples/EventSample/EventSample.csproj +++ b/_OmsiHookExamples/EventSample/EventSample.csproj @@ -11,7 +11,7 @@ - + diff --git a/_OmsiHookExamples/TriggersSample/TriggersSample.csproj b/_OmsiHookExamples/TriggersSample/TriggersSample.csproj index 1ee32ba..396ff9c 100644 --- a/_OmsiHookExamples/TriggersSample/TriggersSample.csproj +++ b/_OmsiHookExamples/TriggersSample/TriggersSample.csproj @@ -11,7 +11,7 @@ - + diff --git a/_OmsiHookExamples/VideoDemo/VideoDemo.csproj b/_OmsiHookExamples/VideoDemo/VideoDemo.csproj index a9498e9..635b181 100644 --- a/_OmsiHookExamples/VideoDemo/VideoDemo.csproj +++ b/_OmsiHookExamples/VideoDemo/VideoDemo.csproj @@ -12,7 +12,7 @@ - +