Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ is_performance_descriptor(const topology::Snapshot& topology,
return std::tie(row.sceneSlotIndex, row.configTag, row.descriptorOffset, row.squadSlotIndex);
}

/** Sort key matching the final event-key pack order: the scene, then the gate. */
[[nodiscard]] inline auto event_key_natural(const EventKey& row) noexcept {
return std::tie(row.slotIndex, row.gateOffset);
}

/** Sort key matching the final task-target pack order. */
[[nodiscard]] inline auto task_natural(const TaskTarget& row) noexcept {
return std::tie(row.taskSlotIndex, row.configTag, row.descriptorOffset);
Expand Down Expand Up @@ -176,6 +181,13 @@ schema_index(const topology::Snapshot& topology, const Facts& facts, SchemaIndex
const squad::DescriptorFact& descriptor,
Text& output) noexcept;

/** Formats one event-key ID from the resource, its graph, the scene slot and the gate. */
[[nodiscard]] bool event_key_id(const topology::Snapshot& topology,
const squad::DescriptorFact& descriptor,
std::uint32_t graphTag,
std::uint32_t gateOffset,
Text& output) noexcept;

/** Formats one scene-to-squad edge ID from its descriptor tuple and the squad slot it names. */
[[nodiscard]] bool edge_id(const topology::Snapshot& topology,
const squad::DescriptorFact& descriptor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ bool resource_id(const topology::Snapshot& topology,
static_cast<unsigned>(slot.slotType));
}

/** Formats one event-key ID from the resource, its graph, the scene slot and the gate. */
bool event_key_id(const topology::Snapshot& topology,
const squad::DescriptorFact& descriptor,
std::uint32_t graphTag,
std::uint32_t gateOffset,
Text& output) noexcept {
if (descriptor.slotIndex >= topology.slots.size()) {
return false;
}
const topology::Slot& slot = topology.slots[descriptor.slotIndex];
return format_text(output,
"authored-scene-event-key/%08x/%08x/%04x/%04x/%08x",
static_cast<unsigned>(descriptor.configTag),
static_cast<unsigned>(graphTag),
static_cast<unsigned>(slot.slotIndex),
static_cast<unsigned>(slot.slotType),
static_cast<unsigned>(gateOffset));
}

/** Formats one scene-to-squad edge ID from its descriptor tuple and the squad slot it names. */
bool edge_id(const topology::Snapshot& topology,
const squad::DescriptorFact& descriptor,
Expand Down Expand Up @@ -219,6 +238,59 @@ void log_scene_resource(const squad::DescriptorFact& descriptor,
}
}

/** Logs a scene graph that yielded no event keys, and why. */
void log_scene_graph(const squad::DescriptorFact& descriptor,
std::uint32_t graphTag,
const char* result,
core::log::Level level) noexcept {
std::array<char, 176> line{};
const int written = std::snprintf(line.data(),
line.size(),
"ev=activity_sdk_scene_graph result=%s config=0x%08X "
"slot_row=%u graph=0x%08X",
result,
static_cast<unsigned>(descriptor.configTag),
static_cast<unsigned>(descriptor.slotIndex),
static_cast<unsigned>(graphTag));
if (written > 0) {
core::log::write(
core::log::Channel::client,
level,
{line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1U)});
}
}

/**
* Reads the gate table field of a scene graph.
* @param blob Graph bytes.
* @param rows Receives the offset of the first table row.
* @param count Receives the row count.
* @return False when the field does not hold a table of gate rows that fits the graph.
*/
[[nodiscard]] bool
read_gate_table(std::span<const std::byte> blob, std::size_t& rows, std::uint64_t& count) noexcept {
std::int64_t relative = 0;
std::uint32_t marker = 0;
std::uint64_t repeated = 0;
std::uint32_t rowClass = 0;
const std::size_t pointer = format::kAuthoredSceneGateTableOffset + 8U;
if (!read_value(blob, format::kAuthoredSceneGateTableOffset, count)
|| !read_value(blob, pointer, relative) || relative <= 0
|| static_cast<std::uint64_t>(relative) > blob.size() - pointer) {
return false;
}
// The pointer is relative to its own field and lands on the count of the array header.
const std::size_t header = pointer + static_cast<std::size_t>(relative);
if (header < 4U || !read_value(blob, header - 4U, marker)
|| marker != format::kPackageArrayMarker || !read_value(blob, header, repeated)
|| repeated != count || !read_value(blob, header + 8U, rowClass)
|| rowClass != format::kAuthoredSceneGateRowClass) {
return false;
}
rows = header + 16U;
return rows <= blob.size() && count <= (blob.size() - rows) / format::kAuthoredSceneGateRowSize;
}

