lots done
This commit is contained in:
parent
1ba7c39c30
commit
fbd0ba445a
59 changed files with 6074 additions and 3598 deletions
200
src/Automata.Memory/EntityReader.cs
Normal file
200
src/Automata.Memory/EntityReader.cs
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
using Serilog;
|
||||
|
||||
namespace Automata.Memory;
|
||||
|
||||
/// <summary>
|
||||
/// Reads entity list from AreaInstance's std::map red-black tree.
|
||||
/// </summary>
|
||||
public sealed class EntityReader
|
||||
{
|
||||
private readonly MemoryContext _ctx;
|
||||
private readonly ComponentReader _components;
|
||||
private readonly MsvcStringReader _strings;
|
||||
|
||||
public EntityReader(MemoryContext ctx, ComponentReader components, MsvcStringReader strings)
|
||||
{
|
||||
_ctx = ctx;
|
||||
_components = components;
|
||||
_strings = strings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads entity list into the snapshot for continuous display.
|
||||
/// </summary>
|
||||
public void ReadEntities(GameStateSnapshot snap, nint areaInstance)
|
||||
{
|
||||
var mem = _ctx.Memory;
|
||||
var offsets = _ctx.Offsets;
|
||||
var registry = _ctx.Registry;
|
||||
|
||||
var sentinel = mem.ReadPointer(areaInstance + offsets.EntityListOffset);
|
||||
if (sentinel == 0) return;
|
||||
|
||||
var root = mem.ReadPointer(sentinel + offsets.EntityNodeParentOffset);
|
||||
var entities = new List<Entity>();
|
||||
var maxNodes = Math.Min(snap.EntityCount + 10, 500);
|
||||
var hasComponentLookup = offsets.ComponentLookupEntrySize > 0;
|
||||
var dirty = false;
|
||||
|
||||
WalkTreeInOrder(sentinel, root, maxNodes, node =>
|
||||
{
|
||||
var entityPtr = mem.ReadPointer(node + offsets.EntityNodeValueOffset);
|
||||
if (entityPtr == 0) return;
|
||||
|
||||
var high = (ulong)entityPtr >> 32;
|
||||
if (high == 0 || high >= 0x7FFF || (entityPtr & 0x3) != 0) return;
|
||||
|
||||
var entityId = mem.Read<uint>(entityPtr + offsets.EntityIdOffset);
|
||||
var path = TryReadEntityPath(entityPtr);
|
||||
|
||||
var entity = new Entity(entityPtr, entityId, path);
|
||||
|
||||
if (registry["entities"].Register(entity.Metadata))
|
||||
dirty = true;
|
||||
|
||||
if (TryReadEntityPosition(entityPtr, out var x, out var y, out var z))
|
||||
{
|
||||
entity.HasPosition = true;
|
||||
entity.X = x;
|
||||
entity.Y = y;
|
||||
entity.Z = z;
|
||||
}
|
||||
|
||||
// Read component names for non-trivial entities
|
||||
if (hasComponentLookup &&
|
||||
entity.Type != EntityType.Effect &&
|
||||
entity.Type != EntityType.Terrain &&
|
||||
entity.Type != EntityType.Critter)
|
||||
{
|
||||
var lookup = _components.ReadComponentLookup(entityPtr);
|
||||
if (lookup is not null)
|
||||
{
|
||||
entity.Components = new HashSet<string>(lookup.Keys);
|
||||
entity.ReclassifyFromComponents();
|
||||
|
||||
if (registry["components"].Register(lookup.Keys))
|
||||
dirty = true;
|
||||
|
||||
// Read HP for monsters to determine alive/dead
|
||||
if (entity.Type == EntityType.Monster && lookup.TryGetValue("Life", out var lifeIdx))
|
||||
{
|
||||
var (compFirst, compCount) = _components.FindComponentList(entityPtr);
|
||||
if (lifeIdx >= 0 && lifeIdx < compCount)
|
||||
{
|
||||
var lifeComp = mem.ReadPointer(compFirst + lifeIdx * 8);
|
||||
if (lifeComp != 0)
|
||||
{
|
||||
var hp = mem.Read<int>(lifeComp + offsets.LifeHealthOffset + offsets.VitalCurrentOffset);
|
||||
var hpMax = mem.Read<int>(lifeComp + offsets.LifeHealthOffset + offsets.VitalTotalOffset);
|
||||
if (hpMax > 0 && hpMax < 200000 && hp >= 0 && hp <= hpMax + 1000)
|
||||
{
|
||||
entity.HasVitals = true;
|
||||
entity.LifeCurrent = hp;
|
||||
entity.LifeTotal = hpMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entities.Add(entity);
|
||||
});
|
||||
|
||||
if (dirty)
|
||||
registry.Flush();
|
||||
|
||||
snap.Entities = entities;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterative in-order traversal of an MSVC std::map red-black tree.
|
||||
/// </summary>
|
||||
public void WalkTreeInOrder(nint sentinel, nint root, int maxNodes, Action<nint> visitor)
|
||||
{
|
||||
if (root == 0 || root == sentinel) return;
|
||||
|
||||
var offsets = _ctx.Offsets;
|
||||
var mem = _ctx.Memory;
|
||||
var stack = new Stack<nint>();
|
||||
var current = root;
|
||||
var count = 0;
|
||||
var visited = new HashSet<nint> { sentinel };
|
||||
|
||||
while ((current != sentinel && current != 0) || stack.Count > 0)
|
||||
{
|
||||
while (current != sentinel && current != 0)
|
||||
{
|
||||
if (!visited.Add(current))
|
||||
{
|
||||
current = sentinel;
|
||||
break;
|
||||
}
|
||||
stack.Push(current);
|
||||
current = mem.ReadPointer(current + offsets.EntityNodeLeftOffset);
|
||||
}
|
||||
|
||||
if (stack.Count == 0) break;
|
||||
|
||||
current = stack.Pop();
|
||||
visitor(current);
|
||||
count++;
|
||||
if (count >= maxNodes) break;
|
||||
|
||||
current = mem.ReadPointer(current + offsets.EntityNodeRightOffset);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads entity path string via EntityDetailsPtr → std::wstring.
|
||||
/// </summary>
|
||||
public string? TryReadEntityPath(nint entity)
|
||||
{
|
||||
var mem = _ctx.Memory;
|
||||
var offsets = _ctx.Offsets;
|
||||
|
||||
var detailsPtr = mem.ReadPointer(entity + offsets.EntityDetailsOffset);
|
||||
if (detailsPtr == 0) return null;
|
||||
|
||||
var high = (ulong)detailsPtr >> 32;
|
||||
if (high == 0 || high >= 0x7FFF) return null;
|
||||
|
||||
return _strings.ReadMsvcWString(detailsPtr + offsets.EntityPathStringOffset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to read position from an entity by scanning its component list for the Render component.
|
||||
/// </summary>
|
||||
public bool TryReadEntityPosition(nint entity, out float x, out float y, out float z)
|
||||
{
|
||||
x = y = z = 0;
|
||||
var offsets = _ctx.Offsets;
|
||||
|
||||
var (compFirst, count) = _components.FindComponentList(entity);
|
||||
if (count <= 0) return false;
|
||||
|
||||
// If we know the Render component index, try it directly
|
||||
if (offsets.RenderComponentIndex >= 0 && offsets.RenderComponentIndex < count)
|
||||
{
|
||||
var renderComp = _ctx.Memory.ReadPointer(compFirst + offsets.RenderComponentIndex * 8);
|
||||
if (renderComp != 0 && _components.TryReadPositionRaw(renderComp, out x, out y, out z))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Scan components (limit to avoid performance issues with many entities)
|
||||
var scanLimit = Math.Min(count, 20);
|
||||
for (var i = 0; i < scanLimit; i++)
|
||||
{
|
||||
var compPtr = _ctx.Memory.ReadPointer(compFirst + i * 8);
|
||||
if (compPtr == 0) continue;
|
||||
var high = (ulong)compPtr >> 32;
|
||||
if (high == 0 || high >= 0x7FFF) continue;
|
||||
if ((compPtr & 0x3) != 0) continue;
|
||||
|
||||
if (_components.TryReadPositionRaw(compPtr, out x, out y, out z))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue