fixed stuff up

This commit is contained in:
Boki 2026-03-02 16:36:12 -05:00
parent 18d8721dd5
commit e5ebe05571
4 changed files with 69 additions and 63 deletions

View file

@ -1,4 +1,6 @@
using System.Text; using System.Text;
using Roboto.GameOffsets.Components;
using Roboto.GameOffsets.Natives;
using Serilog; using Serilog;
namespace Roboto.Memory; namespace Roboto.Memory;
@ -146,19 +148,21 @@ public sealed class ComponentReader
if (high == 0 || high >= 0x7FFF) continue; if (high == 0 || high >= 0x7FFF) continue;
if ((compPtr & 0x3) != 0) continue; if ((compPtr & 0x3) != 0) continue;
var hpTotal = mem.Read<int>(compPtr + offsets.LifeHealthOffset + offsets.VitalTotalOffset); var life = mem.Read<Life>(compPtr);
var hpTotal = life.Health.Total;
if (hpTotal < 20 || hpTotal > 200000) continue; if (hpTotal < 20 || hpTotal > 200000) continue;
var hpCurrent = mem.Read<int>(compPtr + offsets.LifeHealthOffset + offsets.VitalCurrentOffset); var hpCurrent = life.Health.Current;
if (hpCurrent < 0 || hpCurrent > hpTotal + 1000) continue; if (hpCurrent < 0 || hpCurrent > hpTotal + 1000) continue;
var manaTotal = mem.Read<int>(compPtr + offsets.LifeManaOffset + offsets.VitalTotalOffset); var manaTotal = life.Mana.Total;
if (manaTotal < 0 || manaTotal > 200000) continue; if (manaTotal < 0 || manaTotal > 200000) continue;
var manaCurrent = mem.Read<int>(compPtr + offsets.LifeManaOffset + offsets.VitalCurrentOffset); var manaCurrent = life.Mana.Current;
if (manaCurrent < 0 || manaCurrent > manaTotal + 1000) continue; if (manaCurrent < 0 || manaCurrent > manaTotal + 1000) continue;
var esTotal = mem.Read<int>(compPtr + offsets.LifeEsOffset + offsets.VitalTotalOffset); var esTotal = life.EnergyShield.Total;
if (manaTotal == 0 && esTotal == 0) continue; if (manaTotal == 0 && esTotal == 0) continue;
_cachedLifeIndex = i; _cachedLifeIndex = i;
@ -174,15 +178,14 @@ public sealed class ComponentReader
/// </summary> /// </summary>
public bool TryReadVitals(GameStateSnapshot snap, nint lifeComp) public bool TryReadVitals(GameStateSnapshot snap, nint lifeComp)
{ {
var mem = _ctx.Memory; var life = _ctx.Memory.Read<Life>(lifeComp);
var offsets = _ctx.Offsets;
var hp = mem.Read<int>(lifeComp + offsets.LifeHealthOffset + offsets.VitalCurrentOffset); var hp = life.Health.Current;
var hpMax = mem.Read<int>(lifeComp + offsets.LifeHealthOffset + offsets.VitalTotalOffset); var hpMax = life.Health.Total;
var mana = mem.Read<int>(lifeComp + offsets.LifeManaOffset + offsets.VitalCurrentOffset); var mana = life.Mana.Current;
var manaMax = mem.Read<int>(lifeComp + offsets.LifeManaOffset + offsets.VitalTotalOffset); var manaMax = life.Mana.Total;
var es = mem.Read<int>(lifeComp + offsets.LifeEsOffset + offsets.VitalCurrentOffset); var es = life.EnergyShield.Current;
var esMax = mem.Read<int>(lifeComp + offsets.LifeEsOffset + offsets.VitalTotalOffset); var esMax = life.EnergyShield.Total;
if (hpMax <= 0 || hpMax > 200000 || hp < 0 || hp > hpMax + 1000) return false; if (hpMax <= 0 || hpMax > 200000 || hp < 0 || hp > hpMax + 1000) return false;
if (manaMax < 0 || manaMax > 200000 || mana < 0) return false; if (manaMax < 0 || manaMax > 200000 || mana < 0) return false;
@ -256,22 +259,17 @@ public sealed class ComponentReader
/// </summary> /// </summary>
public bool TryReadPosition(GameStateSnapshot snap, nint renderComp) public bool TryReadPosition(GameStateSnapshot snap, nint renderComp)
{ {
var mem = _ctx.Memory; var pos = _ctx.Memory.Read<StdTuple3D<float>>(renderComp + _ctx.Offsets.PositionXOffset);
var offsets = _ctx.Offsets;
var x = mem.Read<float>(renderComp + offsets.PositionXOffset); if (float.IsNaN(pos.X) || float.IsNaN(pos.Y) || float.IsNaN(pos.Z)) return false;
var y = mem.Read<float>(renderComp + offsets.PositionYOffset); if (float.IsInfinity(pos.X) || float.IsInfinity(pos.Y) || float.IsInfinity(pos.Z)) return false;
var z = mem.Read<float>(renderComp + offsets.PositionZOffset); if (pos.X < 50 || pos.X > 50000 || pos.Y < 50 || pos.Y > 50000) return false;
if (MathF.Abs(pos.Z) > 5000) return false;
if (float.IsNaN(x) || float.IsNaN(y) || float.IsNaN(z)) return false;
if (float.IsInfinity(x) || float.IsInfinity(y) || float.IsInfinity(z)) return false;
if (x < 50 || x > 50000 || y < 50 || y > 50000) return false;
if (MathF.Abs(z) > 5000) return false;
snap.HasPosition = true; snap.HasPosition = true;
snap.PlayerX = x; snap.PlayerX = pos.X;
snap.PlayerY = y; snap.PlayerY = pos.Y;
snap.PlayerZ = z; snap.PlayerZ = pos.Z;
CachedRenderComponentAddr = renderComp; CachedRenderComponentAddr = renderComp;
return true; return true;
} }
@ -281,12 +279,10 @@ public sealed class ComponentReader
/// </summary> /// </summary>
public bool TryReadPositionRaw(nint comp, out float x, out float y, out float z) public bool TryReadPositionRaw(nint comp, out float x, out float y, out float z)
{ {
var mem = _ctx.Memory; var pos = _ctx.Memory.Read<StdTuple3D<float>>(comp + _ctx.Offsets.PositionXOffset);
var offsets = _ctx.Offsets; x = pos.X;
y = pos.Y;
x = mem.Read<float>(comp + offsets.PositionXOffset); z = pos.Z;
y = mem.Read<float>(comp + offsets.PositionYOffset);
z = mem.Read<float>(comp + offsets.PositionZOffset);
if (float.IsNaN(x) || float.IsNaN(y) || float.IsNaN(z)) return false; if (float.IsNaN(x) || float.IsNaN(y) || float.IsNaN(z)) return false;
if (float.IsInfinity(x) || float.IsInfinity(y) || float.IsInfinity(z)) return false; if (float.IsInfinity(x) || float.IsInfinity(y) || float.IsInfinity(z)) return false;

View file

@ -1,3 +1,5 @@
using Roboto.GameOffsets.Components;
using Roboto.GameOffsets.Entities;
using Serilog; using Serilog;
namespace Roboto.Memory; namespace Roboto.Memory;
@ -36,15 +38,15 @@ public sealed class EntityReader
var hasComponentLookup = offsets.ComponentLookupEntrySize > 0; var hasComponentLookup = offsets.ComponentLookupEntrySize > 0;
var dirty = false; var dirty = false;
WalkTreeInOrder(sentinel, root, maxNodes, node => WalkTreeInOrder(sentinel, root, maxNodes, (_, treeNode) =>
{ {
var entityPtr = mem.ReadPointer(node + offsets.EntityNodeValueOffset); var entityPtr = treeNode.Data.EntityPtr;
if (entityPtr == 0) return; if (entityPtr == 0) return;
var high = (ulong)entityPtr >> 32; var high = (ulong)entityPtr >> 32;
if (high == 0 || high >= 0x7FFF || (entityPtr & 0x3) != 0) return; if (high == 0 || high >= 0x7FFF || (entityPtr & 0x3) != 0) return;
var entityId = mem.Read<uint>(entityPtr + offsets.EntityIdOffset); var entityId = treeNode.Data.Key.EntityId;
var path = TryReadEntityPath(entityPtr); var path = TryReadEntityPath(entityPtr);
var entity = new Entity(entityPtr, entityId, path); var entity = new Entity(entityPtr, entityId, path);
@ -84,13 +86,13 @@ public sealed class EntityReader
var lifeComp = mem.ReadPointer(compFirst + lifeIdx * 8); var lifeComp = mem.ReadPointer(compFirst + lifeIdx * 8);
if (lifeComp != 0) if (lifeComp != 0)
{ {
var hp = mem.Read<int>(lifeComp + offsets.LifeHealthOffset + offsets.VitalCurrentOffset); var life = mem.Read<Life>(lifeComp);
var hpMax = mem.Read<int>(lifeComp + offsets.LifeHealthOffset + offsets.VitalTotalOffset); if (life.Health.Total > 0 && life.Health.Total < 200000 &&
if (hpMax > 0 && hpMax < 200000 && hp >= 0 && hp <= hpMax + 1000) life.Health.Current >= 0 && life.Health.Current <= life.Health.Total + 1000)
{ {
entity.HasVitals = true; entity.HasVitals = true;
entity.LifeCurrent = hp; entity.LifeCurrent = life.Health.Current;
entity.LifeTotal = hpMax; entity.LifeTotal = life.Health.Total;
} }
} }
} }
@ -109,14 +111,24 @@ public sealed class EntityReader
/// <summary> /// <summary>
/// Iterative in-order traversal of an MSVC std::map red-black tree. /// Iterative in-order traversal of an MSVC std::map red-black tree.
/// Backward-compatible overload — delegates to the struct-based version.
/// </summary> /// </summary>
public void WalkTreeInOrder(nint sentinel, nint root, int maxNodes, Action<nint> visitor) public void WalkTreeInOrder(nint sentinel, nint root, int maxNodes, Action<nint> visitor)
{
WalkTreeInOrder(sentinel, root, maxNodes, (addr, _) => visitor(addr));
}
/// <summary>
/// Iterative in-order traversal using Read&lt;EntityTreeNode&gt; — 1 kernel call per node
/// instead of separate Left/Right reads. The visitor receives both the node address
/// and the parsed struct data.
/// </summary>
public void WalkTreeInOrder(nint sentinel, nint root, int maxNodes, Action<nint, EntityTreeNode> visitor)
{ {
if (root == 0 || root == sentinel) return; if (root == 0 || root == sentinel) return;
var offsets = _ctx.Offsets;
var mem = _ctx.Memory; var mem = _ctx.Memory;
var stack = new Stack<nint>(); var stack = new Stack<(nint Addr, EntityTreeNode Node)>();
var current = root; var current = root;
var count = 0; var count = 0;
var visited = new HashSet<nint> { sentinel }; var visited = new HashSet<nint> { sentinel };
@ -130,18 +142,19 @@ public sealed class EntityReader
current = sentinel; current = sentinel;
break; break;
} }
stack.Push(current); var node = mem.Read<EntityTreeNode>(current);
current = mem.ReadPointer(current + offsets.EntityNodeLeftOffset); stack.Push((current, node));
current = node.Left;
} }
if (stack.Count == 0) break; if (stack.Count == 0) break;
current = stack.Pop(); var (nodeAddr, treeNode) = stack.Pop();
visitor(current); visitor(nodeAddr, treeNode);
count++; count++;
if (count >= maxNodes) break; if (count >= maxNodes) break;
current = mem.ReadPointer(current + offsets.EntityNodeRightOffset); current = treeNode.Right;
} }
} }