/** Reads one tag once and retains the physical class beside its bytes. */
[[nodiscard]] bool package_row(squad::TagReader reader,
void* readerContext,
Expand Down Expand Up @@ -312,6 +384,94 @@ void log_scene_resource(const squad::DescriptorFact& descriptor,
return true;
}

/**
* Follows a scene resource to its event graph and appends one row per gate.
* A graph that cannot be read, or one gate that is not a gate element, leaves the scene with no
* keys and is logged. Only a topology inconsistency fails the build.
*/
[[nodiscard]] bool collect_event_keys(const topology::Snapshot& topology,
const squad::DescriptorFact& descriptor,
squad::TagReader reader,
void* readerContext,
PackageCache& cache,
std::uint32_t resourceTag,
std::span<const std::byte> resource,
std::vector<EventKey>& output) {
const std::size_t start = output.size();
std::uint32_t graphTag = 0;
if (!read_value(resource, format::kAuthoredSceneGraphRelativeOffset, graphTag) || graphTag == 0
|| graphTag == format::kAbsentIndex) {
log_scene_graph(descriptor, graphTag, "unreferenced", core::log::Level::debug);
return true;
}
const PackageRow* graph = nullptr;
if (!package_row(reader, readerContext, graphTag, cache, graph) || graph == nullptr) {
log_scene_graph(descriptor, graphTag, "unreadable", core::log::Level::warn);
return true;
}
if (graph->classId != format::kAuthoredSceneGraphClass) {
log_scene_graph(descriptor, graphTag, "graph_class", core::log::Level::warn);
return true;
}
const auto blob = std::span(graph->bytes);
std::size_t rows = 0;
std::uint64_t count = 0;
if (!read_gate_table(blob, rows, count)) {
log_scene_graph(descriptor, graphTag, "gate_table", core::log::Level::warn);
return true;
}
if (count > format::kAuthoredSceneGateCapacity) {
log_scene_graph(descriptor, graphTag, "capacity", core::log::Level::warn);
return true;
}
for (std::uint64_t gate = 0; gate < count; ++gate) {
const std::size_t tableRow =
rows + static_cast<std::size_t>(gate) * format::kAuthoredSceneGateRowSize;
std::uint32_t rowOwner = 0;
std::uint32_t rowClass = 0;
std::uint64_t body = 0;
// A row names its body by absolute offset; the body names the row class back.
if (!read_value(blob, tableRow, rowOwner)
|| !read_value(blob, tableRow + format::kAuthoredSceneGateClassOffset, rowClass)
|| !read_value(blob, tableRow + format::kAuthoredSceneGateBodyOffset, body)
|| rowOwner != graphTag || rowClass != format::kAuthoredSceneGateBodyClass
|| body > blob.size() - format::kAuthoredSceneGateSize) {
output.resize(start);
log_scene_graph(descriptor, graphTag, "gate_row", core::log::Level::warn);
return true;
}
const auto element = static_cast<std::size_t>(body);
std::uint32_t owner = 0;
std::uint32_t bodyClass = 0;
std::uint32_t key = 0;
std::int32_t ordinal = 0;
if (!read_value(blob, element, owner)
|| !read_value(blob, element + format::kAuthoredSceneGateClassOffset, bodyClass)
|| !read_value(blob, element + format::kAuthoredSceneGateKeyOffset, key)
|| !read_value(blob, element + format::kAuthoredSceneGateOrdinalOffset, ordinal)
|| owner != graphTag || bodyClass != format::kAuthoredSceneGateRowClass || key == 0
|| key == format::kAbsentIndex) {
output.resize(start);
log_scene_graph(descriptor, graphTag, "gate", core::log::Level::warn);
return true;
}
EventKey row{};
if (!event_key_id(
topology, descriptor, graphTag, static_cast<std::uint32_t>(element), row.id)) {
return false;
}
row.slotIndex = descriptor.slotIndex;
row.resourceTag = resourceTag;
row.graphTag = graphTag;
row.gateOffset = static_cast<std::uint32_t>(element);
row.ordinal = ordinal;
row.key = key;
row.flags = format::kAuthoredSceneEventKeyExact;
output.push_back(row);
}
return true;
}

} // namespace

/** Builds both authored-scene sections from complete topology and package facts. */
Expand Down Expand Up @@ -525,6 +685,16 @@ bool build(const topology::Snapshot& topology,
row.flags = format::kAuthoredSceneResourceExact;
pending.resources.push_back(row);
}
if (!collect_event_keys(topology,
descriptor,
reader,
readerContext,
cache,
resourceTag,
std::span(resourcePackage->bytes),
pending.eventKeys)) {
return false;
}
}
}

