diff --git a/Core/Buffer.cs b/Core/Buffer.cs new file mode 100644 index 0000000..49a320a --- /dev/null +++ b/Core/Buffer.cs @@ -0,0 +1,182 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using GTA.Math; + +namespace RageCoop.Core +{ + internal unsafe abstract class BufferBase + { + public int Size { get; protected set; } + public int CurrentIndex { get; protected set; } + public byte* Address { get; protected set; } + + /// + /// Ensure memory safety and advance position by specified number of bytes + /// + /// + /// Pointer to the current position in the buffer + protected abstract byte* Alloc(int cbSize); + + protected T* Alloc(int count = 1) where T : unmanaged + => (T*)Alloc(count * sizeof(T)); + } + + internal unsafe sealed class WriteBuffer : BufferBase + { + public WriteBuffer(int size) + { + Resize(size); + } + + public void Resize(int size) + { + if (size < Size) + { + Size = size; + return; + } + + var newAddr = (byte*)Marshal.AllocHGlobal(size); + if (Address != null) + { + Buffer.MemoryCopy(Address, newAddr, size, Size); + Marshal.FreeHGlobal((IntPtr)Address); + } + Size = size; + Address = newAddr; + } + + protected override byte* Alloc(int cbSize) + { + var index = CurrentIndex; + CurrentIndex += cbSize; + + // Resize the buffer by at least 50% if there's no sufficient space + if (CurrentIndex > Size) + Resize(Math.Max(CurrentIndex + 1, (int)(Size * 1.5f))); + + return Address + index; + } + + public void Write(ref T value) where T : unmanaged + { + var addr = Alloc(); + *addr = value; + } + + /// + /// For passing struct smaller than word size (4/8 bytes on 32/64 bit system) + /// + /// + /// + public void WriteVal(T value) where T : unmanaged + { + var addr = Alloc(); + *addr = value; + } + + + public void Write(ReadOnlySpan str) + { + // Prefixed by its size in bytes + var cbBody = Encoding.UTF8.GetByteCount(str); + WriteVal(cbBody); + + // Allocate and write string body + var pBody = Alloc(cbBody); + Encoding.UTF8.GetBytes(str, new(pBody, cbBody)); + } + + // Struct in GTA.Math have pack/padding for memory alignment, we don't want it to waste the bandwidth + + public void Write(ref Vector2 vec2) + { + var faddr = Alloc(2); + faddr[0] = vec2.X; + faddr[1] = vec2.Y; + } + + public void Write(ref Vector3 vec3) + { + var faddr = Alloc(3); + faddr[0] = vec3.X; + faddr[1] = vec3.Y; + faddr[2] = vec3.Z; + } + + public void Write(ref Quaternion quat) + { + var faddr = Alloc(4); + faddr[0] = quat.X; + faddr[1] = quat.Y; + faddr[2] = quat.Z; + faddr[3] = quat.W; + } + } + + internal unsafe sealed class ReadBuffer : BufferBase + { + public ReadBuffer(byte* address, int size) + { + Address = address; + Size = size; + } + + protected override byte* Alloc(int cbSize) + { + var index = CurrentIndex; + CurrentIndex += cbSize; + + if (CurrentIndex > Size) + throw new InvalidOperationException("Attempting to read beyond the existing buffer"); + + return Address + index; + } + + + public T ReadVal() where T : unmanaged => *Alloc(); + + public void Read(out T result) where T : unmanaged + => result = *Alloc(); + + public void Read(out string str) + { + var cbBody = ReadVal(); + str = Encoding.UTF8.GetString(Alloc(cbBody), cbBody); + } + + public void Read(out Vector2 vec) + { + var faddr = Alloc(2); + vec = new() + { + X = faddr[0], + Y = faddr[1], + }; + } + + public void Read(out Vector3 vec) + { + var faddr = Alloc(3); + vec = new() + { + X = faddr[0], + Y = faddr[1], + Z = faddr[2], + }; + } + + public void Read(out Quaternion quat) + { + var faddr = Alloc(4); + quat = new() + { + X = faddr[0], + Y = faddr[1], + Z = faddr[2], + W = faddr[3], + }; + } + } +} diff --git a/Core/CoreUtils.cs b/Core/CoreUtils.cs index d13a03f..7f5bd47 100644 --- a/Core/CoreUtils.cs +++ b/Core/CoreUtils.cs @@ -22,6 +22,7 @@ using RageCoop.Core.Scripting; [assembly: InternalsVisibleTo("RageCoop.Client")] [assembly: InternalsVisibleTo("RageCoop.Client.Installer")] [assembly: InternalsVisibleTo("DataDumper")] +[assembly: InternalsVisibleTo("UnitTest")] [assembly: InternalsVisibleTo("RageCoop.ResourceBuilder")] namespace RageCoop.Core diff --git a/Tools/UnitTest/Program.cs b/Tools/UnitTest/Program.cs new file mode 100644 index 0000000..8ea0479 --- /dev/null +++ b/Tools/UnitTest/Program.cs @@ -0,0 +1,74 @@ +using GTA.Math; +using RageCoop.Core; +using System.Security.Cryptography; + +namespace UnitTest +{ + class TestElement + { + static SHA256 sha = SHA256.Create(); + static int blah = new Random().Next(); + public TestElement(int i) + { + num = (i + blah) * 1024; + vec2 = new(num * 10, num * 20); + vec3 = new(num * 10, num * 20, num * 30); + quat = new(num * 10, num * 20, num * 30, num * 40); + str = sha.ComputeHash(BitConverter.GetBytes(num)).Dump(); + } + public int num; + public Vector2 vec2; + public Vector3 vec3; + public Quaternion quat; + public string str; + } + internal unsafe class Program + { + static void Main(string[] args) + { + TestElement[] test = new TestElement[1024]; + Console.WriteLine("Testing buffers"); + var buf = new WriteBuffer(1024); + for (int i = 0; i < 1024; i++) + { + var e = test[i] = new TestElement(i); + buf.WriteVal(e.num); + buf.Write(ref e.vec2); + buf.Write(ref e.vec3); + buf.Write(ref e.quat); + buf.Write(e.str); + } + Console.WriteLine($"Buffer size: {buf.Size}"); + Console.WriteLine($"Current position: {buf.CurrentIndex}"); + + Console.WriteLine("Validating data"); + var reader = new ReadBuffer(buf.Address, buf.Size); + for (int i = 0; i < 1024; i++) + { + var e = test[i]; + reader.Read(out int num); + reader.Read(out Vector2 vec2); + reader.Read(out Vector3 vec3); + reader.Read(out Quaternion quat); + reader.Read(out string str); + + if (num != e.num) + throw new Exception("POCO fail"); + + if (vec2 != e.vec2) + throw new Exception("vec2 fail"); + + if (vec3 != e.vec3) + throw new Exception("vec3 fail"); + + if (quat != e.quat) + throw new Exception("quat fail"); + + if (str != e.str) + throw new Exception("str fail"); + } + + Console.WriteLine("Buffers OK"); + } + } +} \ No newline at end of file diff --git a/Tools/UnitTest/UnitTest.csproj b/Tools/UnitTest/UnitTest.csproj new file mode 100644 index 0000000..436cc68 --- /dev/null +++ b/Tools/UnitTest/UnitTest.csproj @@ -0,0 +1,20 @@ + + + + Exe + net7.0 + enable + True + + + + + + + + + ..\..\libs\ScriptHookVDotNetCore.dll + + + + diff --git a/libs/ScriptHookVDotNetCore.dll b/libs/ScriptHookVDotNetCore.dll index 3b050f5..d93a544 120000 Binary files a/libs/ScriptHookVDotNetCore.dll and b/libs/ScriptHookVDotNetCore.dll differ