activity_roster_snapshot.cpp 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. #include "../../../../../middleware/bap/activity_message/darkness_zone_auth.h"
  2. #include <Windows.h>
  3. #include <algorithm>
  4. #include <array>
  5. #include <string_view>
  6. #include <vector>
  7. #include "../../../../../middleware/content/packages/tables/region_reader.h"
  8. #include "../../../../../state/activity/defaults/activity_defaults_snapshot.h"
  9. #include "../../../../../state/activity/destination/activity_destination_spawn_binding.h"
  10. #include "../../../../../state/activity/membership/activity_membership_query.h"
  11. #include "../../../../../state/activity/runtime.h"
  12. #include "../../../../../state/build_data/runtime.h"
  13. #include "../../../../../state/runtime/runtime.h"
  14. #include "../../../../gameplay/gameplay_advertisement.h"
  15. #include "../../../../gameplay/group/group_host_sessions.h"
  16. #include "activity_arrival.h"
  17. #include "activity_mission_seed_roster.h"
  18. #include "internal.h"
  19. namespace sunrise::server::bap::encrypted::push::activity {
  20. namespace layouts = state::build_data::scenarios;
  21. namespace squad = middleware::bap::activity_message::squad_auth;
  22. /** Resolves the one region a session publishes. */
  23. EffectiveRegion effective_region(const state::activity::SessionBinding& binding) noexcept {
  24. EffectiveRegion region{};
  25. region.index = state::activity::membership::kAbsentRegionIndex;
  26. if (!state::activity::binding_matches(binding)) {
  27. return region;
  28. }
  29. state::activity::defaults::ActivityDefaults defaults{};
  30. state::activity::defaults::snapshot(defaults);
  31. const state::activity::destination::DestinationSelection& selection = binding.destination;
  32. const std::string_view name(reinterpret_cast<const char*>(selection.packageName.data()),
  33. selection.packageNameLength);
  34. // A missing layout leaves a cleared definition, and the arrival rule then returns the
  35. // authored fallback index.
  36. layouts::Definition layout{};
  37. static_cast<void>(state::build_data::find_scenario_layout(name, layout));
  38. region.arrival = arrival_slice_set(defaults.defaultDestination, selection, name, layout);
  39. const std::int32_t reported = state::activity::membership::player_region(binding.sessionId);
  40. region.reported = reported >= 0;
  41. region.index = region.reported ? reported : static_cast<std::int32_t>(region.arrival);
  42. return region;
  43. }
  44. /** Resolves the exact region one selected BAP ActivityClient would put in msg 5. */
  45. EffectiveRegion selected_effective_region(const Session& session, std::uint16_t arrival) noexcept {
  46. if (session.activity.role == ActivityClientRole::none
  47. || !state::activity::binding_matches(session.activity.session)
  48. || !state::activity::binding_matches(session.activity.source)) {
  49. EffectiveRegion region{};
  50. region.index = state::activity::membership::kAbsentRegionIndex;
  51. region.arrival = arrival;
  52. return region;
  53. }
  54. // The region the client is in. Its pending leg only names where it is heading, and after a
  55. // z-leg switch it names the region behind the player.
  56. const std::int32_t privateReportedRegion =
  57. session.activity.role == ActivityClientRole::privateCurrent
  58. ? state::activity::membership::player_region(session.activity.source.sessionId)
  59. : state::activity::membership::kAbsentRegionIndex;
  60. return select_activity_client_region(
  61. session.activity.role, privateReportedRegion, session.activity.advertisedRegion, arrival);
  62. }
  63. /** Reads where the client says it is. */
  64. state::activity::membership::ClientPlacement
  65. client_placement(const Session& session, const RefreshReport* refresh) noexcept {
  66. state::activity::membership::ClientPlacement placement =
  67. state::activity::membership::reported_placement(session.activity.session.sessionId);
  68. // Staging runs before the commit, so the refresh being answered is not in State yet.
  69. if (refresh != nullptr) {
  70. placement.bubble = refresh->bubble;
  71. placement.bubbleRevision = refresh->revision;
  72. if (refresh->hasCurrentRegion) {
  73. placement.currentRegion = refresh->currentRegion;
  74. }
  75. }
  76. return placement;
  77. }
  78. /** Tests whether the client holds a slice set and no host move is due. */
  79. bool client_region_ready(const Session& session, const RefreshReport* refresh) noexcept {
  80. const state::activity::membership::ClientPlacement placement =
  81. client_placement(session, refresh);
  82. const std::int32_t held = state::activity::membership::instantiated_region(placement);
  83. const MissionSeedLease& lease = session.activityMissionSeed;
  84. // A move is pending only while the client is somewhere other than the region the selection
  85. // names. A selection naming the region it already holds moves nobody, and arming the gate
  86. // behind a player who has arrived flashed their loading screen for one publish tick.
  87. const bool movePending = lease.configured
  88. && lease.bindingGeneration == session.activity.bindingGeneration
  89. && lease.regionArrivalPending
  90. && static_cast<std::int64_t>(lease.plan.effectiveRegion) != held;
  91. return !movePending && held >= 0;
  92. }
  93. /** Tests whether the client has reported arrival in its instantiated region. */
  94. bool client_in_world(const Session& session, const RefreshReport* refresh) noexcept {
  95. // ws-702 world state 8 is the arrival report. It does not depend on the player spawn.
  96. const state::activity::membership::ClientPlacement placement =
  97. client_placement(session, refresh);
  98. return placement.entered && client_region_ready(session, refresh);
  99. }
  100. /** Merges one staged squad body after the complete cumulative frame reached transport output. */
  101. bool activate_staged_squad_override(Session& session) noexcept {
  102. const RosterPublication& staged = session.activityRosterStaged;
  103. const server::activity::host::PendingScriptableOverride& pending = staged.scriptableOverride;
  104. const server::activity::host::ScriptableTarget& target = pending.target;
  105. if (!staged.staged || !staged.hasScriptableOverride || !staged.activatesSquadOverride
  106. || staged.bindingGeneration == 0
  107. || staged.bindingGeneration != session.activity.bindingGeneration
  108. || pending.expectedActivityClientGeneration != staged.bindingGeneration
  109. || pending.kind != server::activity::host::ScriptableOverrideKind::squad
  110. || (target.stateLocalRoster && !staged.hasSquadStateSequence)) {
  111. return false;
  112. }
  113. RetainedSquadAuth retained{};
  114. if (!make_retained_squad_auth(pending, session.activity.bindingGeneration, retained)) {
  115. return false;
  116. }
  117. if (target.stateLocalRoster) {
  118. const layouts::RosterGroup& group = pending.stateLocalRosterGroup;
  119. if (staged.stateLocalRegion != target.stateLocalRegion
  120. || !layouts::valid_roster_group(group)
  121. || target.rosterGroupIndex != server::activity::host::kGeneratedRosterGroupIndex
  122. || target.sdkObjectIndex == server::activity::host::kNoSdkObjectIndex
  123. || group.objectTag != target.objectTag || group.registryKey != target.registryKey
  124. || target.rosterSlotOffset >= group.slotCount
  125. || group.slotTypes[target.rosterSlotOffset] != target.slotType
  126. || group.slotIndices[target.rosterSlotOffset] != target.slotIndex
  127. || (group.slotFlags[target.rosterSlotOffset] & message::kSlotAuthFlag) == 0) {
  128. return false;
  129. }
  130. } else if (target.stateLocalRegion >= 0) {
  131. return false;
  132. }
  133. SquadOverrideLease& lease = session.activitySquadOverride;
  134. if (!lease.active) {
  135. SecureZeroMemory(&lease, sizeof lease);
  136. lease.groups[0].scopeTarget = target;
  137. if (target.stateLocalRoster) {
  138. lease.groups[0].stateLocalRosterGroup = pending.stateLocalRosterGroup;
  139. }
  140. lease.groups[0].region = target.stateLocalRegion;
  141. lease.groups[0].authCount = 1;
  142. lease.groups[0].stateSequence = staged.squadStateSequence;
  143. lease.groups[0].regionEpoch = session.activityRosterRegionEpoch;
  144. retained.groupIndex = 0;
  145. lease.authBodies[0] = retained;
  146. lease.bindingGeneration = session.activity.bindingGeneration;
  147. lease.authCount = 1;
  148. lease.groupCount = 1;
  149. lease.active = true;
  150. return true;
  151. }
  152. if (!valid_retained_squad_lease(lease, session.activity.bindingGeneration)) {
  153. return false;
  154. }
  155. const std::size_t groupIndex = retained_group_index(lease, target);
  156. if (groupIndex < lease.groupCount) {
  157. RetainedSquadGroup& group = lease.groups[groupIndex];
  158. if (target.stateLocalRoster
  159. && (!same_generated_group(group.stateLocalRosterGroup, pending.stateLocalRosterGroup)
  160. || staged.squadStateSequence != group.stateSequence)) {
  161. return false;
  162. }
  163. retained.groupIndex = static_cast<std::uint8_t>(groupIndex);
  164. for (std::size_t index = 0; index < lease.authCount; ++index) {
  165. if (lease.authBodies[index].groupIndex != groupIndex
  166. || !same_retained_target(group, lease.authBodies[index], target)) {
  167. continue;
  168. }
  169. lease.authBodies[index] = retained;
  170. return true;
  171. }
  172. if (lease.authCount >= lease.authBodies.size()
  173. || (target.stateLocalRoster
  174. && group.authCount >= group.stateLocalRosterGroup.slotCount)) {
  175. return false;
  176. }
  177. lease.authBodies[lease.authCount] = retained;
  178. ++lease.authCount;
  179. ++group.authCount;
  180. return true;
  181. }
  182. if (lease.groupCount >= lease.groups.size() || lease.authCount >= lease.authBodies.size()) {
  183. return false;
  184. }
  185. for (std::size_t index = 0; index < lease.groupCount; ++index) {
  186. if (lease.groups[index].scopeTarget.registryKey == target.registryKey) {
  187. return false;
  188. }
  189. }
  190. RetainedSquadGroup& group = lease.groups[lease.groupCount];
  191. group = {};
  192. group.scopeTarget = target;
  193. if (target.stateLocalRoster) {
  194. group.stateLocalRosterGroup = pending.stateLocalRosterGroup;
  195. }
  196. group.region = target.stateLocalRegion;
  197. group.authCount = 1;
  198. group.stateSequence = staged.squadStateSequence;
  199. group.regionEpoch = session.activityRosterRegionEpoch;
  200. retained.groupIndex = static_cast<std::uint8_t>(lease.groupCount);
  201. lease.authBodies[lease.authCount] = retained;
  202. ++lease.authCount;
  203. ++lease.groupCount;
  204. return true;
  205. }
  206. /** Restores counters from one discarded publication while leaving its committed lease intact. */
  207. void rollback_staged_roster_state(Session& session) noexcept {
  208. const RosterPublication& staged = session.activityRosterStaged;
  209. if (!staged.staged) {
  210. return;
  211. }
  212. session.activityRosterGroupLeases = staged.priorLeases;
  213. session.activityRosterSends = staged.priorSends;
  214. session.activityRosterState = staged.priorState;
  215. session.activityRosterRegionEpoch = staged.priorRegionEpoch;
  216. session.activityRosterRegionBubble = staged.priorRegionBubble;
  217. session.activityRosterOwedForEpoch = staged.priorRosterOwedForEpoch;
  218. session.activityRosterStaged = {};
  219. }
  220. /** Resolves the region one prepared membership body publishes. */
  221. EffectiveRegion planned_region(const state::activity::membership::PendingMutation& mutation,
  222. const state::activity::SessionBinding& binding) noexcept {
  223. EffectiveRegion region = effective_region(binding);
  224. // A pending leg naming a region is where the client is heading, so the body advertises it.
  225. // That holds whether the leg arrived in this delta or in an earlier report. A negative one
  226. // is a completed transition, and the committed position stands.
  227. const std::int32_t pending =
  228. mutation.authoritativeInput.hasRegion
  229. ? mutation.authoritativeInput.region.index
  230. : state::activity::membership::reported_region(binding.sessionId);
  231. if (pending > state::activity::membership::kAbsentRegionIndex) {
  232. region.index = pending;
  233. region.reported = true;
  234. }
  235. return region;
  236. }
  237. /** Resolves the region one private link's membership body publishes. */
  238. EffectiveRegion private_planned_region(const state::activity::membership::PendingMutation& mutation,
  239. const state::activity::SessionBinding& binding) noexcept {
  240. EffectiveRegion region = planned_region(mutation, binding);
  241. if (region.reported) {
  242. return region;
  243. }
  244. server::gameplay::group::HostSessionBinding host{};
  245. if (server::gameplay::private_host_session(binding, host) && host.regionIndex >= 0) {
  246. region.index = host.regionIndex;
  247. region.reported = true;
  248. }
  249. return region;
  250. }
  251. /** Lists the regions one membership body advertises a host for. */
  252. void directory_regions(const state::activity::SessionBinding& binding,
  253. std::int32_t regionIndex,
  254. std::span<std::int32_t> output,
  255. std::size_t& count) noexcept {
  256. namespace tables = middleware::content::packages::tables;
  257. count = 0;
  258. if (output.empty() || regionIndex <= state::activity::membership::kAbsentRegionIndex
  259. || !state::activity::binding_matches(binding)) {
  260. return;
  261. }
  262. output[count] = regionIndex;
  263. ++count;
  264. const state::activity::destination::DestinationSelection& selection = binding.destination;
  265. const std::string_view name(reinterpret_cast<const char*>(selection.packageName.data()),
  266. selection.packageNameLength);
  267. layouts::Definition layout{};
  268. if (!state::build_data::find_scenario_layout(name, layout)) {
  269. return;
  270. }
  271. const std::size_t bubbles =
  272. (std::min)(static_cast<std::size_t>(layout.bubbleCount), layout.bubbleStates.size());
  273. for (std::size_t bubble = 0; bubble < bubbles && count < output.size(); ++bubble) {
  274. // A bubble with no slice-set state has no slice set, so nothing can be hosted there.
  275. if (layout.bubbleStates[bubble] != layouts::kBubbleEnabledByte) {
  276. continue;
  277. }
  278. const auto region =
  279. static_cast<std::int32_t>(tables::region_index(static_cast<std::uint32_t>(bubble)));
  280. // One record per bubble, so the published region already speaks for its own bubble. Adding
  281. // that bubble's state-zero region too would ask for two records in one slot.
  282. if (static_cast<std::size_t>(regionIndex) / tables::kSliceSetIndexFactor == bubble) {
  283. continue;
  284. }
  285. output[count] = region;
  286. ++count;
  287. }
  288. }
  289. /** Builds the roster body input for one session's current destination. */
  290. RosterOutcome
  291. build_roster_snapshot(Session& session,
  292. Scratch& scratch,
  293. message::Snapshot& snapshot,
  294. std::span<char> destination,
  295. std::size_t& destinationLength,
  296. const middleware::bap::activity_message::patch_epoch::PatchEpoch* epoch,
  297. std::uint8_t lifetimeState,
  298. const message::AuthOverride* authOverride,
  299. std::uint16_t rosterGroupIndex,
  300. std::uint16_t rosterSlotOffset,
  301. bool stateLocalRosterTarget,
  302. std::int32_t stateLocalRegion,
  303. std::uint32_t sdkObjectIndex,
  304. const layouts::RosterGroup* stateLocalRosterGroup,
  305. const EffectiveRegion* exactRegion,
  306. const RefreshReport* refresh,
  307. std::span<const TailAuthOverride> tailOverrides) noexcept {
  308. snapshot = {};
  309. destinationLength = 0;
  310. state::activity::defaults::ActivityDefaults defaults{};
  311. state::activity::defaults::snapshot(defaults);
  312. if (session.activity.role == ActivityClientRole::none
  313. || !state::activity::binding_matches(session.activity.session)
  314. || !state::activity::binding_matches(session.activity.source)) {
  315. return RosterOutcome::noLayout;
  316. }
  317. // Public targets keep the destination copied from their exact advertised source generation.
  318. const state::activity::destination::DestinationSelection& selection =
  319. session.activity.session.destination;
  320. layouts::Definition layout{};
  321. const std::string_view name(reinterpret_cast<const char*>(selection.packageName.data()),
  322. selection.packageNameLength);
  323. destinationLength = (std::min)(name.size(), destination.size());
  324. std::copy_n(name.begin(), destinationLength, destination.begin());
  325. if (!state::build_data::find_scenario_layout(name, layout)) {
  326. return RosterOutcome::noLayout;
  327. }
  328. const std::uint64_t hostedBubbles = hosted_bubble_mask(session);
  329. if (!fill_roster(layout, hostedBubbles, scratch, snapshot.roster)) {
  330. return RosterOutcome::noGroups;
  331. }
  332. const state::activity::defaults::FallbackPolicy& fallback =
  333. defaults.defaultDestination.fallback;
  334. // One resolution serves this body and the citizen advertisement in message 12. Two would let
  335. // the join descriptor land in a region record the client is not pending on.
  336. const EffectiveRegion committedRegion = selected_effective_region(
  337. session, arrival_slice_set(defaults.defaultDestination, selection, name, layout));
  338. const EffectiveRegion region = exactRegion == nullptr ? committedRegion : *exactRegion;
  339. if (exactRegion != nullptr
  340. && (session.activity.role != ActivityClientRole::privateCurrent || !region.reported
  341. || region.arrival != committedRegion.arrival)) {
  342. return refuse_override("exact_region");
  343. }
  344. if (region.index < 0) {
  345. return RosterOutcome::noLayout;
  346. }
  347. std::vector<server::activity::host::PendingScriptableOverride> authEstate{};
  348. if (!server::activity::host::scriptable_auth_estate(
  349. session.activity.session, session.activity.bindingGeneration, authEstate)) {
  350. return refuse_override("auth_estate");
  351. }
  352. const std::size_t canonicalGroupCount = snapshot.roster.groupCount;
  353. if (append_initial_mission_seed(session,
  354. scratch,
  355. snapshot,
  356. static_cast<std::uint32_t>(region.index),
  357. hostedBubbles,
  358. canonicalGroupCount,
  359. refresh)
  360. == MissionSeedRosterResult::refused) {
  361. return refuse_override("mission_seed");
  362. }
  363. // Retained and pending groups below may only match a group the seed published.
  364. const std::size_t seedGroupCount = snapshot.roster.groupCount;
  365. const bool pendingStateLocal = authOverride != nullptr && stateLocalRosterTarget;
  366. if (session.activitySquadOverride.active
  367. && session.activitySquadOverride.bindingGeneration != session.activity.bindingGeneration) {
  368. SecureZeroMemory(&session.activitySquadOverride, sizeof session.activitySquadOverride);
  369. }
  370. const bool retainedSquad = session.activitySquadOverride.active;
  371. const SquadOverrideLease& lease = session.activitySquadOverride;
  372. if (retainedSquad && !valid_retained_squad_lease(lease, session.activity.bindingGeneration)) {
  373. return refuse_override("retained_lease");
  374. }
  375. server::activity::host::ScriptableTarget pendingTarget{};
  376. if (authOverride != nullptr) {
  377. pendingTarget.objectTag = authOverride->objectTag;
  378. pendingTarget.registryKey = authOverride->key;
  379. pendingTarget.authSchema = authOverride->authSchema;
  380. pendingTarget.rosterGroupIndex = rosterGroupIndex;
  381. pendingTarget.rosterSlotOffset = rosterSlotOffset;
  382. pendingTarget.slotIndex = authOverride->slotIndex;
  383. pendingTarget.sdkObjectIndex = sdkObjectIndex;
  384. pendingTarget.stateLocalRegion = stateLocalRegion;
  385. pendingTarget.slotType = authOverride->slotType;
  386. pendingTarget.stateLocalRoster = stateLocalRosterTarget;
  387. }
  388. const bool squadOverride = authOverride != nullptr && authOverride->slotType == squad::kSlotType
  389. && authOverride->authSchema == squad::kSchema;
  390. const std::size_t pendingGroupIndex = retainedSquad && authOverride != nullptr
  391. ? retained_group_index(lease, pendingTarget)
  392. : lease.groupCount;
  393. if (retainedSquad && authOverride != nullptr) {
  394. if (pendingGroupIndex < lease.groupCount) {
  395. const RetainedSquadGroup& retainedGroup = lease.groups[pendingGroupIndex];
  396. if (stateLocalRosterTarget
  397. && (stateLocalRosterGroup == nullptr
  398. || !same_generated_group(*stateLocalRosterGroup,
  399. retainedGroup.stateLocalRosterGroup))) {
  400. return refuse_override("retained_group_mismatch");
  401. }
  402. } else if (squadOverride
  403. && (lease.groupCount >= lease.groups.size()
  404. || lease.authCount >= lease.authBodies.size())) {
  405. return refuse_override("lease_capacity");
  406. }
  407. }
  408. if (pendingStateLocal
  409. && (stateLocalRosterGroup == nullptr
  410. || rosterGroupIndex != server::activity::host::kGeneratedRosterGroupIndex
  411. || sdkObjectIndex == server::activity::host::kNoSdkObjectIndex)) {
  412. return refuse_override("pending_state_local_target");
  413. }
  414. std::array<std::size_t, message::kPublishedGroupCapacity> retainedGroupPositions{};
  415. retainedGroupPositions.fill(snapshot.roster.groups.size());
  416. for (std::size_t index = 0; retainedSquad && index < lease.groupCount; ++index) {
  417. const RetainedSquadGroup& retainedGroup = lease.groups[index];
  418. if (!retainedGroup.scopeTarget.stateLocalRoster) {
  419. continue;
  420. }
  421. if (!msg1_selects_region(layout, retainedGroup.region)) {
  422. return refuse_override("retained_region");
  423. }
  424. const std::uint32_t bubble = static_cast<std::uint32_t>(retainedGroup.region)
  425. / middleware::content::packages::tables::kSliceSetIndexFactor;
  426. std::size_t position = snapshot.roster.groups.size();
  427. const ExistingGroup existing = find_existing_group(
  428. retainedGroup.stateLocalRosterGroup, scratch, snapshot.roster, position);
  429. if (existing == ExistingGroup::conflict
  430. || (existing == ExistingGroup::exact && position >= seedGroupCount)
  431. || (existing == ExistingGroup::exact
  432. && !activate_existing_group(position, bubble, scratch, snapshot.roster))) {
  433. return refuse_override("retained_existing_group");
  434. }
  435. if (existing == ExistingGroup::missing) {
  436. position = snapshot.roster.groupCount;
  437. if (!append_state_local_group(
  438. retainedGroup.stateLocalRosterGroup, bubble, scratch, snapshot.roster)) {
  439. return refuse_override("retained_append");
  440. }
  441. }
  442. retainedGroupPositions[index] = position;
  443. }
  444. const bool pendingAddsGroup = pendingStateLocal && pendingGroupIndex == lease.groupCount;
  445. std::size_t pendingGroupPosition = snapshot.roster.groups.size();
  446. if (!pendingAddsGroup && pendingStateLocal && pendingGroupIndex < lease.groupCount) {
  447. pendingGroupPosition = retainedGroupPositions[pendingGroupIndex];
  448. }
  449. for (std::size_t index = 0; retainedSquad && index < lease.authCount; ++index) {
  450. message::AuthOverride retainedAuth{};
  451. if (!retained_squad_auth(lease, index, retainedAuth)) {
  452. return refuse_override("retained_auth");
  453. }
  454. const RetainedSquadAuth& retained = lease.authBodies[index];
  455. const RetainedSquadGroup& retainedGroup = lease.groups[retained.groupIndex];
  456. const bool replace =
  457. authOverride != nullptr && same_retained_target(retainedGroup, retained, pendingTarget);
  458. const message::AuthOverride& effectiveAuth = replace ? *authOverride : retainedAuth;
  459. const std::uint16_t effectiveGroup =
  460. replace ? rosterGroupIndex : retainedGroup.scopeTarget.rosterGroupIndex;
  461. const std::uint16_t effectiveSlot = replace ? rosterSlotOffset : retained.rosterSlotOffset;
  462. const bool effectiveStateLocal =
  463. replace ? stateLocalRosterTarget : retainedGroup.scopeTarget.stateLocalRoster;
  464. if (!install_auth_override(layout,
  465. region,
  466. scratch,
  467. snapshot,
  468. effectiveAuth,
  469. effectiveGroup,
  470. effectiveSlot,
  471. effectiveStateLocal)) {
  472. return refuse_override("retained_auth_install");
  473. }
  474. }
  475. // Message 5 resets every registered Auth slot before applying its bodies, so the complete
  476. // latest-per-ClientRef estate is re-emitted and a later action cannot erase an earlier one.
  477. // Squads stay in their lease, which also owns group admission and per-group revisions.
  478. for (const server::activity::host::PendingScriptableOverride& retained : authEstate) {
  479. if (retained.kind == server::activity::host::ScriptableOverrideKind::squad
  480. || retained.kind == server::activity::host::ScriptableOverrideKind::lifetime) {
  481. continue;
  482. }
  483. // A dialogue body is a pulse: the client plays one line of the cue each time it applies
  484. // the body, and it applies every body of every msg 5. So it goes out once, in the push
  485. // that delivers it, and the slot then carries no body.
  486. if (retained.kind == server::activity::host::ScriptableOverrideKind::dialogue) {
  487. continue;
  488. }
  489. const server::activity::host::ScriptableTarget& target = retained.target;
  490. if (!target.stateLocalRoster) {
  491. const CanonicalGroupStatus status =
  492. canonical_group_status(layout, region, target.rosterGroupIndex);
  493. if (status == CanonicalGroupStatus::inactive) {
  494. continue;
  495. }
  496. if (status == CanonicalGroupStatus::unknown) {
  497. return refuse_override("canonical_group_unknown");
  498. }
  499. }
  500. if (target.stateLocalRoster) {
  501. if (!layouts::valid_roster_group(retained.stateLocalRosterGroup)
  502. || target.stateLocalRegion < 0
  503. || target.rosterGroupIndex != server::activity::host::kGeneratedRosterGroupIndex
  504. || target.sdkObjectIndex == server::activity::host::kNoSdkObjectIndex) {
  505. return refuse_override("retained_state_local_target");
  506. }
  507. const std::uint32_t bubble =
  508. static_cast<std::uint32_t>(target.stateLocalRegion)
  509. / middleware::content::packages::tables::kSliceSetIndexFactor;
  510. std::size_t position = snapshot.roster.groups.size();
  511. const ExistingGroup existing = find_existing_group(
  512. retained.stateLocalRosterGroup, scratch, snapshot.roster, position);
  513. if (existing == ExistingGroup::conflict
  514. || (existing == ExistingGroup::exact
  515. && !activate_existing_group(position, bubble, scratch, snapshot.roster))
  516. || (existing == ExistingGroup::missing
  517. && !append_state_local_group(
  518. retained.stateLocalRosterGroup, bubble, scratch, snapshot.roster))) {
  519. return refuse_override("retained_group_install");
  520. }
  521. }
  522. message::AuthOverride value{};
  523. if (!make_auth_override(retained, value)
  524. || !install_auth_override(layout,
  525. region,
  526. scratch,
  527. snapshot,
  528. value,
  529. target.rosterGroupIndex,
  530. target.rosterSlotOffset,
  531. target.stateLocalRoster)) {
  532. return refuse_override("retained_auth_apply");
  533. }
  534. }
  535. // The pending override's group goes after the retained estate, where the next push will place
  536. // it once retained, so two pushes carry the same group order. An exact match here may be a
  537. // group the estate just appended, so no position bound applies.
  538. if (pendingAddsGroup) {
  539. const std::uint32_t bubble = static_cast<std::uint32_t>(stateLocalRegion)
  540. / middleware::content::packages::tables::kSliceSetIndexFactor;
  541. ExistingGroup existing = ExistingGroup::conflict;
  542. if (stateLocalRosterGroup != nullptr) {
  543. existing = find_existing_group(
  544. *stateLocalRosterGroup, scratch, snapshot.roster, pendingGroupPosition);
  545. }
  546. if (existing == ExistingGroup::conflict
  547. || (existing == ExistingGroup::exact
  548. && !activate_existing_group(
  549. pendingGroupPosition, bubble, scratch, snapshot.roster))) {
  550. return refuse_override("pending_existing_group");
  551. }
  552. if (existing == ExistingGroup::missing) {
  553. pendingGroupPosition = snapshot.roster.groupCount;
  554. if (!append_state_local_group(
  555. *stateLocalRosterGroup, bubble, scratch, snapshot.roster)) {
  556. return refuse_override("pending_append");
  557. }
  558. }
  559. }
  560. // The estate may contain an older SDK objective update for a squad that was
  561. // also installed through its lease above. Apply the pending body last even
  562. // for that target; otherwise the older body silently wins this packet.
  563. if (authOverride != nullptr
  564. && !install_auth_override(layout,
  565. region,
  566. scratch,
  567. snapshot,
  568. *authOverride,
  569. rosterGroupIndex,
  570. rosterSlotOffset,
  571. stateLocalRosterTarget)) {
  572. return refuse_override("pending_auth_apply");
  573. }
  574. // A burst commits behind the head and leaves on this body with it.
  575. for (const TailAuthOverride& queued : tailOverrides) {
  576. if (!install_auth_override(layout,
  577. region,
  578. scratch,
  579. snapshot,
  580. queued.value,
  581. queued.rosterGroupIndex,
  582. queued.rosterSlotOffset,
  583. queued.stateLocalRosterTarget)) {
  584. return refuse_override("tail_auth_apply");
  585. }
  586. }
  587. // Use the final merged estate, so a pending disable wins over retained enable.
  588. namespace darkness=middleware::bap::activity_message::darkness_zone;
  589. for (const auto& value : snapshot.authOverrides) {
  590. bool enabled{};
  591. if (value.sdkCompiled&&value.present&&value.slotType==darkness::kSlotType
  592. &&value.authSchema==darkness::kSchema&&value.byteCount<=value.body.size()
  593. &&darkness::decode(std::span(value.body).first(value.byteCount),value.bitCount,enabled)) {
  594. snapshot.hasDarknessPolicy=true;
  595. snapshot.darknessEnabled=enabled;
  596. }
  597. }
  598. // Staging runs before the connection field is published, so a body answering message 52 has to
  599. // take the epoch from that message instead of from the connection.
  600. snapshot.patchEpoch = epoch != nullptr ? *epoch : session.activityPatchEpoch.value;
  601. // The character the join named wins, resolved to its authored SOID. The client binds its
  602. // player by matching this value against the object registry, and the short form the join
  603. // carries matches nothing.
  604. snapshot.playerKey = published_player_key(session);
  605. snapshot.lifetime = lifetimeState;
  606. // Hold the native spawn gate until the ws-702 world state reads 8. A spawn before the fade
  607. // arms leaves the screen black.
  608. snapshot.awaitClientSync = !client_in_world(session, refresh);
  609. // Player_BindComponents walks every type-13 reference and the player datum can name any one of
  610. // them. So every participation record carries the same player key. Selecting the first slot
  611. // leaves the authored cinematic participant unbound whenever it names another record.
  612. snapshot.keyOnEveryParticipationSlot = true;
  613. // The participation record's `+0` latches only when the region index is known.
  614. snapshot.region = static_cast<std::uint32_t>(region.index);
  615. snapshot.hasRegion = true;
  616. // The override must name the exact slice set the client is in, so it follows the published
  617. // region and is never floored to the bubble's first state. A pair naming the arrival is inert
  618. // after a teleport, and the picker then falls back to an arbitrary point.
  619. snapshot.spawnSliceSet =
  620. region.index >= 0 ? static_cast<std::uint32_t>(region.index) : region.arrival;
  621. snapshot.spawnSetHash =
  622. state::activity::destination::attachable_spawn_set_hash(selection, fallback.spawnSetHash);
  623. // A genuine wipe selects the authored darkness checkpoint, not the mission's
  624. // initial arrival override. Keep it in this region while the player respawns.
  625. if (const auto checkpoint=state::activity::membership::checkpoint_spawn_hash(
  626. session.activity.source.sessionId,region.index); checkpoint!=0)
  627. snapshot.spawnSetHash=checkpoint;
  628. snapshot.hasSpawnOverride =
  629. snapshot.spawnSetHash != 0 && snapshot.spawnSetHash != message::kAbsentSpawnSetHash;
  630. advance_region_epoch(session, refresh);
  631. stamp_group_sequences(session, snapshot.roster);
  632. snapshot.stateSequence = session.activityRosterState;
  633. // One-shot first-roster flag; the mission seed's adoption guard reads it.
  634. if (session.activityRosterSends == 0) {
  635. session.activityRosterSends = 1;
  636. }
  637. // A retained key holds its revision, so a later Auth change cannot re-register the group and
  638. // rebuild its actors. A bubble change re-arms it once per epoch; the client unloads the group
  639. // with the bubble.
  640. for (std::size_t index = 0; retainedSquad && index < lease.groupCount; ++index) {
  641. const std::size_t position = retainedGroupPositions[index];
  642. if (position >= snapshot.roster.groupCount) {
  643. continue;
  644. }
  645. RetainedSquadGroup& retained = session.activitySquadOverride.groups[index];
  646. if (retained.regionEpoch == session.activityRosterRegionEpoch) {
  647. snapshot.roster.groups[position].stateSequence = retained.stateSequence;
  648. continue;
  649. }
  650. retained.regionEpoch = session.activityRosterRegionEpoch;
  651. retained.stateSequence = snapshot.roster.groups[position].stateSequence;
  652. }
  653. if (pendingStateLocal && pendingGroupPosition >= snapshot.roster.groupCount) {
  654. return refuse_override("pending_group_position");
  655. }
  656. std::size_t senseCount = 0;
  657. for (const message::AuthOverride& auth : snapshot.authOverrides) {
  658. if (auth.slotType != 1) {
  659. continue;
  660. }
  661. server::activity::host::SenseObservationKey key{};
  662. key.registryKey = auth.key;
  663. key.objectTag = auth.objectTag;
  664. key.senseSchema = 0x80807ECCU;
  665. key.slotIndex = auth.slotIndex;
  666. key.slotType = auth.slotType;
  667. middleware::bap::activity_message::squad_sense::State recovered{};
  668. if (!server::activity::host::snapshot_squad_sense(
  669. session.activity.session, session.activity.bindingGeneration, key, recovered)) {
  670. continue;
  671. }
  672. message::SenseOverride& sense = scratch.rosterSenseOverrides[senseCount];
  673. sense = {};
  674. if (!middleware::bap::activity_message::squad_sense::encode(
  675. recovered, sense.body, sense.byteCount, sense.bitCount)) {
  676. return refuse_override("squad_sense");
  677. }
  678. sense.key = auth.key;
  679. sense.objectTag = auth.objectTag;
  680. sense.slotIndex = auth.slotIndex;
  681. sense.slotType = auth.slotType;
  682. sense.counter = recovered.counter;
  683. ++senseCount;
  684. }
  685. snapshot.senseOverrides = std::span(scratch.rosterSenseOverrides).first(senseCount);
  686. return RosterOutcome::published;
  687. }
  688. /** The player key this link's message 5 binds: the join character's SOID, or its identity. */
  689. std::uint64_t published_player_key(const Session& session) noexcept {
  690. state::activity::defaults::ActivityDefaults defaults{};
  691. state::activity::defaults::snapshot(defaults);
  692. std::uint64_t key = roster_player_key(session.activityCharacterSoid);
  693. if (defaults.rosterKeyFromIdentity) {
  694. const std::uint64_t identity =
  695. state::activity::membership::join_identity(session.activity.session.sessionId);
  696. if (identity != 0) {
  697. key = identity;
  698. }
  699. }
  700. return key;
  701. }
  702. } // namespace sunrise::server::bap::encrypted::push::activity