composite_entity_codec.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. #include <algorithm>
  2. #include <array>
  3. #include <cstring>
  4. #include <limits>
  5. #include <memory>
  6. #include <span>
  7. #include "composite_entity_codec_internal.h"
  8. namespace sunrise::middleware::gameplay::external {
  9. namespace {
  10. namespace format = state::activity_sdk::format;
  11. namespace wire = actor_wire;
  12. namespace bits = middleware::encoding::bits;
  13. /** The walker view stores the type code and codec parameter 2 as one byte each. */
  14. constexpr std::uint32_t kMaximumByteField = 0xFFU;
  15. /** A schema reference the mirror retains must fit one 32-bit tag. */
  16. constexpr std::uint64_t kMaximumSemanticTag = 0xFFFFFFFFULL;
  17. /** Resolves one schema only when it supports the selected codec family. */
  18. [[nodiscard]] bool
  19. find_schema(const void* raw, std::uint32_t handle, wire::runtime::SchemaView& output) noexcept {
  20. const auto* const context = static_cast<const detail::ResolverContext*>(raw);
  21. if (context == nullptr || context->catalog == nullptr) {
  22. return false;
  23. }
  24. const format::RuntimeSchema* row = detail::runtime_schema(*context->catalog, handle);
  25. if (row == nullptr && context->catalog->resolveAdditionalSchema != nullptr) {
  26. return context->catalog->resolveAdditionalSchema(
  27. context->catalog->planContext, handle, output)
  28. && output.handle == handle && output.row == handle;
  29. }
  30. // A schema carrying any other flag is not one this codec can walk.
  31. constexpr std::uint32_t kAllowedFlags =
  32. format::kRuntimeSchemaExact | format::kRuntimeSchemaArrayRegion;
  33. const bool arrayRegion =
  34. row != nullptr && (row->flags & format::kRuntimeSchemaArrayRegion) != 0;
  35. if (row == nullptr || (row->flags & format::kRuntimeSchemaExact) == 0
  36. || (row->flags & ~kAllowedFlags) != 0 || arrayRegion != (row->arrayElementCount != 0)
  37. || (context->family == format::RuntimeCodecFamily::activity
  38. && (row->codecFamilies & static_cast<std::uint32_t>(context->family)) == 0)) {
  39. return false;
  40. }
  41. const auto schemas = context->catalog->runtimeSchemas;
  42. const auto fields = context->catalog->runtimeFields;
  43. if (row < schemas.data() || row >= schemas.data() + schemas.size()
  44. || row->fields.first > fields.size()
  45. || row->fields.count > fields.size() - row->fields.first) {
  46. return false;
  47. }
  48. output.row = static_cast<std::uint32_t>(row - schemas.data());
  49. output.handle = row->handle;
  50. output.arrayLength = row->arrayElementCount;
  51. output.firstField = row->fields.first;
  52. output.fieldCount = row->fields.count;
  53. output.structSize = row->decodedSize;
  54. if (context->family != format::RuntimeCodecFamily::activity
  55. && context->catalog->resolveSchemaLayout != nullptr
  56. && !context->catalog->resolveSchemaLayout(
  57. context->catalog->planContext, row->handle, output.structSize)) {
  58. return false;
  59. }
  60. return true;
  61. }
  62. /** Resolves one schema by its stable generated row index. */
  63. [[nodiscard]] bool
  64. read_schema(const void* raw, std::uint32_t rowIndex, wire::runtime::SchemaView& output) noexcept {
  65. const auto* const context = static_cast<const detail::ResolverContext*>(raw);
  66. if (context == nullptr || context->catalog == nullptr) {
  67. return false;
  68. }
  69. if (rowIndex >= context->catalog->runtimeSchemas.size()) {
  70. return find_schema(raw, rowIndex, output) && output.row == rowIndex;
  71. }
  72. return find_schema(raw, context->catalog->runtimeSchemas[rowIndex].handle, output)
  73. && output.row == rowIndex;
  74. }
  75. /** Reference types that share one native reader decode through one walker case. */
  76. [[nodiscard]] std::uint8_t reference_alias(format::RuntimeCodecFamily family,
  77. std::uint8_t type) noexcept {
  78. if (family != format::RuntimeCodecFamily::sobjectModeZero && type == 30) {
  79. return 18;
  80. }
  81. // Type 21 uses the native nullable-u32 reader in every family: one present bit, then 32.
  82. if (type == 21) {
  83. return 22;
  84. }
  85. if (family == format::RuntimeCodecFamily::sobjectModeOne) {
  86. // Types 18, 20, 30 and 31 share one native mode-one reader.
  87. if (type == 20 || type == 31) {
  88. return 18;
  89. }
  90. if (type == 29) {
  91. return 43;
  92. }
  93. }
  94. return type;
  95. }
  96. /** Rewrites one field view to the type its family's native reader implements. */
  97. [[nodiscard]] bool canonicalize_field(const void* raw,
  98. format::RuntimeCodecFamily family,
  99. wire::runtime::FieldView& output) noexcept {
  100. output.typeCode = reference_alias(family, output.typeCode);
  101. // In mode one, a type-37 custom field is a plain nested walk of one fixed schema: a 19-bit real
  102. // and a 5-bit int8, 24 bits in all.
  103. constexpr std::uint32_t kNestedCustomSchema = 0x808092FFU;
  104. if (family == format::RuntimeCodecFamily::sobjectModeOne && output.typeCode == 37) {
  105. wire::runtime::SchemaView nested{};
  106. if (!find_schema(raw, kNestedCustomSchema, nested)) {
  107. return false;
  108. }
  109. output.typeCode = 1;
  110. output.nestedSchemaRow = nested.row;
  111. // Its fields carry no presence bits, so it needs no slot in the component bitmap.
  112. output.hasBitmapOffset = true;
  113. }
  114. return true;
  115. }
  116. /** Converts one SDK field row into the generic runtime walker view. */
  117. [[nodiscard]] bool
  118. read_field(const void* raw, std::uint32_t rowIndex, wire::runtime::FieldView& output) noexcept {
  119. const auto* const context = static_cast<const detail::ResolverContext*>(raw);
  120. if (context == nullptr || context->catalog == nullptr) {
  121. return false;
  122. }
  123. if (rowIndex >= context->catalog->runtimeFields.size()) {
  124. std::uint32_t nested = format::kAbsentIndex;
  125. if (context->catalog->resolveAdditionalField == nullptr
  126. || !context->catalog->resolveAdditionalField(
  127. context->catalog->planContext, rowIndex, output, nested)
  128. || output.row != rowIndex) {
  129. return false;
  130. }
  131. if (nested != format::kAbsentIndex) {
  132. wire::runtime::SchemaView target{};
  133. if (!find_schema(raw, nested, target)) {
  134. return false;
  135. }
  136. output.nestedSchemaRow = target.row;
  137. }
  138. return canonicalize_field(raw, context->family, output);
  139. }
  140. const format::RuntimeField& row = context->catalog->runtimeFields[rowIndex];
  141. if (row.schemaIndex >= context->catalog->runtimeSchemas.size()
  142. || (row.flags & format::kRuntimeFieldExact) == 0 || row.typeCode > kMaximumByteField) {
  143. return false;
  144. }
  145. std::uint32_t nestedRow = wire::runtime::kAbsentRuntimeRow;
  146. if (row.nestedHandle != format::kAbsentIndex) {
  147. wire::runtime::SchemaView nested{};
  148. if (!find_schema(raw, row.nestedHandle, nested)) {
  149. return false;
  150. }
  151. nestedRow = nested.row;
  152. }
  153. std::int32_t bias = 0;
  154. if (row.bias != format::kAbsentSignedValue) {
  155. if (row.bias < (std::numeric_limits<std::int32_t>::min)()
  156. || row.bias > (std::numeric_limits<std::int32_t>::max)()) {
  157. return false;
  158. }
  159. bias = static_cast<std::int32_t>(row.bias);
  160. }
  161. const bool dynamicArray = (row.flags & format::kRuntimeFieldDynamicArray) != 0;
  162. const bool quantized =
  163. row.typeCode == static_cast<std::uint32_t>(format::RuntimeFieldType::quantizedFloat)
  164. || row.typeCode == 42;
  165. output = {};
  166. output.row = rowIndex;
  167. output.nestedSchemaRow = nestedRow;
  168. output.structOffset = context->family == format::RuntimeCodecFamily::activity
  169. ? row.structOffset
  170. : row.alternateOffset;
  171. if (context->family != format::RuntimeCodecFamily::activity
  172. && context->catalog->resolveFieldLayout != nullptr) {
  173. output.hasBitmapOffset = context->catalog->resolveFieldLayout(
  174. context->catalog->planContext,
  175. context->catalog->runtimeSchemas[row.schemaIndex].handle,
  176. row.ordinal,
  177. output.bitmapOffset);
  178. }
  179. output.biasOrDynamic = quantized ? static_cast<std::int32_t>(row.codecParameters[0])
  180. : dynamicArray ? 1
  181. : bias;
  182. output.widthOrCountOffset = quantized || dynamicArray || row.typeCode == 44
  183. ? static_cast<std::int32_t>(row.codecParameters[1])
  184. : row.bits == format::kAbsentIndex
  185. ? 0
  186. : static_cast<std::int32_t>(row.bits);
  187. output.typeCode = static_cast<std::uint8_t>(row.typeCode);
  188. if (!canonicalize_field(raw, context->family, output)) {
  189. return false;
  190. }
  191. output.presence =
  192. static_cast<std::uint8_t>((row.flags & format::kRuntimeFieldPresenceBit) != 0);
  193. output.parameter2 = static_cast<std::uint8_t>(
  194. row.codecParameters[2] <= kMaximumByteField ? row.codecParameters[2] : 0);
  195. return true;
  196. }
  197. /** Accepts a union selector only when its family-specific SDK row exists. */
  198. [[nodiscard]] bool validate_type(const void* raw, std::uint8_t typeCode) noexcept {
  199. const auto* const context = static_cast<const detail::ResolverContext*>(raw);
  200. if (context == nullptr || context->catalog == nullptr) {
  201. return false;
  202. }
  203. const std::uint32_t family = static_cast<std::uint32_t>(context->family);
  204. return std::any_of(context->catalog->runtimeTypes.begin(),
  205. context->catalog->runtimeTypes.end(),
  206. [family, typeCode](const format::RuntimeTypeDefinition& row) {
  207. return row.typeCode == typeCode && (row.codecFamilies & family) != 0;
  208. });
  209. }
  210. /** Accepts one exact family-specific unsupported type only when it consumes no bits. */
  211. [[nodiscard]] bool zero_bit_type(const void* raw, std::uint8_t typeCode) noexcept {
  212. const auto* const context = static_cast<const detail::ResolverContext*>(raw);
  213. if (context == nullptr || context->catalog == nullptr) {
  214. return false;
  215. }
  216. const std::uint32_t family = static_cast<std::uint32_t>(context->family);
  217. const format::RuntimeTypeDefinition* match = nullptr;
  218. for (const format::RuntimeTypeDefinition& row : context->catalog->runtimeTypes) {
  219. if (row.typeCode != typeCode || (row.codecFamilies & family) == 0) {
  220. continue;
  221. }
  222. if (match != nullptr) {
  223. return false;
  224. }
  225. match = &row;
  226. }
  227. return match != nullptr && (match->flags & format::kRuntimeTypeDefinitionExact) != 0
  228. && (match->flags & format::kRuntimeTypeUnsupported) != 0 && match->fixedBits == 0
  229. && match->minimumBits == 0 && match->maximumBits == 0 && match->reserved == 0;
  230. }
  231. /** Per-field presence updates the component's compiled selection bitmap. */
  232. static bool record_presence(const void* raw, std::uint32_t bit, bool present) noexcept {
  233. const auto& context = *static_cast<const detail::ResolverContext*>(raw);
  234. if (bit >= context.presence.size()) {
  235. return false;
  236. }
  237. context.presence[bit] = present ? 1 : 0;
  238. return true;
  239. }
  240. /** T11 reference aliases share wire forms without changing other codec families. */
  241. static std::uint8_t canonical_type(const void* raw, std::uint8_t type) noexcept {
  242. const auto& context = *static_cast<const detail::ResolverContext*>(raw);
  243. // Mode zero uses a different raw reference body.
  244. if (context.family == format::RuntimeCodecFamily::sobjectModeZero && type == 27) {
  245. return 0;
  246. }
  247. return reference_alias(context.family, type);
  248. }
  249. /** The native action selector chooses one exact SDK-declared payload schema. */
  250. static bool
  251. resolve_command_payload(const void* raw, std::uint8_t selector, std::uint32_t& output) noexcept {
  252. output = 0;
  253. const auto& context = *static_cast<const detail::ResolverContext*>(raw);
  254. if (context.catalog->catalog == nullptr) {
  255. return false;
  256. }
  257. bool found = false;
  258. for (const auto& command : context.catalog->catalog->actor_command_definitions()) {
  259. if (command.selector != selector) {
  260. continue;
  261. }
  262. if (found || command.flags != format::kActorCommandDefinitionExact
  263. || command.payloadHandle == 0 || command.payloadHandle == format::kAbsentIndex) {
  264. return false;
  265. }
  266. found = true;
  267. output = command.payloadHandle;
  268. }
  269. return found;
  270. }
  271. /** Builds borrowed reflection callbacks over one stable resolver context. */
  272. [[nodiscard]] wire::RuntimeSchemaResolver make_resolver(detail::ResolverContext& context) noexcept {
  273. wire::RuntimeSchemaResolver resolver{};
  274. resolver.context = &context;
  275. resolver.findSchema = find_schema;
  276. resolver.readSchema = read_schema;
  277. resolver.readField = read_field;
  278. resolver.validateType = validate_type;
  279. resolver.isZeroBitType = zero_bit_type;
  280. resolver.positionProfile = &context.catalog->positionProfile;
  281. resolver.recordPresence = context.presence.empty() ? nullptr : record_presence;
  282. resolver.firstFieldBit = context.firstFieldBit;
  283. resolver.canonicalType = canonical_type;
  284. resolver.nativeCommandEmptyShortcut =
  285. context.family == format::RuntimeCodecFamily::sobjectModeOne;
  286. resolver.resolveCommandPayload = resolve_command_payload;
  287. return resolver;
  288. }
  289. /** Decodes one retained typed mirror through its published executable schema. */
  290. bool decode_composite_entity_payload_impl(const state::activity_sdk::Snapshot& catalog,
  291. EntityType type,
  292. TypePayloadPart part,
  293. const TypePayload& payload,
  294. std::span<wire::RuntimeDecodedValue> values,
  295. wire::RuntimeDecodeResult& result) noexcept {
  296. result = {};
  297. if (catalog == nullptr) {
  298. return false;
  299. }
  300. CompositeEntityCodecContext context{};
  301. context.catalog = catalog;
  302. context.entityTypes = catalog->entity_type_definitions();
  303. context.sobjectRsats = catalog->sobject_rsats();
  304. context.sobjectDescriptors = catalog->sobject_rsat_descriptors();
  305. context.rsatSchemas = catalog->rsat_schemas();
  306. context.rsatFields = catalog->rsat_fields();
  307. context.sobjectBindings = catalog->sobject_rsat_field_bindings();
  308. context.runtimeSchemas = catalog->runtime_schemas();
  309. context.runtimeFields = catalog->runtime_fields();
  310. context.runtimeTypes = catalog->runtime_type_definitions();
  311. context.ready = true;
  312. const format::EntityTypeDefinition* definition = detail::entity_definition(context, type);
  313. const std::uint32_t schemaHandle = definition == nullptr ? format::kAbsentIndex
  314. : part == TypePayloadPart::baseline
  315. ? definition->baselineSchema
  316. : definition->updateSchema;
  317. detail::MirrorView mirror{};
  318. if (schemaHandle == format::kAbsentIndex || !detail::load_mirror(payload, mirror)) {
  319. return false;
  320. }
  321. detail::ResolverContext resolverContext{&context,
  322. part == TypePayloadPart::baseline
  323. ? format::RuntimeCodecFamily::activity
  324. : format::RuntimeCodecFamily::sobjectModeOne};
  325. const wire::RuntimeSchemaResolver resolver = make_resolver(resolverContext);
  326. if (part == TypePayloadPart::update) {
  327. bits::Reader reader(mirror.bytes);
  328. std::uint64_t present = 0;
  329. if (mirror.header.bitCount == 0 || !reader.read(1, present)) {
  330. return false;
  331. }
  332. if (!present) {
  333. result.status = wire::CodecStatus::complete;
  334. result.bitsConsumed = 1;
  335. return mirror.header.bitCount == 1;
  336. }
  337. const bool decoded =
  338. wire::decode_full_schema_prefix(schemaHandle, reader, resolver, values, result);
  339. ++result.bitsConsumed;
  340. return decoded && result.status == wire::CodecStatus::complete && !result.valuesTruncated
  341. && result.bitsConsumed == mirror.header.bitCount;
  342. }
  343. return wire::decode_full_schema(
  344. schemaHandle, mirror.bytes, mirror.header.bitCount, resolver, values, result)
  345. && (result.status == wire::CodecStatus::complete
  346. || result.status == wire::CodecStatus::completeWithPadding)
  347. && !result.valuesTruncated;
  348. }
  349. /** Validates the complete row closure required by the composite codec. */
  350. [[nodiscard]] bool valid_catalog_rows(const CompositeEntityCodecContext& context) noexcept {
  351. if (context.entityTypes.size() != static_cast<std::size_t>(EntityType::count)
  352. || context.sobjectRsats.empty() || context.sobjectDescriptors.empty()
  353. || context.sobjectBindings.size() != context.rsatFields.size()
  354. || context.runtimeSchemas.empty() || context.runtimeFields.empty()
  355. || context.runtimeTypes.empty()
  356. || (context.positionCompression != SobjectPositionCompression::disabled
  357. && context.positionCompression != SobjectPositionCompression::enabledRaw)) {
  358. return false;
  359. }
  360. std::array<bool, static_cast<std::size_t>(EntityType::count)> found{};
  361. for (const format::EntityTypeDefinition& row : context.entityTypes) {
  362. if (row.entityType >= found.size() || found[row.entityType]
  363. || (row.flags & format::kEntityTypeDefinitionExact) == 0) {
  364. return false;
  365. }
  366. const bool stock = (row.flags & format::kEntityTypeStockEmittable) != 0;
  367. const bool update = (row.flags & format::kEntityTypeUpdateSupported) != 0;
  368. const bool dynamic = (row.flags & format::kEntityTypeUpdateUsesSobjectRsat) != 0;
  369. if ((stock && row.baselineSchema == format::kAbsentIndex)
  370. || (dynamic && (!update || row.updateSchema != format::kAbsentIndex))
  371. || (!dynamic && update && row.updateSchema == format::kAbsentIndex)) {
  372. return false;
  373. }
  374. if (stock && detail::runtime_schema(context, row.baselineSchema) == nullptr) {
  375. return false;
  376. }
  377. if (!dynamic && update && detail::runtime_schema(context, row.updateSchema) == nullptr) {
  378. return false;
  379. }
  380. found[row.entityType] = true;
  381. }
  382. return std::all_of(found.begin(), found.end(), [](bool value) { return value; });
  383. }
  384. } // namespace
  385. namespace detail {
  386. /** Finds one unique runtime schema handle in the pinned view. */
  387. [[nodiscard]] const format::RuntimeSchema*
  388. runtime_schema(const CompositeEntityCodecContext& context, std::uint32_t handle) noexcept {
  389. for (const format::RuntimeSchema& row : context.runtimeSchemas) {
  390. if (row.handle == handle) {
  391. return &row;
  392. }
  393. }
  394. return nullptr;
  395. }
  396. /** Finds one installed RSAT in its generated tag order. */
  397. [[nodiscard]] const format::SobjectRsat* sobject_rsat(const CompositeEntityCodecContext& context,
  398. std::uint32_t tag) noexcept {
  399. const auto found =
  400. std::lower_bound(context.sobjectRsats.begin(),
  401. context.sobjectRsats.end(),
  402. tag,
  403. [](const auto& row, auto value) { return row.rsatTag < value; });
  404. return found != context.sobjectRsats.end() && found->rsatTag == tag ? &*found : nullptr;
  405. }
  406. /** Checks the complete 17-bit entity-token domain. */
  407. [[nodiscard]] bool valid_token(const EntityToken& token) noexcept {
  408. return token.slot <= kMaximumEntitySlot && token.incarnation <= kMaximumEntityIncarnation;
  409. }
  410. /** Validates and opens one callback-owned canonical mirror. */
  411. [[nodiscard]] bool load_mirror(const TypePayload& payload, MirrorView& output) noexcept {
  412. if (payload.byteCount < sizeof(MirrorHeader) || payload.byteCount > payload.state.size()) {
  413. return false;
  414. }
  415. MirrorHeader header{};
  416. std::memcpy(&header, payload.state.data(), sizeof(header));
  417. const std::size_t byteCount = (static_cast<std::size_t>(header.bitCount) + 7U) / 8U;
  418. if (header.identity != kMirrorIdentity || header.reserved != 0
  419. || header.bitCount > kMaximumTypePayloadBits
  420. || payload.byteCount != sizeof(header) + byteCount) {
  421. return false;
  422. }
  423. output.header = header;
  424. output.bytes = {payload.state.data() + sizeof(header), byteCount};
  425. return true;
  426. }
  427. /** Reads one field and retains the same wire bits. */
  428. [[nodiscard]] bool read_and_append(bits::Reader& reader,
  429. MirrorBuilder& mirror,
  430. std::uint8_t width,
  431. std::uint64_t& output) noexcept {
  432. return reader.read(width, output) && mirror.append(output, width);
  433. }
  434. /** Reads and retains one required boolean field. */
  435. [[nodiscard]] bool read_flag(bits::Reader& reader, MirrorBuilder& mirror, bool& output) noexcept {
  436. std::uint64_t value = 0;
  437. if (!read_and_append(reader, mirror, kFlagWidth, value)) {
  438. return false;
  439. }
  440. output = value != 0;
  441. return true;
  442. }
  443. /** Decodes, re-encodes, and retains one complete reflected schema body. */
  444. [[nodiscard]] bool append_schema(bits::Reader& reader,
  445. MirrorBuilder& mirror,
  446. ResolverContext& resolverContext,
  447. std::uint32_t schemaHandle,
  448. std::uint32_t* semanticTag) noexcept {
  449. const wire::RuntimeSchemaResolver resolver = make_resolver(resolverContext);
  450. std::array<wire::RuntimeDecodedValue, wire::kRuntimeValueCapacity> values{};
  451. wire::RuntimeDecodeResult result{};
  452. if (!wire::decode_full_schema_prefix(schemaHandle, reader, resolver, values, result)
  453. || result.status != wire::CodecStatus::complete || result.valuesTruncated
  454. || result.valueCount > values.size()) {
  455. #if defined(SUNRISE_ACTIVITY_SDK_TESTING)
  456. if (resolverContext.catalog->schemaValues != nullptr && result.valueCount <= values.size())
  457. resolverContext.catalog->schemaValues(schemaHandle,
  458. std::span(values).first(result.valueCount));
  459. if (resolverContext.catalog->schemaFailure != nullptr) {
  460. const auto field = result.valueCount != 0 && result.valueCount <= values.size()
  461. ? values[result.valueCount - 1].fieldRow
  462. : format::kAbsentIndex;
  463. resolverContext.catalog->schemaFailure(schemaHandle,
  464. field,
  465. result,
  466. result.valueCount != 0
  467. && result.valueCount <= values.size()
  468. ? &values[result.valueCount - 1]
  469. : nullptr);
  470. }
  471. #endif
  472. return false;
  473. }
  474. std::array<wire::RuntimeDraftValue, wire::kRuntimeValueCapacity> draft{};
  475. #if defined(SUNRISE_ACTIVITY_SDK_TESTING)
  476. if (resolverContext.catalog->schemaValues != nullptr)
  477. resolverContext.catalog->schemaValues(schemaHandle,
  478. std::span(values).first(result.valueCount));
  479. #endif
  480. for (std::size_t index = 0; index < result.valueCount; ++index) {
  481. static_cast<wire::RuntimeDraftValue&>(draft[index]) = values[index];
  482. }
  483. std::array<std::byte, kMaximumTypePayloadBits / 8U> encoded{};
  484. std::size_t written = 0;
  485. std::size_t writtenBits = 0;
  486. wire::CodecStatus status = wire::CodecStatus::malformed;
  487. if (!wire::encode_full_schema(
  488. schemaHandle,
  489. std::span<const wire::RuntimeDraftValue>{draft.data(), result.valueCount},
  490. resolver,
  491. encoded,
  492. written,
  493. writtenBits,
  494. status)
  495. || status != wire::CodecStatus::complete || writtenBits != result.bitsConsumed
  496. || !mirror.append(std::span<const std::byte>{encoded.data(), written}, writtenBits)) {
  497. #if defined(SUNRISE_ACTIVITY_SDK_TESTING)
  498. if (resolverContext.catalog->schemaFailure != nullptr) {
  499. result.status = status;
  500. resolverContext.catalog->schemaFailure(
  501. schemaHandle, static_cast<std::uint32_t>(writtenBits), result, nullptr);
  502. }
  503. #endif
  504. return false;
  505. }
  506. if (semanticTag != nullptr) {
  507. const std::uint64_t nullableType =
  508. static_cast<std::uint32_t>(format::RuntimeFieldType::nullableTag);
  509. bool selected = false;
  510. bool found = false;
  511. std::uint32_t tag = 0;
  512. for (std::size_t index = 0; index < result.valueCount; ++index) {
  513. const wire::RuntimeDecodedValue& value = values[index];
  514. if (value.role == wire::ValueRole::variantSelector
  515. && value.unsignedValue == nullableType) {
  516. selected = true;
  517. } else if (selected && value.role == wire::ValueRole::schemaReference && value.present
  518. && value.unsignedValue <= kMaximumSemanticTag) {
  519. if (found) {
  520. return false;
  521. }
  522. found = true;
  523. tag = static_cast<std::uint32_t>(value.unsignedValue);
  524. }
  525. }
  526. if (!found || tag == 0 || tag == format::kAbsentIndex) {
  527. return false;
  528. }
  529. *semanticTag = tag;
  530. }
  531. // The damage component sends each level pool as its own ten-bit walk; pool 0 is health.
  532. constexpr std::uint32_t kDamageComponent = 0x80804BEEU;
  533. // Schema 80804C5C is one pool level; its single field is the only value it decodes.
  534. constexpr std::uint32_t kDamagePoolSchema = 0x80804C5CU;
  535. if (resolverContext.componentTag == kDamageComponent) {
  536. for (const auto& value : std::span(values).first(result.valueCount)) {
  537. if (value.schemaHandle != kDamagePoolSchema || !value.present) {
  538. continue;
  539. }
  540. const double level = value.kind == wire::ValueKind::real32
  541. ? static_cast<double>(value.realValue)
  542. : value.kind == wire::ValueKind::signedInteger
  543. ? static_cast<double>(value.signedValue)
  544. : static_cast<double>(value.unsignedValue);
  545. if (!(level >= 0.0 && level <= 1023.0)) {
  546. continue;
  547. }
  548. if (mirror.damagePools == 0) {
  549. mirror.damageHealth = static_cast<std::uint16_t>(level + 0.5);
  550. mirror.damageKnown = true;
  551. } else if (mirror.damagePools == 1) {
  552. mirror.damageShield = static_cast<std::uint16_t>(level + 0.5);
  553. }
  554. if (mirror.damagePools != 0xFF) {
  555. ++mirror.damagePools;
  556. }
  557. }
  558. }
  559. if (resolverContext.componentTag == 0x80C70EDCU && schemaHandle == 0x80C70EDCU) {
  560. wire::runtime::SchemaView component{}, source{}, reference{};
  561. wire::runtime::FieldView componentField{}, sourceField{};
  562. if (!find_schema(&resolverContext, schemaHandle, component) || component.fieldCount != 1
  563. || !read_field(&resolverContext, component.firstField, componentField)
  564. || componentField.typeCode != 1 || componentField.structOffset != 0
  565. || !read_schema(&resolverContext, componentField.nestedSchemaRow, source)
  566. || source.handle != 0x808090E6U || source.fieldCount == 0
  567. || !read_field(&resolverContext, source.firstField, sourceField)
  568. || sourceField.typeCode != 1 || sourceField.structOffset != 0
  569. || !read_schema(&resolverContext, sourceField.nestedSchemaRow, reference)
  570. || reference.handle != 0x80809C42U || reference.fieldCount != 3) {
  571. return false;
  572. }
  573. std::array<const wire::RuntimeDecodedValue*, 3> fields{};
  574. for (const auto& value : std::span(values).first(result.valueCount)) {
  575. if (value.schemaHandle != reference.handle || value.role != wire::ValueRole::scalar) {
  576. continue;
  577. }
  578. if (value.fieldRow < reference.firstField || value.fieldRow >= reference.firstField + 3
  579. || value.occurrence != 0 || !value.present) {
  580. return false;
  581. }
  582. auto& field = fields[value.fieldRow - reference.firstField];
  583. if (field != nullptr) {
  584. return false;
  585. }
  586. field = &value;
  587. }
  588. if (fields[0] != nullptr && fields[1] != nullptr && fields[2] != nullptr) {
  589. const auto key = fields[0]->unsignedValue;
  590. const auto type = fields[1]->signedValue;
  591. const auto index = fields[2]->signedValue;
  592. if (key == 0x811C9DC5U && type == -1 && index == -1) {
  593. mirror.actorSource = {};
  594. mirror.actorSource.known = true;
  595. } else if (key != 0 && key != 0xFFFFFFFFU && key <= 0xFFFFFFFFU && type >= 0
  596. && type <= 126 && index >= 0 && index <= 32767) {
  597. mirror.actorSource.key = static_cast<std::uint32_t>(key);
  598. mirror.actorSource.type = static_cast<std::uint8_t>(type);
  599. mirror.actorSource.index = static_cast<std::uint16_t>(index);
  600. mirror.actorSource.known = true;
  601. mirror.actorSource.present = true;
  602. }
  603. }
  604. }
  605. return true;
  606. }
  607. /** Resolves one exact channel-2 type contract from the SDK. */
  608. [[nodiscard]] const format::EntityTypeDefinition*
  609. entity_definition(const CompositeEntityCodecContext& context, EntityType type) noexcept {
  610. for (const format::EntityTypeDefinition& row : context.entityTypes) {
  611. if (row.entityType == static_cast<std::uint32_t>(type)) {
  612. return (row.flags & format::kEntityTypeDefinitionExact) != 0 ? &row : nullptr;
  613. }
  614. }
  615. return nullptr;
  616. }
  617. /** Pins one authenticated SDK snapshot and resets stale registry ownership. */
  618. [[nodiscard]] bool bind_context(CompositeEntityCodecContext& context,
  619. EntityBaselineRegistry& registry,
  620. const state::activity_sdk::Snapshot& catalog,
  621. SobjectPositionCompression positionCompression) noexcept {
  622. if (catalog == nullptr
  623. || (positionCompression != SobjectPositionCompression::disabled
  624. && positionCompression != SobjectPositionCompression::enabledRaw)) {
  625. return false;
  626. }
  627. if (registry.catalog != catalog) {
  628. registry.catalog = catalog;
  629. registry.slots.fill({});
  630. }
  631. context.catalog = catalog;
  632. context.entityTypes = catalog->entity_type_definitions();
  633. context.sobjectRsats = catalog->sobject_rsats();
  634. context.sobjectDescriptors = catalog->sobject_rsat_descriptors();
  635. context.rsatSchemas = catalog->rsat_schemas();
  636. context.rsatFields = catalog->rsat_fields();
  637. context.sobjectBindings = catalog->sobject_rsat_field_bindings();
  638. context.runtimeSchemas = catalog->runtime_schemas();
  639. context.runtimeFields = catalog->runtime_fields();
  640. context.runtimeTypes = catalog->runtime_type_definitions();
  641. context.registry = &registry;
  642. context.positionCompression = positionCompression;
  643. context.ready = valid_catalog_rows(context);
  644. return context.ready;
  645. }
  646. } // namespace detail
  647. /** Decodes one retained typed mirror through its published executable schema. */
  648. bool decode_composite_entity_payload(const state::activity_sdk::Snapshot& catalog,
  649. EntityType type,
  650. TypePayloadPart part,
  651. const TypePayload& payload,
  652. std::span<wire::RuntimeDecodedValue> values,
  653. wire::RuntimeDecodeResult& result) noexcept {
  654. return decode_composite_entity_payload_impl(catalog, type, part, payload, values, result);
  655. }
  656. /**
  657. * Reads only the private create-mirror layout, never a legacy typed payload.
  658. * @param payload Composite SObject baseline retained by the wire decoder.
  659. * @param output Receives its RSAT tag, or zero on failure.
  660. * @return True when the complete create mirror carries a valid tag.
  661. */
  662. bool composite_sobject_rsat(const TypePayload& payload, std::uint32_t& output) noexcept {
  663. output = 0;
  664. detail::MirrorView view{};
  665. if (!detail::load_mirror(payload, view) || view.header.bitCount != kSobjectBaselineBits
  666. || view.header.semanticTag == 0 || view.header.semanticTag == format::kAbsentIndex) {
  667. return false;
  668. }
  669. output = view.header.semanticTag;
  670. return true;
  671. }
  672. /** Binds the currently published authenticated SDK. */
  673. bool initialize_composite_entity_codec(CompositeEntityCodecContext& context,
  674. EntityBaselineRegistry& registry,
  675. SobjectPositionCompression positionCompression) noexcept {
  676. state::activity_sdk::Snapshot catalog = state::activity_sdk::snapshot();
  677. return detail::bind_context(context, registry, catalog, positionCompression);
  678. }
  679. /** Binds one caller-pinned authenticated SDK. */
  680. bool initialize_composite_entity_codec_from_catalog(
  681. CompositeEntityCodecContext& context,
  682. EntityBaselineRegistry& registry,
  683. const state::activity_sdk::Snapshot& catalog,
  684. SobjectPositionCompression positionCompression) noexcept {
  685. return detail::bind_context(context, registry, catalog, positionCompression);
  686. }
  687. #if defined(SUNRISE_ACTIVITY_SDK_TESTING)
  688. /** Binds deterministic row spans in focused test binaries only. */
  689. bool initialize_composite_entity_codec_for_test(
  690. CompositeEntityCodecContext& context,
  691. EntityBaselineRegistry& registry,
  692. const CompositeEntityCatalogFixture& fixture,
  693. SobjectPositionCompression positionCompression) noexcept {
  694. CompositeEntityCodecContext candidate{};
  695. candidate.entityTypes = fixture.entityTypes;
  696. candidate.sobjectRsats = fixture.sobjectRsats;
  697. candidate.sobjectDescriptors = fixture.sobjectDescriptors;
  698. candidate.rsatSchemas = fixture.rsatSchemas;
  699. candidate.rsatFields = fixture.rsatFields;
  700. candidate.sobjectBindings = fixture.sobjectBindings;
  701. candidate.runtimeSchemas = fixture.runtimeSchemas;
  702. candidate.runtimeFields = fixture.runtimeFields;
  703. candidate.runtimeTypes = fixture.runtimeTypes;
  704. candidate.resolvePlan = fixture.resolvePlan;
  705. candidate.planContext = fixture.planContext;
  706. candidate.registry = &registry;
  707. candidate.positionCompression = positionCompression;
  708. candidate.ready = valid_catalog_rows(candidate);
  709. if (!candidate.ready) {
  710. return false;
  711. }
  712. std::destroy_at(&registry);
  713. std::construct_at(&registry);
  714. context = candidate;
  715. return true;
  716. }
  717. #endif
  718. } // namespace sunrise::middleware::gameplay::external