Просмотр исходного кода

[4] fix(content): locate the bucket-definition table by its class

Review: hardcoded stuff in package_root_tables -> this should be
extracted at runtime and not hardcoded.
Resolution: the bucket-definition table is found by sweeping for the
class its entry record carries, not by naming the tag it holds in one
install.

read_bucket_equipment_slots resolved the table through
kBucketDefinitionTableTag, an absolute 0x81327D66 written into the
source. A tag is a package-local handle: it names one entry of one
package as that package was built, so a repack moves it and the pass
reads whatever now sits at the old handle, or fails. Every other table
here is reached through the investment root, so this was the one place
naming a package directly.

The class is the part that does not move. It belongs to the definition
ABI, and the entry record already carries it, which is why read_tag can
report it without a second read. scan_class sweeps the installed entry
tables for one class; entry tables are plain file data, so the sweep
needs no block keys and runs before any are known. The pass already
validated the class after resolving the tag, so the class was the real
identity of the table and the tag only indexed one install.

Requiring exactly one match keeps it fail-closed: a build carrying the
class more than once refuses the relation under a named table_sweep
stage rather than picking a table by position. The class the reader
reports is still checked, because the sweep and the reader resolve the
entry through independent paths.

Swept against the installed packages: 508 packages, 2801092 entries, one
match, and it is the tag that was hardcoded. Extraction publishes the
same 18 mapped slots.

kBucketByEquipmentSlot is unchanged and still hardcoded. The bucket id
is not in the 72-byte definition record, its descriptors carry no
matching hash, the rows are not positionally paired, and the installed
item rows report no equipment slot at all, so nothing in the packages
has been found to derive it from yet.
Thomas Shields 3 недель назад
Родитель
Сommit
47096dd669

+ 49 - 5
Sunrise/src/client/content/items/packages/package_root_tables.cpp

@@ -40,6 +40,48 @@ find_bucket(std::span<const buckets::Descriptor> descriptors, std::uint8_t bucke
     return found;
     return found;
 }
 }
 
 
+/** The first tag a class sweep reported and how many entries carried the class. */
+struct BucketDefinitionTable {
+    std::uint32_t tag{};
+    std::size_t matches{};
+};
+
+/** @param context Sweep result. @param tag One matching tag. @return Always true, to count all. */
+bool collect_bucket_definition_tag(void* context, std::uint32_t tag) noexcept {
+    auto* located = static_cast<BucketDefinitionTable*>(context);
+    if (located->matches == 0) {
+        located->tag = tag;
+    }
+    ++located->matches;
+    return true;
+}
+
+/**
+ * Locates the bucket-definition table by the class its entry record carries.
+ * A tag is a package-local handle that a repack moves, so naming one pins the extraction to a
+ * single install. The class belongs to the definition ABI, so it names the table wherever it was
+ * packed. Entry tables are plain file data, so the sweep needs no block keys.
+ * @param source Package source.
+ * @param tag Receives the located tag.
+ * @return True when exactly one installed entry carries the class.
+ */
+[[nodiscard]] bool find_bucket_definition_table(const reader::Source& source,
+                                                std::uint32_t& tag) noexcept {
+    BucketDefinitionTable located{};
+    reader::ScanResult scanned{};
+    if (!reader::scan_class(source.directory,
+                            tables::kBucketDefinitionTableClass,
+                            &collect_bucket_definition_tag,
+                            &located,
+                            scanned)
+        || located.matches != 1) {
+        report_bucket_equipment_failure("table_sweep", located.matches, scanned.packages);
+        return false;
+    }
+    tag = located.tag;
+    return true;
+}
+
 /**
 /**
  * Extracts and validates the installed bucket/equipment-slot relation.
  * Extracts and validates the installed bucket/equipment-slot relation.
  * The table contains inline 72-byte records; its array elements are not index rows or tag links.
  * The table contains inline 72-byte records; its array elements are not index rows or tag links.
@@ -50,16 +92,18 @@ find_bucket(std::span<const buckets::Descriptor> descriptors, std::uint8_t bucke
     std::span<const buckets::Descriptor> descriptors,
     std::span<const buckets::Descriptor> descriptors,
     std::array<std::int8_t, buckets::kDescriptorCapacity>& equipmentSlots) noexcept {
     std::array<std::int8_t, buckets::kDescriptorCapacity>& equipmentSlots) noexcept {
     equipmentSlots.fill(buckets::kUnavailableEquipmentSlot);
     equipmentSlots.fill(buckets::kUnavailableEquipmentSlot);
+    std::uint32_t tableTag = 0;
+    if (!find_bucket_definition_table(source, tableTag)) {
+        return false;
+    }
     std::uint32_t tableClass = 0;
     std::uint32_t tableClass = 0;
     tables::Array table{};
     tables::Array table{};
-    if (!reader::read_tag(source,
-                          storage.scratch,
-                          tables::kBucketDefinitionTableTag,
-                          storage.child,
-                          tableClass)) {
+    if (!reader::read_tag(source, storage.scratch, tableTag, storage.child, tableClass)) {
         report_bucket_equipment_failure("table_read", 0, 0);
         report_bucket_equipment_failure("table_read", 0, 0);
         return false;
         return false;
     }
     }
+    // The sweep and the reader resolve the installed entry independently, so the class the reader
+    // reports still has to agree with the one the table was selected by.
     if (tableClass != tables::kBucketDefinitionTableClass) {
     if (tableClass != tables::kBucketDefinitionTableClass) {
         report_bucket_equipment_failure("table_class", tableClass, 0);
         report_bucket_equipment_failure("table_class", tableClass, 0);
         return false;
         return false;

+ 5 - 3
Sunrise/src/middleware/content/packages/tables/definition_index_table.h

@@ -176,9 +176,11 @@ inline constexpr std::size_t kSocketEntryListTableSlot = 97;
 inline constexpr std::size_t kInvestmentRootChild = 0;
 inline constexpr std::size_t kInvestmentRootChild = 0;
 /** The investment root holds the inventory bucket table at this slot. */
 /** The investment root holds the inventory bucket table at this slot. */
 inline constexpr std::size_t kBucketTableSlot = 17;
 inline constexpr std::size_t kBucketTableSlot = 17;
-/** Installed bucket-definition table pairing inventory buckets with native equipment slots. */
-inline constexpr std::uint32_t kBucketDefinitionTableTag = 0x81327D66U;
-/** Package class of the bucket-definition index table. */
+/**
+ * Package class of the bucket-definition table pairing inventory buckets with native equipment
+ * slots. Exactly one installed entry carries it, so a class sweep locates the table without
+ * naming the tag it happens to hold in one install.
+ */
 inline constexpr std::uint32_t kBucketDefinitionTableClass = 0x80805936U;
 inline constexpr std::uint32_t kBucketDefinitionTableClass = 0x80805936U;
 /** The installed table contains one row for each item-bearing bucket definition. */
 /** The installed table contains one row for each item-bearing bucket definition. */
 inline constexpr std::size_t kBucketDefinitionCount = 34;
 inline constexpr std::size_t kBucketDefinitionCount = 34;