lots done

This commit is contained in:
Boki 2026-03-02 11:17:37 -05:00
parent 1ba7c39c30
commit fbd0ba445a
59 changed files with 6074 additions and 3598 deletions

View 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;
}
}