composite_entity_codec.cpp 34 KB

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