areatemplate added

This commit is contained in:
Boki 2026-03-04 17:12:32 -05:00
parent 0c14d78d8a
commit 94b460bbc8
8 changed files with 125 additions and 20 deletions

View file

@ -103,6 +103,13 @@ public partial class MemoryViewModel : ObservableObject
private MemoryNodeViewModel? _entityListNode; private MemoryNodeViewModel? _entityListNode;
private MemoryNodeViewModel? _skillsNode; private MemoryNodeViewModel? _skillsNode;
private MemoryNodeViewModel? _questsNode; private MemoryNodeViewModel? _questsNode;
private MemoryNodeViewModel? _areaRawName;
private MemoryNodeViewModel? _areaDisplayName;
private MemoryNodeViewModel? _areaAct;
private MemoryNodeViewModel? _areaIsTown;
private MemoryNodeViewModel? _areaHasWaypoint;
private MemoryNodeViewModel? _areaMonsterLevel;
private MemoryNodeViewModel? _worldAreaId;
partial void OnIsEnabledChanged(bool value) partial void OnIsEnabledChanged(bool value)
{ {
@ -235,7 +242,25 @@ public partial class MemoryViewModel : ObservableObject
terrain.Children.Add(_terrainGrid); terrain.Children.Add(_terrainGrid);
terrain.Children.Add(_terrainWalkable); terrain.Children.Add(_terrainWalkable);
// AreaTemplate (from WorldData — zone metadata)
var areaTemplateGroup = new MemoryNodeViewModel("AreaTemplate");
_areaRawName = new MemoryNodeViewModel("RawName:");
_areaDisplayName = new MemoryNodeViewModel("Name:");
_areaAct = new MemoryNodeViewModel("Act:");
_areaIsTown = new MemoryNodeViewModel("IsTown:");
_areaHasWaypoint = new MemoryNodeViewModel("HasWaypoint:");
_areaMonsterLevel = new MemoryNodeViewModel("MonsterLevel:");
_worldAreaId = new MemoryNodeViewModel("WorldAreaId:");
areaTemplateGroup.Children.Add(_areaRawName);
areaTemplateGroup.Children.Add(_areaDisplayName);
areaTemplateGroup.Children.Add(_areaAct);
areaTemplateGroup.Children.Add(_areaIsTown);
areaTemplateGroup.Children.Add(_areaHasWaypoint);
areaTemplateGroup.Children.Add(_areaMonsterLevel);
areaTemplateGroup.Children.Add(_worldAreaId);
inGameStateGroup.Children.Add(areaInstanceGroup); inGameStateGroup.Children.Add(areaInstanceGroup);
inGameStateGroup.Children.Add(areaTemplateGroup);
inGameStateGroup.Children.Add(player); inGameStateGroup.Children.Add(player);
inGameStateGroup.Children.Add(entitiesGroup); inGameStateGroup.Children.Add(entitiesGroup);
inGameStateGroup.Children.Add(terrain); inGameStateGroup.Children.Add(terrain);
@ -437,6 +462,15 @@ public partial class MemoryViewModel : ObservableObject
snap.EntityCount > 0 ? snap.EntityCount.ToString() : "—", snap.EntityCount > 0 ? snap.EntityCount.ToString() : "—",
snap.EntityCount > 0); snap.EntityCount > 0);
// AreaTemplate
_areaRawName!.Set(snap.AreaRawName ?? "—", snap.AreaRawName is not null);
_areaDisplayName!.Set(snap.AreaName ?? "—", snap.AreaName is not null);
_areaAct!.Set(snap.AreaAct > 0 ? snap.AreaAct.ToString() : "—", snap.AreaAct > 0);
_areaIsTown!.Set(snap.AreaIsTown ? "Yes" : "No", snap.AreaIsTown);
_areaHasWaypoint!.Set(snap.AreaHasWaypoint ? "Yes" : "No", snap.AreaHasWaypoint);
_areaMonsterLevel!.Set(snap.AreaMonsterLevel > 0 ? snap.AreaMonsterLevel.ToString() : "—", snap.AreaMonsterLevel > 0);
_worldAreaId!.Set(snap.WorldAreaId > 0 ? snap.WorldAreaId.ToString() : "—", snap.WorldAreaId > 0);
// Player position // Player position
if (snap.HasPosition) if (snap.HasPosition)
_playerPos!.Set($"({snap.PlayerX:F1}, {snap.PlayerY:F1}, {snap.PlayerZ:F1})"); _playerPos!.Set($"({snap.PlayerX:F1}, {snap.PlayerY:F1}, {snap.PlayerZ:F1})");
@ -1355,4 +1389,16 @@ public partial class MemoryViewModel : ObservableObject
ScanResult = _reader.Diagnostics!.ScanQuestStateObjects(); ScanResult = _reader.Diagnostics!.ScanQuestStateObjects();
} }
[RelayCommand]
private void ScanAreaTemplateExecute()
{
if (_reader is null || !_reader.IsAttached)
{
ScanResult = "Error: not attached";
return;
}
ScanResult = _reader.Diagnostics!.ScanAreaTemplate();
}
} }

