From 6497c626222b4a639d18f59591609aca40f3560f Mon Sep 17 00:00:00 2001 From: srkizer Date: Sat, 17 Feb 2024 01:16:21 +0900 Subject: [PATCH] Change MemoryHelper to allocate less (#1657) * Change MemoryHelper to allocate less * Use StringBuilder pool for ReadSeStringAsString * fix * Use CreateReadOnlySpanFromNullTerminated where possible --- Dalamud/Game/Internal/DalamudAtkTweaks.cs | 2 +- Dalamud/Interface/Internal/UiDebug.cs | 27 +- Dalamud/Memory/MemoryHelper.cs | 520 ++++++++++++++++------ 3 files changed, 400 insertions(+), 149 deletions(-) diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index 4eb605a76..30fab6b1b 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -107,7 +107,7 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg) { - // Log.Information("{0}: cmd#{1} a3#{2} - HasAnyFocus:{3}", Marshal.PtrToStringAnsi(new IntPtr(thisPtr->Name)), cmd, a3, WindowSystem.HasAnyWindowSystemFocus); + // Log.Information("{0}: cmd#{1} a3#{2} - HasAnyFocus:{3}", MemoryHelper.ReadSeStringAsString(out _, new IntPtr(thisPtr->Name)), cmd, a3, WindowSystem.HasAnyWindowSystemFocus); // "SendHotkey" // 3 == Close diff --git a/Dalamud/Interface/Internal/UiDebug.cs b/Dalamud/Interface/Internal/UiDebug.cs index 14f062e01..d93b90799 100644 --- a/Dalamud/Interface/Internal/UiDebug.cs +++ b/Dalamud/Interface/Internal/UiDebug.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using Dalamud.Game; using Dalamud.Game.Gui; using Dalamud.Interface.Utility; +using Dalamud.Memory; using Dalamud.Utility; using FFXIVClientStructs.FFXIV.Component.GUI; using ImGuiNET; @@ -82,7 +83,7 @@ internal unsafe class UiDebug private void DrawUnitBase(AtkUnitBase* atkUnitBase) { var isVisible = (atkUnitBase->Flags & 0x20) == 0x20; - var addonName = Marshal.PtrToStringAnsi(new IntPtr(atkUnitBase->Name)); + var addonName = MemoryHelper.ReadSeStringAsString(out _, new IntPtr(atkUnitBase->Name)); var agent = Service.Get().FindAgentInterface(atkUnitBase); ImGui.Text($"{addonName}"); @@ -204,7 +205,7 @@ internal unsafe class UiDebug { case NodeType.Text: var textNode = (AtkTextNode*)node; - ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(textNode->NodeText.StringPtr))}"); + ImGui.Text($"text: {MemoryHelper.ReadSeStringAsString(out _, (nint)textNode->NodeText.StringPtr)}"); ImGui.InputText($"Replace Text##{(ulong)textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint)textNode->NodeText.BufSize); @@ -231,7 +232,7 @@ internal unsafe class UiDebug break; case NodeType.Counter: var counterNode = (AtkCounterNode*)node; - ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(counterNode->NodeText.StringPtr))}"); + ImGui.Text($"text: {MemoryHelper.ReadSeStringAsString(out _, (nint)counterNode->NodeText.StringPtr)}"); break; case NodeType.Image: var imageNode = (AtkImageNode*)node; @@ -250,8 +251,8 @@ internal unsafe class UiDebug { var texFileNameStdString = &textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName; var texString = texFileNameStdString->Length < 16 - ? Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->Buffer) - : Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->BufferPtr); + ? MemoryHelper.ReadSeStringAsString(out _, (nint)texFileNameStdString->Buffer) + : MemoryHelper.ReadSeStringAsString(out _, (nint)texFileNameStdString->BufferPtr); ImGui.Text($"texture path: {texString}"); var kernelTexture = textureInfo->AtkTexture.Resource->KernelTextureObject; @@ -352,13 +353,13 @@ internal unsafe class UiDebug { case ComponentType.TextInput: var textInputComponent = (AtkComponentTextInput*)compNode->Component; - ImGui.Text($"InputBase Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); - ImGui.Text($"InputBase Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); - ImGui.Text($"Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText1.StringPtr))}"); - ImGui.Text($"Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText2.StringPtr))}"); - ImGui.Text($"Text3: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText3.StringPtr))}"); - ImGui.Text($"Text4: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText4.StringPtr))}"); - ImGui.Text($"Text5: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText5.StringPtr))}"); + ImGui.Text($"InputBase Text1: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}"); + ImGui.Text($"InputBase Text2: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}"); + ImGui.Text($"Text1: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText1.StringPtr))}"); + ImGui.Text($"Text2: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText2.StringPtr))}"); + ImGui.Text($"Text3: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText3.StringPtr))}"); + ImGui.Text($"Text4: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText4.StringPtr))}"); + ImGui.Text($"Text5: {MemoryHelper.ReadSeStringAsString(out _, new IntPtr(textInputComponent->UnkText5.StringPtr))}"); break; } @@ -474,7 +475,7 @@ internal unsafe class UiDebug foundSelected = true; } - var name = Marshal.PtrToStringAnsi(new IntPtr(unitBase->Name)); + var name = MemoryHelper.ReadSeStringAsString(out _, new IntPtr(unitBase->Name)); if (searching) { if (name == null || !name.ToLower().Contains(searchStr.ToLower())) continue; diff --git a/Dalamud/Memory/MemoryHelper.cs b/Dalamud/Memory/MemoryHelper.cs index 552817646..09f45e2d3 100644 --- a/Dalamud/Memory/MemoryHelper.cs +++ b/Dalamud/Memory/MemoryHelper.cs @@ -1,15 +1,21 @@ -using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Memory.Exceptions; + using FFXIVClientStructs.FFXIV.Client.System.Memory; using FFXIVClientStructs.FFXIV.Client.System.String; +using Microsoft.Extensions.ObjectPool; + using static Dalamud.NativeFunctions; +using LPayloadType = Lumina.Text.Payloads.PayloadType; +using LSeString = Lumina.Text.SeString; + // Heavily inspired from Reloaded (https://github.com/Reloaded-Project/Reloaded.Memory) namespace Dalamud.Memory; @@ -19,6 +25,47 @@ namespace Dalamud.Memory; /// public static unsafe class MemoryHelper { + private static readonly ObjectPool StringBuilderPool = + ObjectPool.Create(new StringBuilderPooledObjectPolicy()); + + #region Cast + + /// Casts the given memory address as the reference to the live object. + /// The memory address. + /// The unmanaged type. + /// The reference to the live object. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Cast(nint memoryAddress) where T : unmanaged => ref *(T*)memoryAddress; + + /// Casts the given memory address as the span of the live object(s). + /// The memory address. + /// The number of items. + /// The unmanaged type. + /// The span containing reference to the live object(s). + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span Cast(nint memoryAddress, int length) where T : unmanaged => + new((void*)memoryAddress, length); + + /// Casts the given memory address as the span of the live object(s), until it encounters a zero. + /// The memory address. + /// The maximum number of items. + /// The unmanaged type. + /// The span containing reference to the live object(s). + /// If is byte or char and is not + /// specified, consider using or + /// . + public static Span CastNullTerminated(nint memoryAddress, int maxLength = int.MaxValue) + where T : unmanaged, IEquatable + { + var typedPointer = (T*)memoryAddress; + var length = 0; + while (length < maxLength && !default(T).Equals(*typedPointer++)) + length++; + return new((void*)memoryAddress, length); + } + + #endregion + #region Read /// @@ -27,7 +74,9 @@ public static unsafe class MemoryHelper /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. /// The memory address to read from. /// The read in struct. - public static T Read(IntPtr memoryAddress) where T : unmanaged + /// If you do not need to make a copy, use instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Read(nint memoryAddress) where T : unmanaged => Read(memoryAddress, false); /// @@ -37,12 +86,13 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// Set this to true to enable struct marshalling. /// The read in struct. - public static T Read(IntPtr memoryAddress, bool marshal) - { - return marshal - ? Marshal.PtrToStructure(memoryAddress) - : Unsafe.Read((void*)memoryAddress); - } + /// If you do not need to make a copy and is false, + /// use instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Read(nint memoryAddress, bool marshal) => + marshal + ? Marshal.PtrToStructure(memoryAddress) + : Unsafe.Read((void*)memoryAddress); /// /// Reads a byte array from a specified memory address. @@ -50,12 +100,9 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The amount of bytes to read starting from the memoryAddress. /// The read in byte array. - public static byte[] ReadRaw(IntPtr memoryAddress, int length) - { - var value = new byte[length]; - Marshal.Copy(memoryAddress, value, 0, value.Length); - return value; - } + /// If you do not need to make a copy, use instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] ReadRaw(nint memoryAddress, int length) => Cast(memoryAddress, length).ToArray(); /// /// Reads a generic type array from a specified memory address. @@ -64,8 +111,10 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The amount of array items to read. /// The read in struct array. - public static T[] Read(IntPtr memoryAddress, int arrayLength) where T : unmanaged - => Read(memoryAddress, arrayLength, false); + /// If you do not need to make a copy, use instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T[] Read(nint memoryAddress, int arrayLength) where T : unmanaged + => Cast(memoryAddress, arrayLength).ToArray(); /// /// Reads a generic type array from a specified memory address. @@ -75,16 +124,18 @@ public static unsafe class MemoryHelper /// The amount of array items to read. /// Set this to true to enable struct marshalling. /// The read in struct array. - public static T[] Read(IntPtr memoryAddress, int arrayLength, bool marshal) + /// If you do not need to make a copy and is false, + /// use instead. + public static T[] Read(nint memoryAddress, int arrayLength, bool marshal) { var structSize = SizeOf(marshal); var value = new T[arrayLength]; for (var i = 0; i < arrayLength; i++) { - var address = memoryAddress + (structSize * i); - Read(address, out T result, marshal); + Read(memoryAddress, out T result, marshal); value[i] = result; + memoryAddress += structSize; } return value; @@ -95,16 +146,10 @@ public static unsafe class MemoryHelper /// /// The memory address to read from. /// The read in byte array. - public static unsafe byte[] ReadRawNullTerminated(IntPtr memoryAddress) - { - var byteCount = 0; - while (*(byte*)(memoryAddress + byteCount) != 0x00) - { - byteCount++; - } - - return ReadRaw(memoryAddress, byteCount); - } + /// If you do not need to make a copy, use instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] ReadRawNullTerminated(nint memoryAddress) => + MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)memoryAddress).ToArray(); #endregion @@ -116,7 +161,9 @@ public static unsafe class MemoryHelper /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. /// The memory address to read from. /// Local variable to receive the read in struct. - public static void Read(IntPtr memoryAddress, out T value) where T : unmanaged + /// If you do not need to make a copy, use instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(nint memoryAddress, out T value) where T : unmanaged => value = Read(memoryAddress); /// @@ -126,7 +173,10 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// Local variable to receive the read in struct. /// Set this to true to enable struct marshalling. - public static void Read(IntPtr memoryAddress, out T value, bool marshal) + /// If you do not need to make a copy and is false, + /// use instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(nint memoryAddress, out T value, bool marshal) => value = Read(memoryAddress, marshal); /// @@ -135,7 +185,9 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The amount of bytes to read starting from the memoryAddress. /// Local variable to receive the read in bytes. - public static void ReadRaw(IntPtr memoryAddress, int length, out byte[] value) + /// If you do not need to make a copy, use instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadRaw(nint memoryAddress, int length, out byte[] value) => value = ReadRaw(memoryAddress, length); /// @@ -145,7 +197,9 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The amount of array items to read. /// The read in struct array. - public static void Read(IntPtr memoryAddress, int arrayLength, out T[] value) where T : unmanaged + /// If you do not need to make a copy, use instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(nint memoryAddress, int arrayLength, out T[] value) where T : unmanaged => value = Read(memoryAddress, arrayLength); /// @@ -156,7 +210,10 @@ public static unsafe class MemoryHelper /// The amount of array items to read. /// Set this to true to enable struct marshalling. /// The read in struct array. - public static void Read(IntPtr memoryAddress, int arrayLength, bool marshal, out T[] value) + /// If you do not need to make a copy and is false, + /// use instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Read(nint memoryAddress, int arrayLength, bool marshal, out T[] value) => value = Read(memoryAddress, arrayLength, marshal); #endregion @@ -184,15 +241,27 @@ public static unsafe class MemoryHelper var length = 0; while (length < maxLength && pmem[length] != 0) length++; - + var mem = new Span(pmem, length); var memCharCount = encoding.GetCharCount(mem); if (memCharCount != charSpan.Length) return false; - Span chars = stackalloc char[memCharCount]; - encoding.GetChars(mem, chars); - return charSpan.SequenceEqual(chars); + if (memCharCount < 1024) + { + Span chars = stackalloc char[memCharCount]; + encoding.GetChars(mem, chars); + return charSpan.SequenceEqual(chars); + } + else + { + var rented = ArrayPool.Shared.Rent(memCharCount); + var chars = rented.AsSpan(0, memCharCount); + encoding.GetChars(mem, chars); + var equals = charSpan.SequenceEqual(chars); + ArrayPool.Shared.Return(rented); + return equals; + } } /// @@ -203,8 +272,9 @@ public static unsafe class MemoryHelper /// /// The memory address to read from. /// The read in string. - public static string ReadStringNullTerminated(IntPtr memoryAddress) - => ReadStringNullTerminated(memoryAddress, Encoding.UTF8); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ReadStringNullTerminated(nint memoryAddress) + => Encoding.UTF8.GetString(MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)memoryAddress)); /// /// Read a string with the given encoding from a specified memory address. @@ -215,10 +285,25 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The encoding to use to decode the string. /// The read in string. - public static string ReadStringNullTerminated(IntPtr memoryAddress, Encoding encoding) + public static string ReadStringNullTerminated(nint memoryAddress, Encoding encoding) { - var buffer = ReadRawNullTerminated(memoryAddress); - return encoding.GetString(buffer); + switch (encoding) + { + case UTF8Encoding: + case var _ when encoding.IsSingleByte: + return encoding.GetString(MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)memoryAddress)); + case UnicodeEncoding: + // Note that it may be in little or big endian, so using `new string(...)` is not always correct. + return encoding.GetString( + MemoryMarshal.Cast( + MemoryMarshal.CreateReadOnlySpanFromNullTerminated((char*)memoryAddress))); + case UTF32Encoding: + return encoding.GetString(MemoryMarshal.Cast(CastNullTerminated(memoryAddress))); + default: + // For correctness' sake; if there does not exist an encoding which will contain a (byte)0 for a + // non-null character, then this branch can be merged with UTF8Encoding one. + return encoding.GetString(ReadRawNullTerminated(memoryAddress)); + } } /// @@ -228,10 +313,12 @@ public static unsafe class MemoryHelper /// Attention! If this is an , use the applicable helper methods to decode. /// /// The memory address to read from. - /// The maximum length of the string. + /// The maximum number of bytes to read. + /// Note that this is NOT the maximum length of the returned string. /// The read in string. - public static string ReadString(IntPtr memoryAddress, int maxLength) - => ReadString(memoryAddress, Encoding.UTF8, maxLength); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ReadString(nint memoryAddress, int maxLength) + => Encoding.UTF8.GetString(CastNullTerminated(memoryAddress, maxLength)); /// /// Read a string with the given encoding from a specified memory address. @@ -241,18 +328,32 @@ public static unsafe class MemoryHelper /// /// The memory address to read from. /// The encoding to use to decode the string. - /// The maximum length of the string. + /// The maximum number of bytes to read. + /// Note that this is NOT the maximum length of the returned string. /// The read in string. - public static string ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength) + public static string ReadString(nint memoryAddress, Encoding encoding, int maxLength) { if (maxLength <= 0) return string.Empty; - ReadRaw(memoryAddress, maxLength, out var buffer); - - var data = encoding.GetString(buffer); - var eosPos = data.IndexOf('\0'); - return eosPos >= 0 ? data.Substring(0, eosPos) : data; + switch (encoding) + { + case UTF8Encoding: + case var _ when encoding.IsSingleByte: + return encoding.GetString(CastNullTerminated(memoryAddress, maxLength)); + case UnicodeEncoding: + return encoding.GetString( + MemoryMarshal.Cast(CastNullTerminated(memoryAddress, maxLength / 2))); + case UTF32Encoding: + return encoding.GetString( + MemoryMarshal.Cast(CastNullTerminated(memoryAddress, maxLength / 4))); + default: + // For correctness' sake; if there does not exist an encoding which will contain a (byte)0 for a + // non-null character, then this branch can be merged with UTF8Encoding one. + var data = encoding.GetString(Cast(memoryAddress, maxLength)); + var eosPos = data.IndexOf('\0'); + return eosPos >= 0 ? data[..eosPos] : data; + } } /// @@ -260,11 +361,9 @@ public static unsafe class MemoryHelper /// /// The memory address to read from. /// The read in string. - public static SeString ReadSeStringNullTerminated(IntPtr memoryAddress) - { - var buffer = ReadRawNullTerminated(memoryAddress); - return SeString.Parse(buffer); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SeString ReadSeStringNullTerminated(nint memoryAddress) => + SeString.Parse(MemoryMarshal.CreateReadOnlySpanFromNullTerminated((byte*)memoryAddress)); /// /// Read an SeString from a specified memory address. @@ -272,40 +371,165 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The maximum length of the string. /// The read in string. - public static SeString ReadSeString(IntPtr memoryAddress, int maxLength) - { - ReadRaw(memoryAddress, maxLength, out var buffer); - - var eos = Array.IndexOf(buffer, (byte)0); - if (eos < 0) - { - return SeString.Parse(buffer); - } - else - { - var newBuffer = new byte[eos]; - Buffer.BlockCopy(buffer, 0, newBuffer, 0, eos); - return SeString.Parse(newBuffer); - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SeString ReadSeString(nint memoryAddress, int maxLength) => + // Note that a valid SeString never contains a null character, other than for the sequence terminator purpose. + SeString.Parse(CastNullTerminated(memoryAddress, maxLength)); /// /// Read an SeString from a specified Utf8String structure. /// /// The memory address to read from. /// The read in string. - public static unsafe SeString ReadSeString(Utf8String* utf8String) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SeString ReadSeString(Utf8String* utf8String) => + utf8String == null ? string.Empty : SeString.Parse(utf8String->AsSpan()); + + /// + /// Reads an SeString from a specified memory address, and extracts the outermost string.
+ /// If the SeString is malformed, behavior is undefined. + ///
+ /// Whether the SeString contained a non-represented payload. + /// The memory address to read from. + /// The maximum length of the string. + /// Stop reading on encountering the first non-represented payload. + /// What payloads are represented via this function may change. + /// Replacement for non-represented payloads. + /// The read in string. + public static string ReadSeStringAsString( + out bool containsNonRepresentedPayload, + nint memoryAddress, + int maxLength = int.MaxValue, + bool stopOnFirstNonRepresentedPayload = false, + string nonRepresentedPayloadReplacement = "*") { - if (utf8String == null) - return string.Empty; + var sb = StringBuilderPool.Get(); + sb.EnsureCapacity(maxLength = CastNullTerminated(memoryAddress, maxLength).Length); - var ptr = utf8String->StringPtr; - if (ptr == null) - return string.Empty; + // 1 utf-8 codepoint can spill up to 2 characters. + Span tmp = stackalloc char[2]; - var len = Math.Max(utf8String->BufUsed, utf8String->StringLength); + var pin = (byte*)memoryAddress; + containsNonRepresentedPayload = false; + while (*pin != 0 && maxLength > 0) + { + if (*pin != LSeString.StartByte) + { + var len = *pin switch + { + < 0x80 => 1, + >= 0b11000000 and <= 0b11011111 => 2, + >= 0b11100000 and <= 0b11101111 => 3, + >= 0b11110000 and <= 0b11110111 => 4, + _ => 0, + }; + if (len == 0 || len > maxLength) + break; - return ReadSeString((IntPtr)ptr, (int)len); + var numChars = Encoding.UTF8.GetChars(new(pin, len), tmp); + sb.Append(tmp[..numChars]); + pin += len; + maxLength -= len; + continue; + } + + // Start byte + ++pin; + --maxLength; + + // Payload type + var payloadType = (LPayloadType)(*pin++); + + // Payload length + if (!ReadIntExpression(ref pin, ref maxLength, out var expressionLength)) + break; + if (expressionLength > maxLength) + break; + pin += expressionLength; + maxLength -= unchecked((int)expressionLength); + + // End byte + if (*pin++ != LSeString.EndByte) + break; + --maxLength; + + switch (payloadType) + { + case LPayloadType.NewLine: + sb.AppendLine(); + break; + case LPayloadType.Hyphen: + sb.Append('–'); + break; + case LPayloadType.SoftHyphen: + sb.Append('\u00AD'); + break; + default: + sb.Append(nonRepresentedPayloadReplacement); + containsNonRepresentedPayload = true; + if (stopOnFirstNonRepresentedPayload) + maxLength = 0; + break; + } + } + + var res = sb.ToString(); + StringBuilderPool.Return(sb); + return res; + + static bool ReadIntExpression(ref byte* p, ref int maxLength, out uint value) + { + if (maxLength <= 0) + { + value = 0; + return false; + } + + var typeByte = *p++; + --maxLength; + + switch (typeByte) + { + case > 0 and < 0xD0: + value = (uint)typeByte - 1; + return true; + case >= 0xF0 and <= 0xFE: + ++typeByte; + value = 0u; + if ((typeByte & 8) != 0) + { + if (maxLength <= 0 || *p == 0) + return false; + value |= (uint)*p++ << 24; + } + + if ((typeByte & 4) != 0) + { + if (maxLength <= 0 || *p == 0) + return false; + value |= (uint)*p++ << 16; + } + + if ((typeByte & 2) != 0) + { + if (maxLength <= 0 || *p == 0) + return false; + value |= (uint)*p++ << 8; + } + + if ((typeByte & 1) != 0) + { + if (maxLength <= 0 || *p == 0) + return false; + value |= *p++; + } + + return true; + default: + value = 0; + return false; + } + } } #endregion @@ -320,7 +544,8 @@ public static unsafe class MemoryHelper /// /// The memory address to read from. /// The read in string. - public static void ReadStringNullTerminated(IntPtr memoryAddress, out string value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadStringNullTerminated(nint memoryAddress, out string value) => value = ReadStringNullTerminated(memoryAddress); /// @@ -332,7 +557,8 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The encoding to use to decode the string. /// The read in string. - public static void ReadStringNullTerminated(IntPtr memoryAddress, Encoding encoding, out string value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadStringNullTerminated(nint memoryAddress, Encoding encoding, out string value) => value = ReadStringNullTerminated(memoryAddress, encoding); /// @@ -344,7 +570,8 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The read in string. /// The maximum length of the string. - public static void ReadString(IntPtr memoryAddress, out string value, int maxLength) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadString(nint memoryAddress, out string value, int maxLength) => value = ReadString(memoryAddress, maxLength); /// @@ -357,7 +584,8 @@ public static unsafe class MemoryHelper /// The encoding to use to decode the string. /// The maximum length of the string. /// The read in string. - public static void ReadString(IntPtr memoryAddress, Encoding encoding, int maxLength, out string value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadString(nint memoryAddress, Encoding encoding, int maxLength, out string value) => value = ReadString(memoryAddress, encoding, maxLength); /// @@ -365,7 +593,8 @@ public static unsafe class MemoryHelper /// /// The memory address to read from. /// The read in SeString. - public static void ReadSeStringNullTerminated(IntPtr memoryAddress, out SeString value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadSeStringNullTerminated(nint memoryAddress, out SeString value) => value = ReadSeStringNullTerminated(memoryAddress); /// @@ -374,7 +603,8 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The maximum length of the string. /// The read in SeString. - public static void ReadSeString(IntPtr memoryAddress, int maxLength, out SeString value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadSeString(nint memoryAddress, int maxLength, out SeString value) => value = ReadSeString(memoryAddress, maxLength); /// @@ -382,6 +612,7 @@ public static unsafe class MemoryHelper /// /// The memory address to read from. /// The read in string. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe void ReadSeString(Utf8String* utf8String, out SeString value) => value = ReadSeString(utf8String); @@ -395,7 +626,8 @@ public static unsafe class MemoryHelper /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. /// The memory address to read from. /// The item to write to the address. - public static void Write(IntPtr memoryAddress, T item) where T : unmanaged + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(nint memoryAddress, T item) where T : unmanaged => Write(memoryAddress, item, false); /// @@ -405,7 +637,7 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The item to write to the address. /// Set this to true to enable struct marshalling. - public static void Write(IntPtr memoryAddress, T item, bool marshal) + public static void Write(nint memoryAddress, T item, bool marshal) { if (marshal) Marshal.StructureToPtr(item, memoryAddress, false); @@ -418,10 +650,8 @@ public static unsafe class MemoryHelper /// /// The memory address to read from. /// The bytes to write to memoryAddress. - public static void WriteRaw(IntPtr memoryAddress, byte[] data) - { - Marshal.Copy(data, 0, memoryAddress, data.Length); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteRaw(nint memoryAddress, byte[] data) => Marshal.Copy(data, 0, memoryAddress, data.Length); /// /// Writes a generic type array to a specified memory address. @@ -429,7 +659,8 @@ public static unsafe class MemoryHelper /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. /// The memory address to write to. /// The array of items to write to the address. - public static void Write(IntPtr memoryAddress, T[] items) where T : unmanaged + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(nint memoryAddress, T[] items) where T : unmanaged => Write(memoryAddress, items, false); /// @@ -439,7 +670,8 @@ public static unsafe class MemoryHelper /// The memory address to write to. /// The array of items to write to the address. /// Set this to true to enable struct marshalling. - public static void Write(IntPtr memoryAddress, T[] items, bool marshal) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Write(nint memoryAddress, T[] items, bool marshal) { var structSize = SizeOf(marshal); @@ -462,7 +694,8 @@ public static unsafe class MemoryHelper /// /// The memory address to write to. /// The string to write. - public static void WriteString(IntPtr memoryAddress, string value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteString(nint memoryAddress, string? value) => WriteString(memoryAddress, value, Encoding.UTF8); /// @@ -474,14 +707,12 @@ public static unsafe class MemoryHelper /// The memory address to write to. /// The string to write. /// The encoding to use. - public static void WriteString(IntPtr memoryAddress, string value, Encoding encoding) + public static void WriteString(nint memoryAddress, string? value, Encoding encoding) { - if (string.IsNullOrEmpty(value)) - return; - - var bytes = encoding.GetBytes(value + '\0'); - - WriteRaw(memoryAddress, bytes); + var ptr = 0; + if (value is not null) + ptr = encoding.GetBytes(value, Cast(memoryAddress, encoding.GetMaxByteCount(value.Length))); + encoding.GetBytes("\0", Cast(memoryAddress + ptr, 4)); } /// @@ -489,7 +720,8 @@ public static unsafe class MemoryHelper /// /// The memory address to write to. /// The SeString to write. - public static void WriteSeString(IntPtr memoryAddress, SeString value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteSeString(nint memoryAddress, SeString? value) { if (value is null) return; @@ -507,15 +739,16 @@ public static unsafe class MemoryHelper /// /// Amount of bytes to be allocated. /// Address to the newly allocated memory. - public static IntPtr Allocate(int length) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint Allocate(int length) { var address = VirtualAlloc( - IntPtr.Zero, - (UIntPtr)length, + nint.Zero, + (nuint)length, AllocationType.Commit | AllocationType.Reserve, MemoryProtection.ExecuteReadWrite); - if (address == IntPtr.Zero) + if (address == nint.Zero) throw new MemoryAllocationException($"Unable to allocate {length} bytes."); return address; @@ -527,7 +760,8 @@ public static unsafe class MemoryHelper /// /// Amount of bytes to be allocated. /// Address to the newly allocated memory. - public static void Allocate(int length, out IntPtr memoryAddress) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Allocate(int length, out nint memoryAddress) => memoryAddress = Allocate(length); /// @@ -535,9 +769,10 @@ public static unsafe class MemoryHelper /// /// The address of the memory to free. /// True if the operation is successful. - public static bool Free(IntPtr memoryAddress) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Free(nint memoryAddress) { - return VirtualFree(memoryAddress, UIntPtr.Zero, AllocationType.Release); + return VirtualFree(memoryAddress, nuint.Zero, AllocationType.Release); } /// @@ -547,9 +782,9 @@ public static unsafe class MemoryHelper /// The region size for which to change permissions for. /// The new permissions to set. /// The old page permissions. - public static MemoryProtection ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions) + public static MemoryProtection ChangePermission(nint memoryAddress, int length, MemoryProtection newPermissions) { - var result = VirtualProtect(memoryAddress, (UIntPtr)length, newPermissions, out var oldPermissions); + var result = VirtualProtect(memoryAddress, (nuint)length, newPermissions, out var oldPermissions); if (!result) throw new MemoryPermissionException($"Unable to change permissions at 0x{memoryAddress.ToInt64():X} of length {length} and permission {newPermissions} (result={result})"); @@ -568,7 +803,9 @@ public static unsafe class MemoryHelper /// The region size for which to change permissions for. /// The new permissions to set. /// The old page permissions. - public static void ChangePermission(IntPtr memoryAddress, int length, MemoryProtection newPermissions, out MemoryProtection oldPermissions) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ChangePermission( + nint memoryAddress, int length, MemoryProtection newPermissions, out MemoryProtection oldPermissions) => oldPermissions = ChangePermission(memoryAddress, length, newPermissions); /// @@ -580,7 +817,9 @@ public static unsafe class MemoryHelper /// The new permissions to set. /// Set to true to calculate the size of the struct after marshalling instead of before. /// The old page permissions. - public static MemoryProtection ChangePermission(IntPtr memoryAddress, ref T baseElement, MemoryProtection newPermissions, bool marshal) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static MemoryProtection ChangePermission( + nint memoryAddress, ref T baseElement, MemoryProtection newPermissions, bool marshal) => ChangePermission(memoryAddress, SizeOf(marshal), newPermissions); /// @@ -590,7 +829,8 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The amount of bytes to read starting from the memoryAddress. /// The read in bytes. - public static byte[] ReadProcessMemory(IntPtr memoryAddress, int length) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] ReadProcessMemory(nint memoryAddress, int length) { var value = new byte[length]; ReadProcessMemory(memoryAddress, ref value); @@ -604,7 +844,8 @@ public static unsafe class MemoryHelper /// The memory address to read from. /// The amount of bytes to read starting from the memoryAddress. /// The read in bytes. - public static void ReadProcessMemory(IntPtr memoryAddress, int length, out byte[] value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ReadProcessMemory(nint memoryAddress, int length, out byte[] value) => value = ReadProcessMemory(memoryAddress, length); /// @@ -613,12 +854,12 @@ public static unsafe class MemoryHelper /// /// The memory address to read from. /// The read in bytes. - public static void ReadProcessMemory(IntPtr memoryAddress, ref byte[] value) + public static void ReadProcessMemory(nint memoryAddress, ref byte[] value) { unchecked { var length = value.Length; - var result = NativeFunctions.ReadProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, value, length, out _); + var result = NativeFunctions.ReadProcessMemory((nint)0xFFFFFFFF, memoryAddress, value, length, out _); if (!result) throw new MemoryReadException($"Unable to read memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})"); @@ -635,12 +876,12 @@ public static unsafe class MemoryHelper /// /// The memory address to write to. /// The bytes to write to memoryAddress. - public static void WriteProcessMemory(IntPtr memoryAddress, byte[] data) + public static void WriteProcessMemory(nint memoryAddress, byte[] data) { unchecked { var length = data.Length; - var result = NativeFunctions.WriteProcessMemory((IntPtr)0xFFFFFFFF, memoryAddress, data, length, out _); + var result = NativeFunctions.WriteProcessMemory((nint)0xFFFFFFFF, memoryAddress, data, length, out _); if (!result) throw new MemoryWriteException($"Unable to write memory at 0x{memoryAddress.ToInt64():X} of length {length} (result={result})"); @@ -660,6 +901,7 @@ public static unsafe class MemoryHelper /// /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. /// The size of the primitive or struct. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int SizeOf() => SizeOf(false); @@ -669,6 +911,7 @@ public static unsafe class MemoryHelper /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. /// If set to true; will return the size of an element after marshalling. /// The size of the primitive or struct. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int SizeOf(bool marshal) => marshal ? Marshal.SizeOf() : Unsafe.SizeOf(); @@ -678,6 +921,7 @@ public static unsafe class MemoryHelper /// An individual struct type of a class with an explicit StructLayout.LayoutKind attribute. /// The number of array elements present. /// The size of the primitive or struct array. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int SizeOf(int elementCount) where T : unmanaged => SizeOf() * elementCount; @@ -688,6 +932,7 @@ public static unsafe class MemoryHelper /// The number of array elements present. /// If set to true; will return the size of an element after marshalling. /// The size of the primitive or struct array. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int SizeOf(int elementCount, bool marshal) => SizeOf(marshal) * elementCount; @@ -701,9 +946,10 @@ public static unsafe class MemoryHelper /// Amount of bytes to allocate. /// The alignment of the allocation. /// Pointer to the allocated region. - public static IntPtr GameAllocateUi(ulong size, ulong alignment = 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint GameAllocateUi(ulong size, ulong alignment = 0) { - return new IntPtr(IMemorySpace.GetUISpace()->Malloc(size, alignment)); + return new nint(IMemorySpace.GetUISpace()->Malloc(size, alignment)); } /// @@ -712,9 +958,10 @@ public static unsafe class MemoryHelper /// Amount of bytes to allocate. /// The alignment of the allocation. /// Pointer to the allocated region. - public static IntPtr GameAllocateDefault(ulong size, ulong alignment = 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint GameAllocateDefault(ulong size, ulong alignment = 0) { - return new IntPtr(IMemorySpace.GetDefaultSpace()->Malloc(size, alignment)); + return new nint(IMemorySpace.GetDefaultSpace()->Malloc(size, alignment)); } /// @@ -723,9 +970,10 @@ public static unsafe class MemoryHelper /// Amount of bytes to allocate. /// The alignment of the allocation. /// Pointer to the allocated region. - public static IntPtr GameAllocateAnimation(ulong size, ulong alignment = 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint GameAllocateAnimation(ulong size, ulong alignment = 0) { - return new IntPtr(IMemorySpace.GetAnimationSpace()->Malloc(size, alignment)); + return new nint(IMemorySpace.GetAnimationSpace()->Malloc(size, alignment)); } /// @@ -734,9 +982,10 @@ public static unsafe class MemoryHelper /// Amount of bytes to allocate. /// The alignment of the allocation. /// Pointer to the allocated region. - public static IntPtr GameAllocateApricot(ulong size, ulong alignment = 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint GameAllocateApricot(ulong size, ulong alignment = 0) { - return new IntPtr(IMemorySpace.GetApricotSpace()->Malloc(size, alignment)); + return new nint(IMemorySpace.GetApricotSpace()->Malloc(size, alignment)); } /// @@ -745,9 +994,10 @@ public static unsafe class MemoryHelper /// Amount of bytes to allocate. /// The alignment of the allocation. /// Pointer to the allocated region. - public static IntPtr GameAllocateSound(ulong size, ulong alignment = 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static nint GameAllocateSound(ulong size, ulong alignment = 0) { - return new IntPtr(IMemorySpace.GetSoundSpace()->Malloc(size, alignment)); + return new nint(IMemorySpace.GetSoundSpace()->Malloc(size, alignment)); } /// @@ -756,15 +1006,15 @@ public static unsafe class MemoryHelper /// The memory you are freeing must be allocated with game allocators. /// Position at which the memory to be freed is located. /// Amount of bytes to free. - public static void GameFree(ref IntPtr ptr, ulong size) + public static void GameFree(ref nint ptr, ulong size) { - if (ptr == IntPtr.Zero) + if (ptr == nint.Zero) { return; } IMemorySpace.Free((void*)ptr, size); - ptr = IntPtr.Zero; + ptr = nint.Zero; } #endregion