Expand Down Expand Up @@ -635,6 +805,11 @@ bool build(const topology::Snapshot& topology,
}
}
std::sort(pending.resources.begin(), pending.resources.end(), resource_less);
std::sort(pending.eventKeys.begin(),
pending.eventKeys.end(),
[](const EventKey& left, const EventKey& right) {
return event_key_natural(left) < event_key_natural(right);
});
std::sort(pending.squadEdges.begin(), pending.squadEdges.end(), edge_less);
std::sort(pending.taskTargets.begin(), pending.taskTargets.end(), task_less);
pending.resources.erase(std::unique(pending.resources.begin(),
Expand All @@ -644,6 +819,13 @@ bool build(const topology::Snapshot& topology,
== resource_natural(right);
}),
pending.resources.end());
pending.eventKeys.erase(std::unique(pending.eventKeys.begin(),
pending.eventKeys.end(),
[](const auto& left, const auto& right) {
return event_key_natural(left)
== event_key_natural(right);
}),
pending.eventKeys.end());
pending.squadEdges.erase(std::unique(pending.squadEdges.begin(),
pending.squadEdges.end(),
[](const auto& left, const auto& right) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ struct Resource final {
std::uint32_t reserved{};
};

/** One event gate of a scene's graph before its ID is linked. */
struct EventKey final {
Text id{};
std::uint32_t slotIndex{};
std::uint32_t resourceTag{};
std::uint32_t graphTag{};
std::uint32_t gateOffset{};
std::int32_t ordinal{};
std::uint32_t key{};
std::uint32_t flags{};
std::uint32_t reserved{};
};

/** One exact format-v12 same-object scene-to-squad edge before its ID is linked. */
struct SquadEdge final {
Text id{};
Expand Down Expand Up @@ -75,11 +88,12 @@ struct DialogueCueText final {
std::uint32_t stringHash{};
};

/** One exact type-68 HUD element and its two authored localized fields. */
/** One exact type-68 HUD element and its authored localized fields; absent ones stay empty. */
struct DirectiveElement final {
Text id{};
Text title{};
Text description{};
Text progress{};
std::uint32_t slotIndex{};
std::uint32_t nameHash{};
std::int32_t elementIndex{};
Expand All @@ -88,6 +102,9 @@ struct DirectiveElement final {
std::uint32_t titleStringHash{};
std::uint32_t descriptionContainerTag{};
std::uint32_t descriptionStringHash{};
std::uint32_t progressContainerTag{};
std::uint32_t progressStringHash{};
std::uint32_t flags{};
};

/** Scene-owned descriptor facts retain a complete source universe per selected object. */
Expand All @@ -101,6 +118,8 @@ struct Facts final {
/** Canonical section-19 and section-22 rows with unresolved string references. */
struct Snapshot final {
std::vector<Resource> resources{};
/** Every gate of every resourced scene, in slot then gate order. */
std::vector<EventKey> eventKeys{};
std::vector<SquadEdge> squadEdges{};
std::vector<TaskTarget> taskTargets{};
std::vector<DialogueCueText> dialogueCueTexts{};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,31 @@ bool validate(const topology::Snapshot& topology,
return false;
}
}
for (std::size_t index = 0; index < snapshot.eventKeys.size(); ++index) {
const EventKey& row = snapshot.eventKeys[index];
// A key belongs to a resourced scene and names the resource its graph hangs off.
const auto resource = std::find_if(
snapshot.resources.begin(),
snapshot.resources.end(),
[&row](const Resource& candidate) { return candidate.slotIndex == row.slotIndex; });
const squad::DescriptorFact* descriptor = resource == snapshot.resources.end()
? nullptr
: find_descriptor(descriptors,
resource->slotIndex,
resource->configTag,
resource->descriptorOffset);
Text expectedId{};
if (descriptor == nullptr || resource->resourceTag != row.resourceTag || row.graphTag == 0
|| row.graphTag == format::kAbsentIndex || row.key == 0
|| row.key == format::kAbsentIndex || row.flags != format::kAuthoredSceneEventKeyExact
|| row.reserved != 0
|| !event_key_id(topology, *descriptor, row.graphTag, row.gateOffset, expectedId)
|| expectedId.value != row.id.value || expectedId.length != row.id.length
|| (index != 0
&& event_key_natural(snapshot.eventKeys[index - 1]) >= event_key_natural(row))) {
return false;
}
}
for (std::size_t index = 0; index < snapshot.squadEdges.size(); ++index) {
const SquadEdge& row = snapshot.squadEdges[index];
const squad::DescriptorFact* descriptor =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ struct Source final {
std::span<const ScenarioWorldSource> scenarioWorldSources{};
std::span<const format::CombatObjectiveGroup> combatObjectiveGroups{};
std::span<const format::ActorAbility> actorAbilities{};
std::span<const format::AuthoredSceneEventKey> authoredSceneEventKeys{};
};

/** One readable generated Lua module with a filesystem-safe canonical name. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct RowSpec final {
};

/** Every StringRef field declared by the row table below. */
constexpr std::size_t kDecodedStringFieldCount = 54;
constexpr std::size_t kDecodedStringFieldCount = 55;
/** Section index of the activity-owned binding-tag rows, which have no direct collection. */
constexpr std::uint32_t kBindingTagSectionIndex = 24;
/** Section index of the binding-locator rows, projected both directly and per activity. */
Expand Down Expand Up @@ -175,7 +175,8 @@ constexpr std::array<RowSpec, 40> kRows{{
"CatalogDirectiveElementView",
"s:id;s:title;s:description;u32:slot_index;u32:name_hash;i32:element_index;"
"u32:element_count;u32:title_container_tag;u32:title_string_hash;"
"u32:description_container_tag;u32:description_string_hash"},
"u32:description_container_tag;u32:description_string_hash;s:progress;"
"u32:progress_container_tag;u32:progress_string_hash;u32:flags"},
{25,
"activity_binding_locators",
"activity_binding_locators",
Expand Down
Loading
Loading