View file

@ -784,6 +784,8 @@
Padding="10,4" FontWeight="Bold" Margin="0,0,6,4" /> Padding="10,4" FontWeight="Bold" Margin="0,0,6,4" />
<Button Content="Quest Objects" Command="{Binding ScanQuestObjectsExecuteCommand}" <Button Content="Quest Objects" Command="{Binding ScanQuestObjectsExecuteCommand}"
Padding="10,4" FontWeight="Bold" Margin="0,0,6,4" /> Padding="10,4" FontWeight="Bold" Margin="0,0,6,4" />
<Button Content="Area Template" Command="{Binding ScanAreaTemplateExecuteCommand}"
Padding="10,4" FontWeight="Bold" Margin="0,0,6,4" />
</WrapPanel> </WrapPanel>
<TextBox Text="{Binding ScanResult, Mode=OneWay}" FontFamily="Consolas" <TextBox Text="{Binding ScanResult, Mode=OneWay}" FontFamily="Consolas"
FontSize="10" Foreground="#e6edf3" Background="#0d1117" FontSize="10" Foreground="#e6edf3" Background="#0d1117"

View file

@ -5520,4 +5520,64 @@ public sealed class MemoryDiagnostics
BfsSearchUiElements(mem, offsets, childPtr, childPath, currentDepth + 1, maxDepth, visited, results, predicate, selfOffset); BfsSearchUiElements(mem, offsets, childPtr, childPath, currentDepth + 1, maxDepth, visited, results, predicate, selfOffset);
} }
} }
/// <summary>
/// Diagnostic: dump AreaInstance → AreaTemplate pointer chain.
/// </summary>
public string ScanAreaTemplate()
{
var sb = new StringBuilder();
var mem = _ctx.Memory;
var offsets = _ctx.Offsets;
// Resolve InGameState
var controller = mem.ReadPointer(_ctx.GameStateBase);
if (controller == 0) { sb.AppendLine("ERROR: controller is null"); return sb.ToString(); }
nint inGameState;
if (offsets.InGameStateDirectOffset > 0)
inGameState = mem.ReadPointer(controller + offsets.InGameStateDirectOffset);
else
inGameState = 0;
if (inGameState == 0) { sb.AppendLine("ERROR: InGameState is null"); return sb.ToString(); }
sb.AppendLine($"InGameState: 0x{inGameState:X}");
// AreaInstance ptr
var aiPtr = mem.ReadPointer(inGameState + offsets.IngameDataFromStateOffset);
sb.AppendLine($"AreaInstance (+0x{offsets.IngameDataFromStateOffset:X}): 0x{aiPtr:X}");
if (aiPtr == 0) { sb.AppendLine("ERROR: AreaInstance is null"); return sb.ToString(); }
// AreaTemplate ptr
var atPtr = mem.ReadPointer(aiPtr + offsets.AreaTemplateOffset);
sb.AppendLine($"AreaTemplate (+0x{offsets.AreaTemplateOffset:X}): 0x{atPtr:X}");
if (atPtr == 0) { sb.AppendLine("ERROR: AreaTemplate pointer is null"); return sb.ToString(); }
sb.AppendLine();
DumpAreaTemplateAt(sb, mem, atPtr, offsets);
return sb.ToString();
}
private void DumpAreaTemplateAt(StringBuilder sb, ProcessMemory mem, nint addr, GameOffsets offsets)
{
var rawNamePtr = mem.ReadPointer(addr + offsets.AreaTemplateRawNameOffset);
var namePtr = mem.ReadPointer(addr + offsets.AreaTemplateNameOffset);
var act = mem.Read<int>(addr + offsets.AreaTemplateActOffset);
var isTown = mem.Read<byte>(addr + offsets.AreaTemplateIsTownOffset);
var hasWaypoint = mem.Read<byte>(addr + offsets.AreaTemplateHasWaypointOffset);
var monsterLevel = mem.Read<int>(addr + offsets.AreaTemplateMonsterLevelOffset);
var worldAreaId = mem.Read<int>(addr + offsets.AreaTemplateWorldAreaIdOffset);
var rawName = _strings.ReadNullTermWString(rawNamePtr);
var name = _strings.ReadNullTermWString(namePtr);
sb.AppendLine($" RawNamePtr (+0x{offsets.AreaTemplateRawNameOffset:X2}): 0x{rawNamePtr:X} → \"{rawName}\"");
sb.AppendLine($" NamePtr (+0x{offsets.AreaTemplateNameOffset:X2}): 0x{namePtr:X} → \"{name}\"");
sb.AppendLine($" Act (+0x{offsets.AreaTemplateActOffset:X2}): {act}");
sb.AppendLine($" IsTown (+0x{offsets.AreaTemplateIsTownOffset:X2}): {isTown}");
sb.AppendLine($" HasWaypoint(+0x{offsets.AreaTemplateHasWaypointOffset:X2}): {hasWaypoint}");
sb.AppendLine($" MonsterLvl (+0x{offsets.AreaTemplateMonsterLevelOffset:X2}): {monsterLevel}");
sb.AppendLine($" WorldAreaId(+0x{offsets.AreaTemplateWorldAreaIdOffset:X2}): {worldAreaId}");
}
} }

