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 Roboto.GameOffsets.Components;
using Roboto.GameOffsets.Natives;
using Serilog;
namespace Roboto.Memory;
@ -146,19 +148,21 @@ public sealed class ComponentReader
if (high == 0 || high >= 0x7FFF) 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;
var hpCurrent = mem.Read<int>(compPtr + offsets.LifeHealthOffset + offsets.VitalCurrentOffset);
var hpCurrent = life.Health.Current;
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;
var manaCurrent = mem.Read<int>(compPtr + offsets.LifeManaOffset + offsets.VitalCurrentOffset);
var manaCurrent = life.Mana.Current;
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;
_cachedLifeIndex = i;
@ -174,15 +178,14 @@ public sealed class ComponentReader
/// </summary>
public bool TryReadVitals(GameStateSnapshot snap, nint lifeComp)
{
var mem = _ctx.Memory;
var offsets = _ctx.Offsets;
var life = _ctx.Memory.Read<Life>(lifeComp);
var hp = mem.Read<int>(lifeComp + offsets.LifeHealthOffset + offsets.VitalCurrentOffset);
var hpMax = mem.Read<int>(lifeComp + offsets.LifeHealthOffset + offsets.VitalTotalOffset);
var mana = mem.Read<int>(lifeComp + offsets.LifeManaOffset + offsets.VitalCurrentOffset);
var manaMax = mem.Read<int>(lifeComp + offsets.LifeManaOffset + offsets.VitalTotalOffset);
var es = mem.Read<int>(lifeComp + offsets.LifeEsOffset + offsets.VitalCurrentOffset);
var esMax = mem.Read<int>(lifeComp + offsets.LifeEsOffset + offsets.VitalTotalOffset);
var hp = life.Health.Current;
var hpMax = life.Health.Total;
var mana = life.Mana.Current;
var manaMax = life.Mana.Total;
var es = life.EnergyShield.Current;
var esMax = life.EnergyShield.Total;
if (hpMax <= 0 || hpMax > 200000 || hp < 0 || hp > hpMax + 1000) return false;
if (manaMax < 0 || manaMax > 200000 || mana < 0) return false;
@ -256,22 +259,17 @@ public sealed class ComponentReader
/// </summary>
public bool TryReadPosition(GameStateSnapshot snap, nint renderComp)
{
var mem = _ctx.Memory;
var offsets = _ctx.Offsets;
var pos = _ctx.Memory.Read<StdTuple3D<float>>(renderComp + _ctx.Offsets.PositionXOffset);
var x = mem.Read<float>(renderComp + offsets.PositionXOffset);
var y = mem.Read<float>(renderComp + offsets.PositionYOffset);
var z = mem.Read<float>(renderComp + offsets.PositionZOffset);
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;
if (float.IsNaN(pos.X) || float.IsNaN(pos.Y) || float.IsNaN(pos.Z)) return false;
if (float.IsInfinity(pos.X) || float.IsInfinity(pos.Y) || float.IsInfinity(pos.Z)) return false;
if (pos.X < 50 || pos.X > 50000 || pos.Y < 50 || pos.Y > 50000) return false;
if (MathF.Abs(pos.Z) > 5000) return false;
snap.HasPosition = true;
snap.PlayerX = x;
snap.PlayerY = y;
snap.PlayerZ = z;
snap.PlayerX = pos.X;
snap.PlayerY = pos.Y;
snap.PlayerZ = pos.Z;
CachedRenderComponentAddr = renderComp;
return true;
}
@ -281,12 +279,10 @@ public sealed class ComponentReader
/// </summary>
public bool TryReadPositionRaw(nint comp, out float x, out float y, out float z)
{
var mem = _ctx.Memory;
var offsets = _ctx.Offsets;
x = mem.Read<float>(comp + offsets.PositionXOffset);
y = mem.Read<float>(comp + offsets.PositionYOffset);
z = mem.Read<float>(comp + offsets.PositionZOffset);
var pos = _ctx.Memory.Read<StdTuple3D<float>>(comp + _ctx.Offsets.PositionXOffset);
x = pos.X;
y = pos.Y;
z = pos.Z;
if (float.IsNaN(x) || float.IsNaN(y) || float.IsNaN(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;
namespace Roboto.Memory;
@ -36,15 +38,15 @@ public sealed class EntityReader
var hasComponentLookup = offsets.ComponentLookupEntrySize > 0;
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;
var high = (ulong)entityPtr >> 32;
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 entity = new Entity(entityPtr, entityId, path);
@ -84,13 +86,13 @@ public sealed class EntityReader
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)
var life = mem.Read<Life>(lifeComp);
if (life.Health.Total > 0 && life.Health.Total < 200000 &&
life.Health.Current >= 0 && life.Health.Current <= life.Health.Total + 1000)
{
entity.HasVitals = true;
entity.LifeCurrent = hp;
entity.LifeTotal = hpMax;
entity.LifeCurrent = life.Health.Current;
entity.LifeTotal = life.Health.Total;
}
}
}
@ -109,14 +111,24 @@ public sealed class EntityReader
/// <summary>
/// Iterative in-order traversal of an MSVC std::map red-black tree.
/// Backward-compatible overload — delegates to the struct-based version.
/// </summary>
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;
var offsets = _ctx.Offsets;
var mem = _ctx.Memory;
var stack = new Stack<nint>();
var stack = new Stack<(nint Addr, EntityTreeNode Node)>();
var current = root;
var count = 0;
var visited = new HashSet<nint> { sentinel };
@ -130,18 +142,19 @@ public sealed class EntityReader
current = sentinel;
break;
}
stack.Push(current);
current = mem.ReadPointer(current + offsets.EntityNodeLeftOffset);
var node = mem.Read<EntityTreeNode>(current);
stack.Push((current, node));
current = node.Left;
}
if (stack.Count == 0) break;
current = stack.Pop();
visitor(current);
var (nodeAddr, treeNode) = stack.Pop();
visitor(nodeAddr, treeNode);
count++;
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
_cachedCameraMatrixAddr = matrixAddr;
// 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));
// Read 64-byte Matrix4x4 as a single struct (System.Numerics.Matrix4x4 is already unmanaged/sequential)
var m = mem.Read<Matrix4x4>(matrixAddr);
// Quick sanity check
if (float.IsNaN(m.M11) || float.IsInfinity(m.M11)) return;

View file

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