110 lines
3.4 KiB
C#
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 <= 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 <= 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);
|
|
}
|
|
}
|