View file

@ -192,8 +192,8 @@ public class GameMemoryReader : IDisposable
snap.AreaLevel = areaLevel; snap.AreaLevel = areaLevel;
snap.AreaHash = ai.AreaHash; snap.AreaHash = ai.AreaHash;
// Area template from WorldData // Area template from AreaInstance
var at = gs.InGame.WorldData.AreaTemplate; var at = ai.AreaTemplate;
if (at.IsValid) if (at.IsValid)
{ {
snap.AreaRawName = at.RawName; snap.AreaRawName = at.RawName;

View file

@ -235,9 +235,9 @@ public sealed class GameOffsets
/// <summary>Offset within Camera struct to the Matrix4x4 (64 bytes). 0 = disabled.</summary> /// <summary>Offset within Camera struct to the Matrix4x4 (64 bytes). 0 = disabled.</summary>
public int CameraMatrixOffset { get; set; } = 0x1A0; public int CameraMatrixOffset { get; set; } = 0x1A0;
// ── AreaTemplate (WorldData → WorldAreaDetailsPtr → AreaTemplate) ── // ── AreaTemplate (AreaInstance +0xA0 → AreaTemplate) ──
/// <summary>WorldData struct → WorldAreaDetailsPtr offset. Already in struct at 0x98.</summary> /// <summary>AreaInstance → AreaTemplate pointer. Confirmed: 0xA0.</summary>
public int WorldAreaDetailsOffset { get; set; } = 0x98; public int AreaTemplateOffset { get; set; } = 0xA0;
/// <summary>AreaTemplate → RawName wchar_t* pointer.</summary> /// <summary>AreaTemplate → RawName wchar_t* pointer.</summary>
public int AreaTemplateRawNameOffset { get; set; } = 0x00; public int AreaTemplateRawNameOffset { get; set; } = 0x00;
/// <summary>AreaTemplate → Name wchar_t* pointer (display name).</summary> /// <summary>AreaTemplate → Name wchar_t* pointer (display name).</summary>

View file

@ -20,6 +20,7 @@ public sealed class AreaInstance : RemoteObject
public PlayerSkills PlayerSkills { get; } public PlayerSkills PlayerSkills { get; }
public QuestFlags QuestFlags { get; } public QuestFlags QuestFlags { get; }
public Terrain Terrain { get; } public Terrain Terrain { get; }
public AreaTemplate AreaTemplate { get; }
public AreaInstance(MemoryContext ctx, ComponentReader components, MsvcStringReader strings, QuestNameLookup? questNames) public AreaInstance(MemoryContext ctx, ComponentReader components, MsvcStringReader strings, QuestNameLookup? questNames)
: base(ctx) : base(ctx)
@ -28,6 +29,7 @@ public sealed class AreaInstance : RemoteObject
PlayerSkills = new PlayerSkills(ctx, components, strings); PlayerSkills = new PlayerSkills(ctx, components, strings);
QuestFlags = new QuestFlags(ctx, strings, questNames); QuestFlags = new QuestFlags(ctx, strings, questNames);
Terrain = new Terrain(ctx); Terrain = new Terrain(ctx);
AreaTemplate = new AreaTemplate(ctx, strings);
} }
protected override bool ReadData() protected override bool ReadData()
@ -99,6 +101,13 @@ public sealed class AreaInstance : RemoteObject
else else
QuestFlags.Reset(); QuestFlags.Reset();
// AreaTemplate — pointer at AreaInstance + AreaTemplateOffset
var areaTemplatePtr = mem.ReadPointer(Address + offsets.AreaTemplateOffset);
if (areaTemplatePtr != 0)
AreaTemplate.Update(areaTemplatePtr);
else
AreaTemplate.Reset();
// Terrain — pass loading/area state before update // Terrain — pass loading/area state before update
Terrain.AreaHash = AreaHash; Terrain.AreaHash = AreaHash;
Terrain.Update(Address); Terrain.Update(Address);
@ -133,5 +142,6 @@ public sealed class AreaInstance : RemoteObject
PlayerSkills.Reset(); PlayerSkills.Reset();
QuestFlags.Reset(); QuestFlags.Reset();
Terrain.Reset(); Terrain.Reset();
AreaTemplate.Reset();
} }
} }

