test
This commit is contained in:
parent
0df70abad7
commit
0c14d78d8a
25 changed files with 1487 additions and 179 deletions
266
src/Roboto.Memory/Objects/QuestFlags.cs
Normal file
266
src/Roboto.Memory/Objects/QuestFlags.cs
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
using Roboto.Memory;
|
||||
using Serilog;
|
||||
|
||||
namespace Roboto.Memory.Objects;
|
||||
|
||||
/// <summary>
|
||||
/// Reads quest flags from ServerData → PlayerServerData → QuestFlags.
|
||||
/// RemoteObject wrapping the previous QuestReader logic.
|
||||
/// Update(serverDataPtr) to read quests.
|
||||
/// </summary>
|
||||
public sealed class QuestFlags : RemoteObject
|
||||
{
|
||||
private readonly MsvcStringReader _strings;
|
||||
private readonly QuestNameLookup? _nameLookup;
|
||||
|
||||
private readonly Dictionary<nint, string?> _nameCache = new();
|
||||
private nint _lastPsd;
|
||||
|
||||
public List<QuestSnapshot>? Quests { get; private set; }
|
||||
|
||||
public QuestFlags(MemoryContext ctx, MsvcStringReader strings, QuestNameLookup? nameLookup = null)
|
||||
: base(ctx)
|
||||
{
|
||||
_strings = strings;
|
||||
_nameLookup = nameLookup;
|
||||
}
|
||||
|
||||
protected override bool ReadData()
|
||||
{
|
||||
var offsets = Ctx.Offsets;
|
||||
if (offsets.QuestFlagEntrySize <= 0) { Quests = null; return true; }
|
||||
|
||||
var mem = Ctx.Memory;
|
||||
|
||||
var psdVecBegin = mem.ReadPointer(Address + offsets.PlayerServerDataOffset);
|
||||
if (psdVecBegin == 0) { Quests = null; return true; }
|
||||
|
||||
var playerServerData = mem.ReadPointer(psdVecBegin);
|
||||
if (playerServerData == 0) { Quests = null; return true; }
|
||||
|
||||
if (playerServerData != _lastPsd)
|
||||
{
|
||||
_nameCache.Clear();
|
||||
_lastPsd = playerServerData;
|
||||
}
|
||||
|
||||
var questFlagsAddr = playerServerData + offsets.QuestFlagsOffset;
|
||||
|
||||
if (offsets.QuestFlagsContainerType == "int_vector")
|
||||
Quests = ReadIntVectorQuests(questFlagsAddr, offsets);
|
||||
else if (offsets.QuestFlagsContainerType == "vector")
|
||||
Quests = ReadStructVectorQuests(questFlagsAddr, offsets);
|
||||
else
|
||||
Quests = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void Clear()
|
||||
{
|
||||
Quests = null;
|
||||
_nameCache.Clear();
|
||||
_lastPsd = 0;
|
||||
}
|
||||
|
||||
private List<QuestSnapshot>? ReadIntVectorQuests(nint questFlagsAddr, GameOffsets offsets)
|
||||
{
|
||||
var mem = Ctx.Memory;
|
||||
|
||||
var vecBegin = mem.ReadPointer(questFlagsAddr);
|
||||
var vecEnd = mem.ReadPointer(questFlagsAddr + 8);
|
||||
if (vecBegin == 0 || vecEnd <= vecBegin) return null;
|
||||
|
||||
var totalBytes = (int)(vecEnd - vecBegin);
|
||||
var entryCount = totalBytes / 4;
|
||||
if (entryCount <= 0 || entryCount > offsets.QuestFlagsMaxEntries) return null;
|
||||
|
||||
var vecData = mem.ReadBytes(vecBegin, totalBytes);
|
||||
if (vecData is null) return null;
|
||||
|
||||
byte[]? compData = null;
|
||||
var compEntryCount = 0;
|
||||
if (offsets.QuestCompanionOffset > 0 && offsets.QuestCompanionEntrySize > 0)
|
||||
{
|
||||
var compBegin = mem.ReadPointer(questFlagsAddr + offsets.QuestCompanionOffset);
|
||||
var compEnd = mem.ReadPointer(questFlagsAddr + offsets.QuestCompanionOffset + 8);
|
||||
if (compBegin != 0 && compEnd > compBegin)
|
||||
{
|
||||
var compBytes = (int)(compEnd - compBegin);
|
||||
compEntryCount = compBytes / offsets.QuestCompanionEntrySize;
|
||||
if (compEntryCount > 0 && compBytes < 0x100000)
|
||||
compData = mem.ReadBytes(compBegin, compBytes);
|
||||
}
|
||||
}
|
||||
|
||||
var datTableBase = FindDatTableBase(offsets);
|
||||
|
||||
var result = new List<QuestSnapshot>(entryCount);
|
||||
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var idx = BitConverter.ToInt32(vecData, i * 4);
|
||||
string? questName = null;
|
||||
string? internalId = null;
|
||||
byte stateId = 0;
|
||||
bool isTracked = false;
|
||||
nint questObjPtr = 0;
|
||||
|
||||
if (compData is not null && i < compEntryCount)
|
||||
{
|
||||
var compOff = i * offsets.QuestCompanionEntrySize;
|
||||
|
||||
if (offsets.QuestCompanionTrackedOffset > 0 &&
|
||||
compOff + offsets.QuestCompanionTrackedOffset + 4 <= compData.Length)
|
||||
{
|
||||
var trackedVal = BitConverter.ToUInt32(compData, compOff + offsets.QuestCompanionTrackedOffset);
|
||||
isTracked = trackedVal == offsets.QuestTrackedMarker;
|
||||
}
|
||||
|
||||
if (compOff + offsets.QuestCompanionObjPtrOffset + 8 <= compData.Length)
|
||||
{
|
||||
questObjPtr = (nint)BitConverter.ToInt64(compData, compOff + offsets.QuestCompanionObjPtrOffset);
|
||||
|
||||
if (questObjPtr != 0 && ((ulong)questObjPtr >> 32) is > 0 and < 0x7FFF
|
||||
&& offsets.QuestObjEncounterStateOffset > 0)
|
||||
{
|
||||
var stateByte = mem.ReadBytes(questObjPtr + offsets.QuestObjEncounterStateOffset, 1);
|
||||
if (stateByte is { Length: 1 })
|
||||
stateId = stateByte[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (datTableBase != 0 && offsets.QuestDatRowSize > 0)
|
||||
{
|
||||
var rowAddr = datTableBase + idx * offsets.QuestDatRowSize;
|
||||
questName = ResolveDatString(rowAddr + offsets.QuestDatNameOffset);
|
||||
internalId = ResolveDatString(rowAddr + offsets.QuestDatInternalIdOffset);
|
||||
}
|
||||
else if (_nameLookup is not null && _nameLookup.TryGet(idx, out var entry))
|
||||
{
|
||||
questName = entry?.Name;
|
||||
internalId = entry?.InternalId;
|
||||
}
|
||||
|
||||
result.Add(new QuestSnapshot
|
||||
{
|
||||
QuestStateIndex = idx,
|
||||
QuestDatPtr = questObjPtr,
|
||||
QuestName = questName,
|
||||
InternalId = internalId,
|
||||
StateId = stateId,
|
||||
IsTracked = isTracked,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private nint FindDatTableBase(GameOffsets offsets)
|
||||
{
|
||||
if (offsets.QuestDatRowSize <= 0) return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private string? ResolveDatString(nint fieldAddr)
|
||||
{
|
||||
if (_nameCache.TryGetValue(fieldAddr, out var cached))
|
||||
return cached;
|
||||
|
||||
var mem = Ctx.Memory;
|
||||
var strPtr = mem.ReadPointer(fieldAddr);
|
||||
string? result = null;
|
||||
|
||||
if (strPtr != 0 && ((ulong)strPtr >> 32) is > 0 and < 0x7FFF)
|
||||
result = _strings.ReadNullTermWString(strPtr);
|
||||
|
||||
_nameCache[fieldAddr] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<QuestSnapshot>? ReadStructVectorQuests(nint questFlagsAddr, GameOffsets offsets)
|
||||
{
|
||||
var mem = Ctx.Memory;
|
||||
|
||||
var vecBegin = mem.ReadPointer(questFlagsAddr);
|
||||
var vecEnd = mem.ReadPointer(questFlagsAddr + 8);
|
||||
if (vecBegin == 0 || vecEnd <= vecBegin) return null;
|
||||
|
||||
var totalBytes = (int)(vecEnd - vecBegin);
|
||||
var entrySize = offsets.QuestFlagEntrySize;
|
||||
var entryCount = totalBytes / entrySize;
|
||||
if (entryCount <= 0 || entryCount > offsets.QuestFlagsMaxEntries) return null;
|
||||
|
||||
var vecData = mem.ReadBytes(vecBegin, totalBytes);
|
||||
if (vecData is null) return null;
|
||||
|
||||
var result = new List<QuestSnapshot>(entryCount);
|
||||
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entryOffset = i * entrySize;
|
||||
|
||||
nint questDatPtr = 0;
|
||||
if (entryOffset + offsets.QuestEntryQuestPtrOffset + 8 <= vecData.Length)
|
||||
questDatPtr = (nint)BitConverter.ToInt64(vecData, entryOffset + offsets.QuestEntryQuestPtrOffset);
|
||||
|
||||
byte stateId = 0;
|
||||
if (entryOffset + offsets.QuestEntryStateIdOffset < vecData.Length)
|
||||
stateId = vecData[entryOffset + offsets.QuestEntryStateIdOffset];
|
||||
|
||||
var questName = ResolveQuestName(questDatPtr);
|
||||
|
||||
string? stateText = null;
|
||||
if (offsets.QuestEntryStateTextOffset > 0 &&
|
||||
entryOffset + offsets.QuestEntryStateTextOffset + 8 <= vecData.Length)
|
||||
{
|
||||
var stateTextPtr = (nint)BitConverter.ToInt64(vecData, entryOffset + offsets.QuestEntryStateTextOffset);
|
||||
if (stateTextPtr != 0 && ((ulong)stateTextPtr >> 32) is > 0 and < 0x7FFF)
|
||||
stateText = _strings.ReadNullTermWString(stateTextPtr);
|
||||
}
|
||||
|
||||
string? progressText = null;
|
||||
if (offsets.QuestEntryProgressTextOffset > 0 &&
|
||||
entryOffset + offsets.QuestEntryProgressTextOffset + 8 <= vecData.Length)
|
||||
{
|
||||
var progressTextPtr = (nint)BitConverter.ToInt64(vecData, entryOffset + offsets.QuestEntryProgressTextOffset);
|
||||
if (progressTextPtr != 0 && ((ulong)progressTextPtr >> 32) is > 0 and < 0x7FFF)
|
||||
progressText = _strings.ReadNullTermWString(progressTextPtr);
|
||||
}
|
||||
|
||||
result.Add(new QuestSnapshot
|
||||
{
|
||||
QuestDatPtr = questDatPtr,
|
||||
QuestName = questName,
|
||||
StateId = stateId,
|
||||
StateText = stateText,
|
||||
ProgressText = progressText,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private string? ResolveQuestName(nint questDatPtr)
|
||||
{
|
||||
if (questDatPtr == 0) return null;
|
||||
|
||||
if (_nameCache.TryGetValue(questDatPtr, out var cached))
|
||||
return cached;
|
||||
|
||||
var mem = Ctx.Memory;
|
||||
string? name = null;
|
||||
|
||||
var high = (ulong)questDatPtr >> 32;
|
||||
if (high is > 0 and < 0x7FFF)
|
||||
{
|
||||
var namePtr = mem.ReadPointer(questDatPtr);
|
||||
if (namePtr != 0)
|
||||
name = _strings.ReadNullTermWString(namePtr);
|
||||
}
|
||||
|
||||
_nameCache[questDatPtr] = name;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue