Przeglądaj źródła

fix activity data for override

stan 3 tygodni temu
rodzic
commit
8eac879cdd

+ 15 - 5
Sunrise/src/middleware/bap/activity_host_manager/request/selection/activity_manager_descriptor_parser.cpp

@@ -109,10 +109,13 @@ constexpr std::size_t kPairEntryWidth = 13;
  * The field is always 40 elements and its tail is padding, so a byte that cannot appear in a
  * name ends the name instead of failing the descriptor and losing the destination choice.
  * @param reader Bounded MSB-first descriptor reader.
+ * @param descriptorStart Reader bits left at the descriptor's first bit.
  * @param selection Temporary scalar-only selection output.
  * @return True when the presence marker and all 40 biased bytes are complete.
  */
-[[nodiscard]] bool read_package_name(Reader& reader, ActivityManagerSelection& selection) noexcept {
+[[nodiscard]] bool read_package_name(Reader& reader,
+                                     std::size_t descriptorStart,
+                                     ActivityManagerSelection& selection) noexcept {
     bool present = false;
     if (!read_presence(reader, present)) {
         return false;
@@ -120,6 +123,8 @@ constexpr std::size_t kPairEntryWidth = 13;
     if (!present) {
         return true;
     }
+    // A forced destination rewrites these bytes in place, so where they sit is kept with them.
+    const std::size_t nameStart = descriptorStart - reader.remaining_bits();
     bool ended = false;
     std::uint8_t length = 0;
     for (std::int8_t& character : selection.packageName) {
@@ -137,6 +142,7 @@ constexpr std::size_t kPairEntryWidth = 13;
     }
     selection.hasPackageName = true;
     selection.packageNameLength = length;
+    selection.packageNameBitOffset = nameStart;
     return true;
 }
 
@@ -157,15 +163,18 @@ constexpr std::size_t kPairEntryWidth = 13;
 /**
  * Reads the safe destination fields and skips the unknown fixed and optional scalars.
  * @param reader Bounded MSB-first descriptor reader.
+ * @param descriptorStart Reader bits left at the descriptor's first bit.
  * @param selection Temporary scalar-only selection output.
  * @return True when every destination field is complete.
  */
-[[nodiscard]] bool read_destination(Reader& reader, ActivityManagerSelection& selection) noexcept {
+[[nodiscard]] bool read_destination(Reader& reader,
+                                    std::size_t descriptorStart,
+                                    ActivityManagerSelection& selection) noexcept {
     return reader.skip(kUnknownByteWidth) && skip_optional(reader, kUnknownByteWidth)
            && read_hash(reader, selection.hasArrivalBubbleHash, selection.arrivalBubbleHash)
            && read_hash(reader, selection.hasSpawnSetHash, selection.spawnSetHash)
-           && read_package_name(reader, selection) && skip_optional(reader, kUnknownHashWidth)
-           && reader.skip(kBooleanWidth);
+           && read_package_name(reader, descriptorStart, selection)
+           && skip_optional(reader, kUnknownHashWidth) && reader.skip(kBooleanWidth);
 }
 
 /**
@@ -202,7 +211,8 @@ constexpr std::size_t kPairEntryWidth = 13;
 
 /** Reads one complete descriptor into safe scalar-only output. */
 bool parse(Reader& reader, ActivityManagerSelection& selection) noexcept {
-    return read_prefix(reader, selection) && read_destination(reader, selection)
+    const std::size_t descriptorStart = reader.remaining_bits();
+    return read_prefix(reader, selection) && read_destination(reader, descriptorStart, selection)
            && skip_tail(reader);
 }
 

+ 2 - 0
Sunrise/src/middleware/bap/activity_host_manager/request/selection/definition.h

@@ -57,6 +57,8 @@ struct ActivityManagerSelection final {
     std::uint8_t packageNameLength{};
     /** Checked package-name bytes, then zero padding. */
     std::array<std::int8_t, kActivityManagerPackageNameCapacity> packageName{};
+    /** First name byte's bit offset from the descriptor's first bit. Valid with hasPackageName. */
+    std::size_t packageNameBitOffset{};
     /** Last root boolean. Its gameplay meaning is not known. */
     bool trailingFlag{};
     /**

+ 3 - 1
Sunrise/src/server/bap/encrypted/activity_host_manager/activity_host_manager_route.cpp

@@ -156,13 +156,15 @@ prepare_allocation(const request_selection::ActivityManagerSelectionResult& pars
         && source.descriptorBitLength <= destination.descriptorBits.size() * CHAR_BIT) {
         destination.descriptorBits = source.descriptorBits;
         destination.descriptorBitLength = static_cast<std::uint16_t>(source.descriptorBitLength);
+        destination.descriptorNameBit = static_cast<std::uint16_t>(source.packageNameBitOffset);
+        destination.hasDescriptorName = source.hasPackageName;
     }
     // The authored override is applied here, once, so every message built from this selection sees
     // the same arrival instead of each push working it out again.
     state::activity::defaults::ActivityDefaults defaults{};
     state::activity::defaults::snapshot(defaults);
     state::activity::defaults::apply_arrival_override(defaults, destination);
-    // Forced lands last and drops the captured descriptor, so name and activity index agree.
+    // Forced lands last and renames the captured descriptor in place.
     if (state::activity::forced::apply(destination)) {
         report_forced(destination);
     }

+ 4 - 0
Sunrise/src/state/activity/destination/definition.h

@@ -80,6 +80,10 @@ struct DestinationSelection final {
     std::array<std::byte, kDescriptorCapacity> descriptorBits{};
     /** Meaningful bits in descriptorBits, or zero when no descriptor was captured. */
     std::uint16_t descriptorBitLength{};
+    /** First name byte's bit offset inside descriptorBits. A forced name is written there. */
+    std::uint16_t descriptorNameBit{};
+    /** True only when the captured descriptor carried the fixed package-name field. */
+    bool hasDescriptorName{};
 };
 
 /**

+ 64 - 4
Sunrise/src/state/activity/forced/activity_forced_destination.cpp

@@ -3,10 +3,64 @@
 #include <Windows.h>
 
 #include <cstddef>
+#include <span>
 
 #include "../../runtime/storage/internal.h"
 
 namespace sunrise::state::activity::forced {
+namespace {
+
+/** Bits in one byte. The name field is 40 of them, back to back. */
+constexpr std::size_t kBitsPerByte = 8;
+/** Every name byte is encoded with this bias, and padding is a biased zero. */
+constexpr unsigned kPackageNameBias = 128;
+/** The most significant bit of a byte, where each packed field starts. */
+constexpr unsigned kHighBit = 0x80;
+
+/**
+ * Writes one byte into bit-packed storage at a bit offset.
+ * @param bits Storage large enough to hold the whole byte at that offset.
+ * @param bitOffset First bit of the byte.
+ * @param value Byte to write, most significant bit first.
+ */
+void write_byte(std::span<std::byte> bits, std::size_t bitOffset, unsigned value) noexcept {
+    for (std::size_t index = 0; index < kBitsPerByte; ++index) {
+        const std::size_t bit = bitOffset + index;
+        std::byte& target = bits[bit / kBitsPerByte];
+        const unsigned mask = kHighBit >> (bit % kBitsPerByte);
+        const bool set = (value >> (kBitsPerByte - 1 - index) & 1U) != 0;
+        target = static_cast<std::byte>(set ? static_cast<unsigned>(target) | mask
+                                            : static_cast<unsigned>(target) & ~mask);
+    }
+}
+
+/**
+ * Rewrites the captured descriptor's package name with the forced one.
+ * @param selection Committed destination holding the captured bits.
+ * @param value Forced destination whose name replaces the captured one.
+ * @return True when the whole 40-byte field sat inside the captured bits.
+ */
+[[nodiscard]] bool rename_descriptor(destination::DestinationSelection& selection,
+                                     const ForcedDestination& value) noexcept {
+    const std::size_t nameBits = destination::kPackageNameCapacity * kBitsPerByte;
+    if (!selection.hasDescriptorName || selection.descriptorBitLength == 0
+        || selection.descriptorNameBit + nameBits > selection.descriptorBitLength) {
+        return false;
+    }
+    for (std::size_t index = 0; index < destination::kPackageNameCapacity; ++index) {
+        // Past the name the field is padding, and a biased zero decodes outside the name charset,
+        // which is what ends the name.
+        const unsigned character = index < value.packageNameLength
+                                       ? static_cast<unsigned char>(value.packageName[index])
+                                       : 0U;
+        write_byte(selection.descriptorBits,
+                   selection.descriptorNameBit + index * kBitsPerByte,
+                   character + kPackageNameBias & 0xFFU);
+    }
+    return true;
+}
+
+} // namespace
 
 /** Replaces the forced destination. */
 bool publish(const ForcedDestination& value) noexcept {
@@ -66,10 +120,16 @@ bool apply(destination::DestinationSelection& selection) noexcept {
     // A map-wide set is not proof that the arrival bubble holds one of its points.
     selection.spawnSetOverride = value.hasSpawnSetHash ? value.spawnSetHash : kAbsentSpawnSetHash;
     selection.hasSpawnSetOverride = true;
-    // The captured descriptor is dropped, not patched: its activity index names what the Client
-    // picked, and a forced name beside a picked index starts the wrong activity with no spawn.
-    selection.descriptorBits = {};
-    selection.descriptorBitLength = 0;
+    // The Client authors this descriptor and the host replays it. Rebuilding it from named fields
+    // drops the ones with no name, and the Client then holds its lobby on Waiting for Other
+    // Players.
+    if (!rename_descriptor(selection, value)) {
+        // Nothing usable was captured, so the reconstructed descriptor goes out instead.
+        selection.descriptorBits = {};
+        selection.descriptorBitLength = 0;
+        selection.descriptorNameBit = 0;
+        selection.hasDescriptorName = false;
+    }
     return true;
 }