View file

@ -19,7 +19,7 @@ public sealed class InGameState : RemoteObject
: base(ctx) : base(ctx)
{ {
AreaInstance = new AreaInstance(ctx, components, strings, questNames); AreaInstance = new AreaInstance(ctx, components, strings, questNames);
WorldData = new WorldData(ctx, strings); WorldData = new WorldData(ctx);
} }
protected override bool ReadData() protected override bool ReadData()

View file

@ -7,7 +7,6 @@ namespace Roboto.Memory.Objects;
/// <summary> /// <summary>
/// Reads WorldData struct (168B, 1 RPM) and resolves the camera matrix. /// Reads WorldData struct (168B, 1 RPM) and resolves the camera matrix.
/// Primary camera source: WorldData.CameraPtr. Fallback: InGameState.CameraPtr (set via FallbackCameraPtr). /// Primary camera source: WorldData.CameraPtr. Fallback: InGameState.CameraPtr (set via FallbackCameraPtr).
/// Owns AreaTemplate child for area metadata.
/// </summary> /// </summary>
public sealed class WorldData : RemoteObject public sealed class WorldData : RemoteObject
{ {
@ -21,12 +20,7 @@ public sealed class WorldData : RemoteObject
/// <summary>Resolved address of the camera matrix for hot-path caching.</summary> /// <summary>Resolved address of the camera matrix for hot-path caching.</summary>
public nint CameraMatrixAddress { get; private set; } public nint CameraMatrixAddress { get; private set; }
public AreaTemplate AreaTemplate { get; } public WorldData(MemoryContext ctx) : base(ctx) { }
public WorldData(MemoryContext ctx, MsvcStringReader strings) : base(ctx)
{
AreaTemplate = new AreaTemplate(ctx, strings);
}
protected override bool ReadData() protected override bool ReadData()
{ {
@ -36,12 +30,6 @@ public sealed class WorldData : RemoteObject
// Read the full WorldData struct (0xA8 = 168 bytes, 1 RPM) // Read the full WorldData struct (0xA8 = 168 bytes, 1 RPM)
_data = mem.Read<WdStruct>(Address); _data = mem.Read<WdStruct>(Address);
// Cascade to AreaTemplate
if (_data.WorldAreaDetailsPtr != 0)
AreaTemplate.Update(_data.WorldAreaDetailsPtr);
else
AreaTemplate.Reset();
// Resolve camera: primary from WorldData, fallback from InGameState // Resolve camera: primary from WorldData, fallback from InGameState
if (offsets.CameraMatrixOffset <= 0) if (offsets.CameraMatrixOffset <= 0)
return true; return true;
@ -69,6 +57,5 @@ public sealed class WorldData : RemoteObject
FallbackCameraPtr = 0; FallbackCameraPtr = 0;
CameraMatrix = null; CameraMatrix = null;
CameraMatrixAddress = 0; CameraMatrixAddress = 0;
AreaTemplate.Reset();
} }
} }