poe2-bot/src/Roboto.Memory/MsvcStringReader.cs
2026-03-02 16:23:23 -05:00

110 lines
3.4 KiB
C#

using System.Text;
namespace Roboto.Memory;
/// <summary>
/// Reads MSVC std::string and std::wstring from process memory.
/// Handles SSO (Small String Optimization) for both narrow and wide strings.
/// </summary>
public sealed class MsvcStringReader
{
private readonly MemoryContext _ctx;
public MsvcStringReader(MemoryContext ctx)
{
_ctx = ctx;
}
/// <summary>
/// Reads an MSVC std::wstring (UTF-16) from the given address.
/// Layout: _Bx (16 bytes: SSO buffer or heap ptr), _Mysize (8), _Myres (8).
/// wchar_t is 2 bytes on Windows. SSO threshold: capacity &lt;= 7.
/// </summary>
public string? ReadMsvcWString(nint stringAddr)
{
var mem = _ctx.Memory;
var size = mem.Read<long>(stringAddr + 0x10);
var capacity = mem.Read<long>(stringAddr + 0x18);
if (size <= 0 || size > 512 || capacity < size) return null;
nint dataAddr;
if (capacity <= 7)
dataAddr = stringAddr; // SSO: inline in _Bx buffer
else
{
dataAddr = mem.ReadPointer(stringAddr);
if (dataAddr == 0) return null;
}
var bytes = mem.ReadBytes(dataAddr, (int)size * 2);
if (bytes is null) return null;
var str = Encoding.Unicode.GetString(bytes);
if (str.Length > 0 && str[0] >= 0x20 && str[0] <= 0x7E)
return str;
return null;
}
/// <summary>
/// Reads an MSVC std::string (narrow, UTF-8/ASCII) from the given address.
/// Layout: _Bx (16 bytes: SSO buffer or heap ptr), _Mysize (8), _Myres (8).
/// SSO threshold: capacity &lt;= 15.
/// </summary>
public string? ReadMsvcString(nint stringAddr)
{
var mem = _ctx.Memory;
var size = mem.Read<long>(stringAddr + 0x10);
var capacity = mem.Read<long>(stringAddr + 0x18);
if (size <= 0 || size > 512 || capacity < size) return null;
nint dataAddr;
if (capacity <= 15)
dataAddr = stringAddr; // SSO: inline in _Bx buffer
else
{
dataAddr = mem.ReadPointer(stringAddr);
if (dataAddr == 0) return null;
}
var bytes = mem.ReadBytes(dataAddr, (int)size);
if (bytes is null) return null;
var str = Encoding.UTF8.GetString(bytes);
if (str.Length > 0 && str[0] >= 0x20 && str[0] <= 0x7E)
return str;
return null;
}
/// <summary>
/// Reads a null-terminated char* string from a module-range or heap address.
/// Component names are char* literals in .rdata, e.g. "Life", "Render", "Monster".
/// </summary>
public string? ReadCharPtr(nint ptr)
{
if (ptr == 0) return null;
var data = _ctx.Memory.ReadBytes(ptr, 64);
if (data is null) return null;
var end = Array.IndexOf(data, (byte)0);
if (end < 1 || end > 50) return null;
var str = Encoding.ASCII.GetString(data, 0, end);
if (str.Length > 0 && str.All(c => c >= 0x20 && c <= 0x7E))
return str;
return null;
}
/// <summary>
/// Reads a null-terminated UTF-8 string (up to 256 bytes).
/// </summary>
public string ReadNullTermString(nint addr)
{
var data = _ctx.Memory.ReadBytes(addr, 256);
if (data is null) return "Error: read failed";
var end = Array.IndexOf(data, (byte)0);
if (end < 0) end = data.Length;
return Encoding.UTF8.GetString(data, 0, end);
}
}