View file

@ -258,15 +258,8 @@ public class GameMemoryReader : IDisposable
// Cache the resolved address for fast per-frame reads // Cache the resolved address for fast per-frame reads
_cachedCameraMatrixAddr = matrixAddr; _cachedCameraMatrixAddr = matrixAddr;
// Read 64-byte Matrix4x4 as 16 floats // Read 64-byte Matrix4x4 as a single struct (System.Numerics.Matrix4x4 is already unmanaged/sequential)
var bytes = mem.ReadBytes(matrixAddr, 64); var m = mem.Read<Matrix4x4>(matrixAddr);
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 // Quick sanity check
if (float.IsNaN(m.M11) || float.IsInfinity(m.M11)) return; if (float.IsNaN(m.M11) || float.IsInfinity(m.M11)) return;

View file

@ -1,4 +1,6 @@
using Roboto.GameOffsets.States;
using Serilog; using Serilog;
using Terrain = Roboto.GameOffsets.States.Terrain;
namespace Roboto.Memory; namespace Roboto.Memory;
@ -64,9 +66,12 @@ public sealed class TerrainReader
} }
// Inline mode: TerrainStruct is inline at AreaInstance + TerrainListOffset // Inline mode: TerrainStruct is inline at AreaInstance + TerrainListOffset
// Single Read<Terrain> (0x1B0 = 432 bytes) replaces 5 individual reads
var terrainBase = areaInstance + offsets.TerrainListOffset; var terrainBase = areaInstance + offsets.TerrainListOffset;
var cols = (int)mem.Read<long>(terrainBase + offsets.TerrainDimensionsOffset); var t = mem.Read<Terrain>(terrainBase);
var rows = (int)mem.Read<long>(terrainBase + offsets.TerrainDimensionsOffset + 8);
var cols = (int)t.Dimensions.X;
var rows = (int)t.Dimensions.Y;
if (cols <= 0 || cols >= 1000 || rows <= 0 || rows >= 1000) if (cols <= 0 || cols >= 1000 || rows <= 0 || rows >= 1000)
return; return;
@ -99,10 +104,9 @@ public sealed class TerrainReader
return; return;
} }
// Read GridWalkableData StdVector (begin/end/cap pointers) // Grid vector pointers already available from the Terrain struct read
var gridVecOffset = offsets.TerrainWalkableGridOffset; var gridBegin = t.WalkableGrid.First;
var gridBegin = mem.ReadPointer(terrainBase + gridVecOffset); var gridEnd = t.WalkableGrid.Last;
var gridEnd = mem.ReadPointer(terrainBase + gridVecOffset + 8);
if (gridBegin == 0 || gridEnd <= gridBegin) if (gridBegin == 0 || gridEnd <= gridBegin)
return; return;
@ -110,7 +114,7 @@ public sealed class TerrainReader
if (gridDataSize <= 0 || gridDataSize > 16 * 1024 * 1024) if (gridDataSize <= 0 || gridDataSize > 16 * 1024 * 1024)
return; return;
var bytesPerRow = mem.Read<int>(terrainBase + offsets.TerrainBytesPerRowOffset); var bytesPerRow = t.BytesPerRow;
if (bytesPerRow <= 0 || bytesPerRow > 0x10000) if (bytesPerRow <= 0 || bytesPerRow > 0x10000)
return; return;