266 lines
9.3 KiB
C#
266 lines
9.3 KiB
C#
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;
|
|
}
|
|
}
|