refactoring
This commit is contained in:
parent
fbd0ba445a
commit
18d8721dd5
68 changed files with 2187 additions and 36 deletions
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue