192 lines
10 KiB
C#
192 lines
10 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using Serilog;
|
|
|
|
namespace Automata.Memory;
|
|
|
|
public sealed class GameOffsets
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
WriteIndented = true,
|
|
DefaultIgnoreCondition = JsonIgnoreCondition.Never
|
|
};
|
|
|
|
public string ProcessName { get; set; } = "PathOfExileSteam";
|
|
|
|
/// <summary>Pattern to find GameState global. Use ^ to mark the RIP displacement position.</summary>
|
|
public string GameStatePattern { get; set; } = "48 83 EC ?? 48 8B F1 33 ED 48 39 2D ^";
|
|
|
|
/// <summary>Fallback: manual offset from module base to GameState global (hex). Used when pattern is empty or fails.</summary>
|
|
public int GameStateGlobalOffset { get; set; }
|
|
|
|
/// <summary>Bytes to add to pattern scan result to reach the actual global.</summary>
|
|
public int PatternResultAdjust { get; set; } = 0x18;
|
|
|
|
// ── GameState → States ──
|
|
/// <summary>Offset to States begin/end pair in GameState (dump: 0x48).</summary>
|
|
public int StatesBeginOffset { get; set; } = 0x48;
|
|
/// <summary>Bytes per state entry (16 for inline slots, 8 for vector of pointers).</summary>
|
|
public int StateStride { get; set; } = 0x10;
|
|
/// <summary>Offset within each state entry to the actual state pointer.</summary>
|
|
public int StatePointerOffset { get; set; } = 0;
|
|
/// <summary>Total number of state slots (ExileCore: 12 states, State0-State11).</summary>
|
|
public int StateCount { get; set; } = 12;
|
|
/// <summary>Which state index is InGameState (typically 4).</summary>
|
|
public int InGameStateIndex { get; set; } = 4;
|
|
/// <summary>Offset from controller to active states vector begin/end pair (ExileCore: 0x20).</summary>
|
|
public int ActiveStatesOffset { get; set; } = 0x20;
|
|
/// <summary>Offset from controller to the active state pointer. When it != InGameState, we're loading. 0 = disabled.</summary>
|
|
public int IsLoadingOffset { get; set; } = 0;
|
|
/// <summary>If true, states are inline in the controller struct. If false, StatesBeginOffset points to a begin/end vector pair.</summary>
|
|
public bool StatesInline { get; set; } = true;
|
|
/// <summary>Direct offset from controller to InGameState pointer (bypasses state array). 0 = use state array instead. CE confirmed: 0x210.</summary>
|
|
public int InGameStateDirectOffset { get; set; } = 0x210;
|
|
|
|
// ── InGameState → sub-structures ──
|
|
/// <summary>InGameState → EscapeState int32 flag (0=closed, 1=open). Diff-scan confirmed: 0x20C. 0 = disabled.</summary>
|
|
public int EscapeStateOffset { get; set; } = 0x20C;
|
|
/// <summary>InGameState → AreaInstance (IngameData) pointer (dump: 0x298, CE confirmed: 0x290).</summary>
|
|
public int IngameDataFromStateOffset { get; set; } = 0x290;
|
|
/// <summary>InGameState → WorldData pointer (dump: 0x2F8).</summary>
|
|
public int WorldDataFromStateOffset { get; set; } = 0x2F8;
|
|
|
|
// ── AreaInstance (IngameData) → sub-structures ──
|
|
/// <summary>AreaInstance → CurrentAreaLevel (dump: byte at 0xAC, CE confirmed: byte at 0xC4).</summary>
|
|
public int AreaLevelOffset { get; set; } = 0xC4;
|
|
/// <summary>If true, AreaLevel is a byte. If false, read as int.</summary>
|
|
public bool AreaLevelIsByte { get; set; } = true;
|
|
/// <summary>Static offset from module base to cached area level int (CE: exe+3E84B78). 0 = disabled.</summary>
|
|
public int AreaLevelStaticOffset { get; set; } = 0;
|
|
/// <summary>AreaInstance → CurrentAreaHash uint (dump: 0xEC).</summary>
|
|
public int AreaHashOffset { get; set; } = 0xEC;
|
|
/// <summary>AreaInstance → ServerData pointer (dump: 0x9F0 via LocalPlayerStruct.ServerDataPtr).</summary>
|
|
public int ServerDataOffset { get; set; } = 0x9F0;
|
|
/// <summary>AreaInstance → LocalPlayer entity pointer (dump: 0x9F0+0x20 = 0xA10 via LocalPlayerStruct.LocalPlayerPtr).</summary>
|
|
public int LocalPlayerDirectOffset { get; set; } = 0xA10;
|
|
/// <summary>AreaInstance → EntityListStruct offset (dump: 0xAF8, CE confirmed: 0xB50).</summary>
|
|
public int EntityListOffset { get; set; } = 0xB50;
|
|
/// <summary>Offset within StdMap to _Mysize (entity count). MSVC std::map: head(8) + size(8).</summary>
|
|
public int EntityCountInternalOffset { get; set; } = 0x08;
|
|
|
|
// ── Entity list node layout (MSVC std::map red-black tree) ──
|
|
/// <summary>Tree node → left child pointer.</summary>
|
|
public int EntityNodeLeftOffset { get; set; } = 0x00;
|
|
/// <summary>Tree node → parent pointer.</summary>
|
|
public int EntityNodeParentOffset { get; set; } = 0x08;
|
|
/// <summary>Tree node → right child pointer.</summary>
|
|
public int EntityNodeRightOffset { get; set; } = 0x10;
|
|
/// <summary>Tree node → entity pointer (pair value at +0x28).</summary>
|
|
public int EntityNodeValueOffset { get; set; } = 0x28;
|
|
/// <summary>Entity → uint ID offset (EntityOffsets: +0x80).</summary>
|
|
public int EntityIdOffset { get; set; } = 0x80;
|
|
/// <summary>Entity → IsValid byte offset (EntityOffsets: +0x84).</summary>
|
|
public int EntityFlagsOffset { get; set; } = 0x84;
|
|
/// <summary>Entity → EntityDetailsPtr (Head/MainObject pointer, +0x08).</summary>
|
|
public int EntityDetailsOffset { get; set; } = 0x08;
|
|
/// <summary>EntityDetails → std::string path (MSVC layout). Offset within EntityDetails struct.</summary>
|
|
public int EntityPathStringOffset { get; set; } = 0x08;
|
|
|
|
// ServerData → fields
|
|
/// <summary>ServerData → LocalPlayer entity pointer (fallback if LocalPlayerDirectOffset is 0).</summary>
|
|
public int LocalPlayerOffset { get; set; } = 0x20;
|
|
|
|
// ── Entity / Component ──
|
|
public int ComponentListOffset { get; set; } = 0x10;
|
|
/// <summary>Entity → ObjectHeader pointer (for alternative component lookup via name→index map). ExileCore: 0x08.</summary>
|
|
public int EntityHeaderOffset { get; set; } = 0x08;
|
|
/// <summary>EntityDetails → ComponentLookup object pointer. Confirmed: 0x28.</summary>
|
|
public int ComponentLookupOffset { get; set; } = 0x28;
|
|
/// <summary>ComponentLookup object → Vec2 begin/end (name entry array).</summary>
|
|
public int ComponentLookupVec2Offset { get; set; } = 0x28;
|
|
/// <summary>Size of each entry in Vec2 (bytes). Confirmed: 16 = { char* name (8), int32 index (4), int32 flags (4) }.</summary>
|
|
public int ComponentLookupEntrySize { get; set; } = 16;
|
|
/// <summary>Offset to the char* name pointer within each lookup entry.</summary>
|
|
public int ComponentLookupNameOffset { get; set; } = 0;
|
|
/// <summary>Offset to the component index (int32) within each lookup entry.</summary>
|
|
public int ComponentLookupIndexOffset { get; set; } = 8;
|
|
/// <summary>Index of Life component in entity's component list. -1 = auto-discover via pattern scan.</summary>
|
|
public int LifeComponentIndex { get; set; } = -1;
|
|
/// <summary>Index of Render/Position component in entity's component list. -1 = unknown.</summary>
|
|
public int RenderComponentIndex { get; set; } = -1;
|
|
|
|
// ── Life component ──
|
|
/// <summary>First offset from AreaInstance to reach Life component (AreaInstance → ptr). 0 = use entity component list instead.</summary>
|
|
public int LifeComponentOffset1 { get; set; } = 0x420;
|
|
/// <summary>Second offset from intermediate pointer to Life component (ptr → Life).</summary>
|
|
public int LifeComponentOffset2 { get; set; } = 0x98;
|
|
public int LifeHealthOffset { get; set; } = 0x1A8;
|
|
public int LifeManaOffset { get; set; } = 0x1F8;
|
|
public int LifeEsOffset { get; set; } = 0x230;
|
|
public int VitalCurrentOffset { get; set; } = 0x30;
|
|
public int VitalTotalOffset { get; set; } = 0x2C;
|
|
|
|
// ── Render/Position component ──
|
|
public int PositionXOffset { get; set; } = 0x138;
|
|
public int PositionYOffset { get; set; } = 0x13C;
|
|
public int PositionZOffset { get; set; } = 0x140;
|
|
|
|
// ── Camera (for WorldToScreen projection) ──
|
|
/// <summary>Offset from InGameState to Camera pointer. 0 = disabled (use ScanCamera to discover).</summary>
|
|
public int CameraOffset { get; set; } = 0x308;
|
|
/// <summary>Offset within Camera struct to the Matrix4x4 (64 bytes). 0 = disabled.</summary>
|
|
public int CameraMatrixOffset { get; set; } = 0x1A0;
|
|
|
|
// ── Terrain (inline in AreaInstance) ──
|
|
/// <summary>Offset from AreaInstance to inline TerrainStruct (dump: 0xCC0).</summary>
|
|
public int TerrainListOffset { get; set; } = 0xCC0;
|
|
/// <summary>If true, terrain is inline in AreaInstance (no pointer dereference). If false, follow pointer.</summary>
|
|
public bool TerrainInline { get; set; } = true;
|
|
/// <summary>TerrainStruct → TotalTiles offset (scan: 0x90, StdTuple2D of long).</summary>
|
|
public int TerrainDimensionsOffset { get; set; } = 0x90;
|
|
/// <summary>TerrainStruct → GridWalkableData StdVector offset (scan: 0x148).</summary>
|
|
public int TerrainWalkableGridOffset { get; set; } = 0x148;
|
|
/// <summary>TerrainStruct → BytesPerRow (scan: 0x1A8).</summary>
|
|
public int TerrainBytesPerRowOffset { get; set; } = 0x1A8;
|
|
/// <summary>Kept for pointer-based terrain mode (TerrainInline=false).</summary>
|
|
public int TerrainGridPtrOffset { get; set; } = 0x08;
|
|
public int SubTilesPerCell { get; set; } = 23;
|
|
|
|
public static GameOffsets Load(string path)
|
|
{
|
|
if (!File.Exists(path))
|
|
{
|
|
Log.Information("Offsets file not found at '{Path}', using defaults", path);
|
|
var defaults = new GameOffsets();
|
|
defaults.Save(path);
|
|
return defaults;
|
|
}
|
|
|
|
try
|
|
{
|
|
var json = File.ReadAllText(path);
|
|
var offsets = JsonSerializer.Deserialize<GameOffsets>(json, JsonOptions);
|
|
if (offsets is null)
|
|
{
|
|
Log.Warning("Failed to deserialize '{Path}', using defaults", path);
|
|
return new GameOffsets();
|
|
}
|
|
Log.Information("Loaded offsets from '{Path}'", path);
|
|
return offsets;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Error loading offsets from '{Path}'", path);
|
|
return new GameOffsets();
|
|
}
|
|
}
|
|
|
|
public void Save(string path)
|
|
{
|
|
try
|
|
{
|
|
var json = JsonSerializer.Serialize(this, JsonOptions);
|
|
File.WriteAllText(path, json);
|
|
Log.Debug("Saved offsets to '{Path}'", path);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Error saving offsets to '{Path}'", path);
|
|
}
|
|
}
|
|
}
|