fixed stuff up
This commit is contained in:
parent
18d8721dd5
commit
e5ebe05571
4 changed files with 69 additions and 63 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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<EntityTreeNode> — 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue