|
@@ -10,47 +10,73 @@ namespace {
|
|
|
|
|
|
|
|
using Quest = state::build_data::items::QuestInitialization;
|
|
using Quest = state::build_data::items::QuestInitialization;
|
|
|
|
|
|
|
|
-// Serialized layout offsets are relative to the definition, block, or row named below,
|
|
|
|
|
-// not process addresses. Class IDs identify the expected serialized block/element type.
|
|
|
|
|
|
|
+/** Policy: shorter item blobs stay outside the supported quest layout. */
|
|
|
constexpr std::size_t kMinimumQuestDefinitionSize = 0xF0;
|
|
constexpr std::size_t kMinimumQuestDefinitionSize = 0xF0;
|
|
|
|
|
+/** A serialized block's 32-bit class ID sits immediately before its payload. */
|
|
|
constexpr std::size_t kBlockClassPrefixSize = sizeof(std::uint32_t);
|
|
constexpr std::size_t kBlockClassPrefixSize = sizeof(std::uint32_t);
|
|
|
|
|
+/** Policy: keep the full fixed block prefix before reading nested arrays. */
|
|
|
constexpr std::size_t kMinimumQuestBlockSize = 0x20;
|
|
constexpr std::size_t kMinimumQuestBlockSize = 0x20;
|
|
|
|
|
|
|
|
|
|
+/** Item +0x30 holds a signed 64-bit offset relative to that field. */
|
|
|
constexpr std::size_t kItemObjectiveBlockOffset = 0x30;
|
|
constexpr std::size_t kItemObjectiveBlockOffset = 0x30;
|
|
|
|
|
+/** This block holds objective indices and the item index that owns the quest set. */
|
|
|
constexpr std::uint32_t kItemObjectiveBlockClass = 0x808077EBU;
|
|
constexpr std::uint32_t kItemObjectiveBlockClass = 0x808077EBU;
|
|
|
|
|
+/** Objective block +0x1C holds a 16-bit item-table index, not a definition hash. */
|
|
|
constexpr std::size_t kObjectiveParentItemOffset = 0x1C;
|
|
constexpr std::size_t kObjectiveParentItemOffset = 0x1C;
|
|
|
|
|
+/** Objective array entries are 16-bit table indices. */
|
|
|
constexpr std::size_t kObjectiveReferenceStride = sizeof(std::uint16_t);
|
|
constexpr std::size_t kObjectiveReferenceStride = sizeof(std::uint16_t);
|
|
|
|
|
|
|
|
|
|
+/** Item +0x60 holds the quest-set block offset relative to that field. */
|
|
|
constexpr std::size_t kItemQuestSetBlockOffset = 0x60;
|
|
constexpr std::size_t kItemQuestSetBlockOffset = 0x60;
|
|
|
|
|
+/** This block holds ordered quest members and the value slot that selects the active step. */
|
|
|
constexpr std::uint32_t kQuestSetBlockClass = 0x808077C8U;
|
|
constexpr std::uint32_t kQuestSetBlockClass = 0x808077C8U;
|
|
|
|
|
+/** Set +0x10 holds a 16-bit unlock value slot; a map supplies its saved bank row. */
|
|
|
constexpr std::size_t kQuestSetValueSlotOffset = 0x10;
|
|
constexpr std::size_t kQuestSetValueSlotOffset = 0x10;
|
|
|
|
|
+/** Set +0x1C holds a one-byte mode separate from the member values. */
|
|
|
constexpr std::size_t kQuestSetModeOffset = 0x1C;
|
|
constexpr std::size_t kQuestSetModeOffset = 0x1C;
|
|
|
-/** Only this authored mode is supported; no semantics are assumed for other modes. */
|
|
|
|
|
|
|
+/** Policy: only mode 1 permits first-step writes; the other modes are not decoded. */
|
|
|
constexpr std::uint8_t kSupportedQuestSetMode = 1;
|
|
constexpr std::uint8_t kSupportedQuestSetMode = 1;
|
|
|
|
|
+/** Each member pairs a signed step value with the item that represents it. */
|
|
|
constexpr std::uint32_t kQuestSetMemberClass = 0x808077CAU;
|
|
constexpr std::uint32_t kQuestSetMemberClass = 0x808077CAU;
|
|
|
|
|
+/** A member is a 32-bit value, a 16-bit item index, then a 16-bit reserved field. */
|
|
|
constexpr std::size_t kQuestSetMemberStride = 8;
|
|
constexpr std::size_t kQuestSetMemberStride = 8;
|
|
|
|
|
+/** Member +0 holds the signed step identifier; values are not ordered progress counts. */
|
|
|
constexpr std::size_t kQuestSetMemberValueOffset = 0;
|
|
constexpr std::size_t kQuestSetMemberValueOffset = 0;
|
|
|
|
|
+/** Member +4 holds the item-table index for that step. */
|
|
|
constexpr std::size_t kQuestSetMemberItemOffset = 4;
|
|
constexpr std::size_t kQuestSetMemberItemOffset = 4;
|
|
|
|
|
+/** Only member rows whose final 16 bits are zero are supported. */
|
|
|
constexpr std::size_t kQuestSetMemberReservedOffset = 6;
|
|
constexpr std::size_t kQuestSetMemberReservedOffset = 6;
|
|
|
|
|
|
|
|
|
|
+/** Item +0x90 holds the unlock block offset relative to that field; zero means absent. */
|
|
|
constexpr std::size_t kItemUnlockBlockOffset = 0x90;
|
|
constexpr std::size_t kItemUnlockBlockOffset = 0x90;
|
|
|
|
|
+/** This block's first array names the flags supplied by item presence. */
|
|
|
constexpr std::uint32_t kItemUnlockBlockClass = 0x808077ABU;
|
|
constexpr std::uint32_t kItemUnlockBlockClass = 0x808077ABU;
|
|
|
|
|
+/** Presence-flag entries hold unlock slot indices, not saved bank rows. */
|
|
|
constexpr std::uint32_t kItemPresenceFlagClass = 0x80807D4BU;
|
|
constexpr std::uint32_t kItemPresenceFlagClass = 0x80807D4BU;
|
|
|
|
|
+/** Each presence-flag entry occupies one 16-bit slot. */
|
|
|
constexpr std::size_t kItemPresenceFlagStride = sizeof(std::uint16_t);
|
|
constexpr std::size_t kItemPresenceFlagStride = sizeof(std::uint16_t);
|
|
|
/** Authored value/flag slots must fit the nonnegative range of a signed 16-bit mapping. */
|
|
/** Authored value/flag slots must fit the nonnegative range of a signed 16-bit mapping. */
|
|
|
constexpr std::uint16_t kUnlockSlotLimit = 0x8000U;
|
|
constexpr std::uint16_t kUnlockSlotLimit = 0x8000U;
|
|
|
-/** Separate objective-free roots are supported only in this native bucket. */
|
|
|
|
|
|
|
+/** Policy: pursuits without presence flags need a bucket-37 root with no objective block. */
|
|
|
constexpr std::uint8_t kSeparateQuestRootBucketId = 37;
|
|
constexpr std::uint8_t kSeparateQuestRootBucketId = 37;
|
|
|
|
|
|
|
|
-/** The remaining maps are scanned to reject matches outside account/character state. */
|
|
|
|
|
|
|
+/** Map +40 has no supported save bank; a matching slot makes initialization unsafe. */
|
|
|
constexpr std::size_t kThirdValueMapDescriptor = 40;
|
|
constexpr std::size_t kThirdValueMapDescriptor = 40;
|
|
|
|
|
+/** Map +56 is also checked for duplicate slots but has no supported save bank. */
|
|
|
constexpr std::size_t kFourthValueMapDescriptor = 56;
|
|
constexpr std::size_t kFourthValueMapDescriptor = 56;
|
|
|
-constexpr std::size_t kValueMapRowStride = 8;
|
|
|
|
|
-constexpr std::size_t kValueMapSlotOffset = 4;
|
|
|
|
|
|
|
+/** A matching map row is supported only when its final 16 bits are zero. */
|
|
|
constexpr std::size_t kValueMapReservedOffset = 6;
|
|
constexpr std::size_t kValueMapReservedOffset = 6;
|
|
|
|
|
+/** The all-one 16-bit row is reserved and cannot name saved state. */
|
|
|
constexpr std::uint16_t kUnavailableValueMapRow = 0xFFFFU;
|
|
constexpr std::uint16_t kUnavailableValueMapRow = 0xFFFFU;
|
|
|
|
|
|
|
|
-/** These are serialized block pointers, not count/relative array descriptors. */
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * A block pointer is relative to its own field; its class ID precedes the payload.
|
|
|
|
|
+ * @param bytes Blob containing the pointer and block.
|
|
|
|
|
+ * @param field Offset of the signed 64-bit block pointer.
|
|
|
|
|
+ * @param expectedClass Required serialized block class.
|
|
|
|
|
+ * @param offset Receives the payload offset; use only on success.
|
|
|
|
|
+ * @return False for absent, out-of-bounds, short, or wrong-class blocks.
|
|
|
|
|
+ */
|
|
|
[[nodiscard]] bool block(std::span<const std::byte> bytes,
|
|
[[nodiscard]] bool block(std::span<const std::byte> bytes,
|
|
|
std::size_t field,
|
|
std::size_t field,
|
|
|
std::uint32_t expectedClass,
|
|
std::uint32_t expectedClass,
|
|
@@ -72,7 +98,15 @@ constexpr std::uint16_t kUnavailableValueMapRow = 0xFFFFU;
|
|
|
return read(bytes, offset - kBlockClassPrefixSize, actualClass) && actualClass == expectedClass;
|
|
return read(bytes, offset - kBlockClassPrefixSize, actualClass) && actualClass == expectedClass;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/** Bounds an authored array using its element class and fixed stride. */
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * The whole fixed-stride array must fit the blob before any row is read.
|
|
|
|
|
+ * @param bytes Blob containing the descriptor and rows.
|
|
|
|
|
+ * @param field Offset of the array descriptor.
|
|
|
|
|
+ * @param expectedClass Required serialized element class.
|
|
|
|
|
+ * @param stride Nonzero byte width of one row.
|
|
|
|
|
+ * @param rows Receives the array bounds; use only on success.
|
|
|
|
|
+ * @return False when the descriptor, class, or row bounds are invalid.
|
|
|
|
|
+ */
|
|
|
[[nodiscard]] bool array(std::span<const std::byte> bytes,
|
|
[[nodiscard]] bool array(std::span<const std::byte> bytes,
|
|
|
std::size_t field,
|
|
std::size_t field,
|
|
|
std::uint32_t expectedClass,
|
|
std::uint32_t expectedClass,
|
|
@@ -85,6 +119,11 @@ constexpr std::uint16_t kUnavailableValueMapRow = 0xFFFFU;
|
|
|
|
|
|
|
|
} // namespace
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Only an objective-bearing pursuit can name a quest-set owner.
|
|
|
|
|
+ * @param definition Item definition bytes, including its nested blocks.
|
|
|
|
|
+ * @return The set owner's item-table index, or kUnavailableQuestParent on rejection.
|
|
|
|
|
+ */
|
|
|
std::uint16_t quest_parent(std::span<const std::byte> definition) noexcept {
|
|
std::uint16_t quest_parent(std::span<const std::byte> definition) noexcept {
|
|
|
std::uint8_t bucket = 0;
|
|
std::uint8_t bucket = 0;
|
|
|
std::size_t objective = 0;
|
|
std::size_t objective = 0;
|
|
@@ -105,6 +144,15 @@ std::uint16_t quest_parent(std::span<const std::byte> definition) noexcept {
|
|
|
return parent;
|
|
return parent;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Only a unique first member with one supported save-bank mapping may start a quest.
|
|
|
|
|
+ * @param definition Pursuit item being acquired.
|
|
|
|
|
+ * @param itemIndex Pursuit's item-table index.
|
|
|
|
|
+ * @param parent Set-owner bytes selected by quest_parent; may be definition itself.
|
|
|
|
|
+ * @param itemCount Exclusive bound for item-table indices.
|
|
|
|
|
+ * @param valueMap Blob containing all four unlock value maps.
|
|
|
|
|
+ * @return The first-step value and bank row, or an empty plan for unsupported content.
|
|
|
|
|
+ */
|
|
|
Quest read_quest_initialization(std::span<const std::byte> definition,
|
|
Quest read_quest_initialization(std::span<const std::byte> definition,
|
|
|
std::uint16_t itemIndex,
|
|
std::uint16_t itemIndex,
|
|
|
std::span<const std::byte> parent,
|
|
std::span<const std::byte> parent,
|
|
@@ -145,9 +193,7 @@ Quest read_quest_initialization(std::span<const std::byte> definition,
|
|
|
return {};
|
|
return {};
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- // A separate bucket-37 set root can track a pursuit without an item-presence flag.
|
|
|
|
|
- // Only the objective-free root / character-value form is supported here; this is
|
|
|
|
|
- // first-step initialization, not a general interaction or eligibility evaluator.
|
|
|
|
|
|
|
+ // Without presence flags, require a separate objective-free root and a character value.
|
|
|
const bool separateRoot = flags.count == 0;
|
|
const bool separateRoot = flags.count == 0;
|
|
|
if (separateRoot) {
|
|
if (separateRoot) {
|
|
|
std::uint8_t parentBucket = 0;
|
|
std::uint8_t parentBucket = 0;
|
|
@@ -177,14 +223,14 @@ Quest read_quest_initialization(std::span<const std::byte> definition,
|
|
|
++matches;
|
|
++matches;
|
|
|
quest.value = value;
|
|
quest.value = value;
|
|
|
} else if (i != 0 && matches != 0 && value == quest.value) {
|
|
} else if (i != 0 && matches != 0 && value == quest.value) {
|
|
|
- return {}; // Two steps cannot give the initial identifier an unambiguous meaning.
|
|
|
|
|
|
|
+ return {}; // The first value must identify only one step.
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
if (matches != 1) {
|
|
if (matches != 1) {
|
|
|
return {};
|
|
return {};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Resolve across all four maps: a context/roster-lane match or duplicate is unsupported.
|
|
|
|
|
|
|
+ // A slot must match once across all maps, including maps with no supported save bank.
|
|
|
matches = 0;
|
|
matches = 0;
|
|
|
for (const std::size_t descriptor : {kAccountValueMapDescriptor,
|
|
for (const std::size_t descriptor : {kAccountValueMapDescriptor,
|
|
|
kCharacterValueMapDescriptor,
|
|
kCharacterValueMapDescriptor,
|
|
@@ -192,17 +238,17 @@ Quest read_quest_initialization(std::span<const std::byte> definition,
|
|
|
kFourthValueMapDescriptor}) {
|
|
kFourthValueMapDescriptor}) {
|
|
|
Array rows{};
|
|
Array rows{};
|
|
|
if (!find_optional_array_at(valueMap, descriptor, rows) || rows.dataOffset > valueMap.size()
|
|
if (!find_optional_array_at(valueMap, descriptor, rows) || rows.dataOffset > valueMap.size()
|
|
|
- || rows.count > (valueMap.size() - rows.dataOffset) / kValueMapRowStride) {
|
|
|
|
|
|
|
+ || rows.count > (valueMap.size() - rows.dataOffset) / kUnlockMapRowStride) {
|
|
|
return {};
|
|
return {};
|
|
|
}
|
|
}
|
|
|
for (std::size_t i = 0; i < rows.count; ++i) {
|
|
for (std::size_t i = 0; i < rows.count; ++i) {
|
|
|
std::int16_t mappedSlot = -1;
|
|
std::int16_t mappedSlot = -1;
|
|
|
std::uint16_t reserved = 0;
|
|
std::uint16_t reserved = 0;
|
|
|
if (!read(valueMap,
|
|
if (!read(valueMap,
|
|
|
- rows.dataOffset + i * kValueMapRowStride + kValueMapSlotOffset,
|
|
|
|
|
|
|
+ rows.dataOffset + i * kUnlockMapRowStride + kUnlockMapDestinationSlotOffset,
|
|
|
mappedSlot)
|
|
mappedSlot)
|
|
|
|| !read(valueMap,
|
|
|| !read(valueMap,
|
|
|
- rows.dataOffset + i * kValueMapRowStride + kValueMapReservedOffset,
|
|
|
|
|
|
|
+ rows.dataOffset + i * kUnlockMapRowStride + kValueMapReservedOffset,
|
|
|
reserved)) {
|
|
reserved)) {
|
|
|
return {};
|
|
return {};
|
|
|
}
|
|
}
|