refactoring

This commit is contained in:
Boki 2026-03-02 16:23:23 -05:00
parent fbd0ba445a
commit 18d8721dd5
68 changed files with 2187 additions and 36 deletions

View file

@ -1,272 +0,0 @@
using System.Numerics;
using Serilog;
namespace Automata.Memory;
public class GameMemoryReader : IDisposable
{
// ExileCore state slot names (index → name)
public static readonly string[] StateNames =
[
"AreaLoading", // 0
"Waiting", // 1
"Credits", // 2
"Escape", // 3
"InGame", // 4
"ChangePassword", // 5
"Login", // 6
"PreGame", // 7
"CreateChar", // 8
"SelectChar", // 9
"DeleteChar", // 10
"Loading", // 11
];
private readonly GameOffsets _offsets;
private readonly ObjectRegistry _registry;
private bool _disposed;
// Sub-readers (created on Attach)
private MemoryContext? _ctx;
private GameStateReader? _stateReader;
private ComponentReader? _components;
private EntityReader? _entities;
private TerrainReader? _terrain;
private MsvcStringReader? _strings;
private RttiResolver? _rtti;
public ObjectRegistry Registry => _registry;
public MemoryDiagnostics? Diagnostics { get; private set; }
public GameMemoryReader()
{
_offsets = GameOffsets.Load("offsets.json");
_registry = new ObjectRegistry();
}
public bool IsAttached => _ctx != null;
public bool Attach()
{
Detach();
var memory = ProcessMemory.Attach(_offsets.ProcessName);
if (memory is null)
return false;
_ctx = new MemoryContext(memory, _offsets, _registry);
var module = memory.GetMainModule();
if (module is not null)
{
_ctx.ModuleBase = module.Value.Base;
_ctx.ModuleSize = module.Value.Size;
}
// Try pattern scan first
if (!string.IsNullOrWhiteSpace(_offsets.GameStatePattern))
{
var scanner = new PatternScanner(memory);
_ctx.GameStateBase = scanner.FindPatternRip(_offsets.GameStatePattern);
if (_ctx.GameStateBase != 0)
{
_ctx.GameStateBase += _offsets.PatternResultAdjust;
Log.Information("GameState base (pattern+adjust): 0x{Address:X}", _ctx.GameStateBase);
}
}
// Fallback: manual offset from module base
if (_ctx.GameStateBase == 0 && _offsets.GameStateGlobalOffset > 0)
{
_ctx.GameStateBase = _ctx.ModuleBase + _offsets.GameStateGlobalOffset;
Log.Information("GameState base (manual): 0x{Address:X}", _ctx.GameStateBase);
}
// Create sub-readers
_strings = new MsvcStringReader(_ctx);
_rtti = new RttiResolver(_ctx);
_stateReader = new GameStateReader(_ctx);
_components = new ComponentReader(_ctx, _strings);
_entities = new EntityReader(_ctx, _components, _strings);
_terrain = new TerrainReader(_ctx);
Diagnostics = new MemoryDiagnostics(_ctx, _stateReader, _components, _entities, _strings, _rtti);
return true;
}
public void Detach()
{
_ctx?.Memory.Dispose();
_ctx = null;
_stateReader = null;
_components = null;
_entities = null;
_terrain = null;
_strings = null;
_rtti = null;
Diagnostics = null;
}
public GameStateSnapshot ReadSnapshot()
{
var snap = new GameStateSnapshot();
if (_ctx is null)
{
snap.Error = "Not attached";
return snap;
}
var mem = _ctx.Memory;
var offsets = _ctx.Offsets;
snap.Attached = true;
snap.ProcessId = mem.ProcessId;
snap.ModuleBase = _ctx.ModuleBase;
snap.ModuleSize = _ctx.ModuleSize;
snap.OffsetsConfigured = _ctx.GameStateBase != 0;
snap.GameStateBase = _ctx.GameStateBase;
if (_ctx.GameStateBase == 0)
return snap;
// Static area level — direct module offset, always reliable
if (offsets.AreaLevelStaticOffset > 0 && _ctx.ModuleBase != 0)
{
var level = mem.Read<int>(_ctx.ModuleBase + offsets.AreaLevelStaticOffset);
if (level > 0 && level < 200)
snap.AreaLevel = level;
}
try
{
// Resolve InGameState from controller
var inGameState = _stateReader!.ResolveInGameState(snap);
if (inGameState == 0)
return snap;
snap.InGameStatePtr = inGameState;
// Read all state slot pointers
_stateReader.ReadStateSlots(snap);
// InGameState → AreaInstance
var ingameData = mem.ReadPointer(inGameState + offsets.IngameDataFromStateOffset);
snap.AreaInstancePtr = ingameData;
if (ingameData != 0)
{
// Area level
if (offsets.AreaLevelIsByte)
{
var level = mem.Read<byte>(ingameData + offsets.AreaLevelOffset);
if (level > 0 && level < 200)
snap.AreaLevel = level;
}
else
{
var level = mem.Read<int>(ingameData + offsets.AreaLevelOffset);
if (level > 0 && level < 200)
snap.AreaLevel = level;
}
// Area hash
snap.AreaHash = mem.Read<uint>(ingameData + offsets.AreaHashOffset);
// ServerData pointer
var serverData = mem.ReadPointer(ingameData + offsets.ServerDataOffset);
snap.ServerDataPtr = serverData;
// LocalPlayer — try direct offset first, fallback to ServerData chain
if (offsets.LocalPlayerDirectOffset > 0)
snap.LocalPlayerPtr = mem.ReadPointer(ingameData + offsets.LocalPlayerDirectOffset);
if (snap.LocalPlayerPtr == 0 && serverData != 0)
snap.LocalPlayerPtr = mem.ReadPointer(serverData + offsets.LocalPlayerOffset);
// Entity count and list
var entityCount = (int)mem.Read<long>(ingameData + offsets.EntityListOffset + offsets.EntityCountInternalOffset);
if (entityCount > 0 && entityCount < 50000)
{
snap.EntityCount = entityCount;
_entities!.ReadEntities(snap, ingameData);
}
// Player vitals & position — ECS
if (snap.LocalPlayerPtr != 0)
{
// Invalidate caches if LocalPlayer entity changed (zone change)
if (snap.LocalPlayerPtr != _components!.LastLocalPlayer)
_terrain!.InvalidateCache();
_components.InvalidateCaches(snap.LocalPlayerPtr);
_components.ReadPlayerVitals(snap);
_components.ReadPlayerPosition(snap);
}
// Camera matrix
ReadCameraMatrix(snap, inGameState);
// Loading and escape state
_stateReader.ReadIsLoading(snap);
_stateReader.ReadEscapeState(snap);
// Read state flag bytes
if (snap.InGameStatePtr != 0)
snap.StateFlagBytes = mem.ReadBytes(snap.InGameStatePtr + snap.StateFlagBaseOffset, 0x30);
// Terrain
_terrain!.ReadTerrain(snap, ingameData);
}
}
catch (Exception ex)
{
Log.Debug(ex, "Error reading snapshot");
}
// Update edge detection for next tick
_terrain!.UpdateLoadingEdge(snap.IsLoading);
return snap;
}
private void ReadCameraMatrix(GameStateSnapshot snap, nint inGameState)
{
var mem = _ctx!.Memory;
var offsets = _ctx.Offsets;
if (offsets.CameraMatrixOffset <= 0) return;
// If CameraOffset > 0: follow pointer from InGameState, then read matrix
// If CameraOffset == 0: matrix is inline in InGameState at CameraMatrixOffset
nint matrixAddr;
if (offsets.CameraOffset > 0)
{
var cam = mem.ReadPointer(inGameState + offsets.CameraOffset);
if (cam == 0) return;
matrixAddr = cam + offsets.CameraMatrixOffset;
}
else
{
matrixAddr = inGameState + offsets.CameraMatrixOffset;
}
// Read 64-byte Matrix4x4 as 16 floats
var bytes = mem.ReadBytes(matrixAddr, 64);
if (bytes is null || bytes.Length < 64) return;
var m = new Matrix4x4(
BitConverter.ToSingle(bytes, 0), BitConverter.ToSingle(bytes, 4), BitConverter.ToSingle(bytes, 8), BitConverter.ToSingle(bytes, 12),
BitConverter.ToSingle(bytes, 16), BitConverter.ToSingle(bytes, 20), BitConverter.ToSingle(bytes, 24), BitConverter.ToSingle(bytes, 28),
BitConverter.ToSingle(bytes, 32), BitConverter.ToSingle(bytes, 36), BitConverter.ToSingle(bytes, 40), BitConverter.ToSingle(bytes, 44),
BitConverter.ToSingle(bytes, 48), BitConverter.ToSingle(bytes, 52), BitConverter.ToSingle(bytes, 56), BitConverter.ToSingle(bytes, 60));
// Quick sanity check
if (float.IsNaN(m.M11) || float.IsInfinity(m.M11)) return;
snap.CameraMatrix = m;
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
Detach();
}
}