using System.Text; namespace Roboto.Memory; /// /// Reads MSVC std::string and std::wstring from process memory. /// Handles SSO (Small String Optimization) for both narrow and wide strings. /// public sealed class MsvcStringReader { private readonly MemoryContext _ctx; public MsvcStringReader(MemoryContext ctx) { _ctx = ctx; } /// /// 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. /// public string? ReadMsvcWString(nint stringAddr) { var mem = _ctx.Memory; var size = mem.Read(stringAddr + 0x10); var capacity = mem.Read(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; } /// /// 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. /// public string? ReadMsvcString(nint stringAddr) { var mem = _ctx.Memory; var size = mem.Read(stringAddr + 0x10); var capacity = mem.Read(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; } /// /// 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". /// 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; } /// /// Reads a null-terminated UTF-8 string (up to 256 bytes). /// 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); } }