entity_identity_metadata.cpp 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #include "entity_identity_metadata.h"
  2. #include <array>
  3. #include <limits>
  4. namespace sunrise::middleware::gameplay::external {
  5. namespace {
  6. namespace wire = middleware::bap::activity_message::wire_schema;
  7. namespace format = state::activity_sdk::format;
  8. using Metadata = state::gameplay::entity_identity::Metadata;
  9. /** Exact baseline roots and the raw eight-byte player-broadcast child schema. */
  10. constexpr std::uint32_t kSquadSchema = 0x80809C42U, kPlayerSchema = 0x80806ABDU;
  11. constexpr std::uint32_t kPlayerBytesSchema = 0x80809F7BU;
  12. /** The squad baseline's decoded struct offsets and wire widths. */
  13. constexpr std::array<std::uint32_t, 3> kSquadOffsets{0, 4, 6};
  14. constexpr std::array<std::uint8_t, 3> kSquadWidths{32, 7, 16};
  15. /** A player baseline is eight unsigned wire bytes, with no numeric identity assumption. */
  16. constexpr std::size_t kPlayerBytes = 8;
  17. constexpr std::uint8_t kByteWidth = 8;
  18. /**
  19. * Only one exact native baseline definition selects metadata semantics.
  20. * @param catalog Pinned SDK rows.
  21. * @param type Wire entity type.
  22. * @param expected Verified schema handle.
  23. * @return True for one exact matching definition.
  24. */
  25. bool has_schema(const state::activity_sdk::Snapshot& catalog,
  26. EntityType type,
  27. std::uint32_t expected) noexcept {
  28. bool found = false;
  29. for (const auto& row : catalog->entity_type_definitions()) {
  30. if (row.entityType != static_cast<std::uint32_t>(type)) {
  31. continue;
  32. }
  33. if (found || row.baselineSchema != expected
  34. || (row.flags & format::kEntityTypeDefinitionExact) == 0) {
  35. return false;
  36. }
  37. found = true;
  38. }
  39. return found;
  40. }
  41. /**
  42. * A squad identity needs all three exact fields and no sentinel values.
  43. * @param catalog Pinned field rows.
  44. * @param values Complete decoded baseline scalars.
  45. * @param output Receives the complete ClientRef.
  46. * @return True when the reference is usable.
  47. */
  48. bool squad_metadata(const state::activity_sdk::Snapshot& catalog,
  49. std::span<const wire::RuntimeDecodedValue> values,
  50. Metadata& output) noexcept {
  51. if (values.size() != kSquadOffsets.size()) {
  52. return false;
  53. }
  54. std::array<std::int64_t, kSquadOffsets.size()> scalars{};
  55. std::array<bool, kSquadOffsets.size()> seen{};
  56. for (const auto& value : values) {
  57. if (value.schemaHandle != kSquadSchema || value.role != wire::ValueRole::scalar
  58. || !value.present || value.occurrence != 0
  59. || value.fieldRow >= catalog->runtime_fields().size()) {
  60. return false;
  61. }
  62. const auto offset = catalog->runtime_fields()[value.fieldRow].structOffset;
  63. bool matched = false;
  64. for (std::size_t index = 0; index < kSquadOffsets.size(); ++index) {
  65. if (offset != kSquadOffsets[index]) {
  66. continue;
  67. }
  68. if (seen[index] || value.width != kSquadWidths[index]) {
  69. return false;
  70. }
  71. if (index == 0) {
  72. if (value.kind != wire::ValueKind::unsignedInteger || value.unsignedValue == 0
  73. || value.unsignedValue >= format::kAbsentIndex) {
  74. return false;
  75. }
  76. scalars[index] = static_cast<std::int64_t>(value.unsignedValue);
  77. } else {
  78. if (value.kind != wire::ValueKind::signedInteger || value.signedValue < 0
  79. || value.signedValue > (index == 1 ? 126 : 32767)) {
  80. return false;
  81. }
  82. scalars[index] = value.signedValue;
  83. }
  84. seen[index] = true;
  85. matched = true;
  86. }
  87. if (!matched) {
  88. return false;
  89. }
  90. }
  91. output.squad = {static_cast<std::uint32_t>(scalars[0]),
  92. static_cast<std::uint16_t>(scalars[2]),
  93. static_cast<std::uint8_t>(scalars[1])};
  94. output.hasSquad = true;
  95. return true;
  96. }
  97. /**
  98. * Raw player bytes keep their reflected order without an endian conversion.
  99. * @param values Complete decoded baseline scalars.
  100. * @param output Receives all eight bytes.
  101. * @return True when each byte occurrence appears exactly once.
  102. */
  103. bool player_metadata(std::span<const wire::RuntimeDecodedValue> values, Metadata& output) noexcept {
  104. if (values.size() != kPlayerBytes) {
  105. return false;
  106. }
  107. std::array<bool, kPlayerBytes> seen{};
  108. for (const auto& value : values) {
  109. if (value.schemaHandle != kPlayerBytesSchema || value.role != wire::ValueRole::scalar
  110. || !value.present || value.kind != wire::ValueKind::unsignedInteger
  111. || value.width != kByteWidth || value.unsignedValue > 0xFFU
  112. || value.occurrence >= seen.size() || seen[value.occurrence]) {
  113. return false;
  114. }
  115. seen[value.occurrence] = true;
  116. output.playerBroadcast[value.occurrence] = static_cast<std::byte>(value.unsignedValue);
  117. }
  118. output.hasPlayerBroadcast = true;
  119. return true;
  120. }
  121. } // namespace
  122. /** Ambiguous or incomplete class joins leave the object type unknown. */
  123. bool extract_sobject_object_type(std::span<const state::activity_sdk::format::ActorClass> classes,
  124. std::uint32_t rsatTag,
  125. std::uint8_t& output) noexcept {
  126. output = 0;
  127. if (rsatTag == 0 || rsatTag == 0xFFFFFFFFU) {
  128. return false;
  129. }
  130. const state::activity_sdk::format::ActorClass* selected = nullptr;
  131. for (const auto& row : classes) {
  132. if (row.rsatTag != rsatTag) {
  133. continue;
  134. }
  135. if (selected || row.definitionTag == 0 || row.definitionTag == 0xFFFFFFFFU
  136. || row.rsatReverseDefinitionTag != row.definitionTag || row.objectType > 0xFFU) {
  137. return false;
  138. }
  139. selected = &row;
  140. }
  141. if (!selected) {
  142. return false;
  143. }
  144. output = static_cast<std::uint8_t>(selected->objectType);
  145. return true;
  146. }
  147. /**
  148. * Unknown metadata stays absent while transport retains the generic entity identity.
  149. * @param catalog Pinned SDK catalog used to decode the baseline.
  150. * @param record Accepted create record.
  151. * @param output Receives known metadata, cleared on failure.
  152. * @param status Optional exact extraction result.
  153. * @return True when the supported metadata was extracted, including an empty test identity.
  154. */
  155. bool extract_entity_identity_metadata(const state::activity_sdk::Snapshot& catalog,
  156. const EntityRecord& record,
  157. Metadata& output,
  158. MetadataStatus* status) noexcept {
  159. output = {};
  160. const auto finish = [status](MetadataStatus value) noexcept {
  161. if (status != nullptr) {
  162. *status = value;
  163. }
  164. return value == MetadataStatus::complete;
  165. };
  166. if ((record.flags & entityCreate) == 0) {
  167. return finish(MetadataStatus::notCreate);
  168. }
  169. Metadata candidate{};
  170. if (record.type == EntityType::sobject) {
  171. if (!composite_sobject_rsat(record.baseline, candidate.rsatTag)) {
  172. return finish(MetadataStatus::malformedPayload);
  173. }
  174. candidate.hasRsat = true;
  175. if (catalog) {
  176. candidate.hasObjectType = extract_sobject_object_type(
  177. catalog->actor_classes(), candidate.rsatTag, candidate.objectType);
  178. }
  179. } else if (record.type == EntityType::test) {
  180. return finish(MetadataStatus::complete);
  181. } else {
  182. if (catalog == nullptr) {
  183. return finish(MetadataStatus::missingCatalog);
  184. }
  185. const auto schema = record.type == EntityType::squad ? kSquadSchema : kPlayerSchema;
  186. if ((record.type != EntityType::squad && record.type != EntityType::playerBroadcast)
  187. || !has_schema(catalog, record.type, schema)) {
  188. return finish(MetadataStatus::unsupportedSchema);
  189. }
  190. std::array<wire::RuntimeDecodedValue, wire::kRuntimeValueCapacity> values{};
  191. wire::RuntimeDecodeResult result{};
  192. if (!decode_composite_entity_payload(
  193. catalog, record.type, TypePayloadPart::baseline, record.baseline, values, result)) {
  194. return finish(MetadataStatus::malformedPayload);
  195. }
  196. const auto decoded = std::span(values).first(result.valueCount);
  197. if (!(record.type == EntityType::squad ? squad_metadata(catalog, decoded, candidate)
  198. : player_metadata(decoded, candidate))) {
  199. return finish(MetadataStatus::invalidReference);
  200. }
  201. }
  202. output = candidate;
  203. return finish(MetadataStatus::complete);
  204. }
  205. /**
  206. * Unobserved source components preserve the retained relation.
  207. * @param record Accepted record with a staged type payload.
  208. * @param output Receives the known relation or clear; cleared on failure.
  209. * @return True only for a valid staged source relation in an SObject update.
  210. */
  211. bool extract_actor_source_reference(
  212. const EntityRecord& record,
  213. state::gameplay::entity_identity::ActorSourceReference& output) noexcept {
  214. output = {};
  215. if (record.type != EntityType::sobject || !(record.flags & entityUpdate)
  216. || (record.flags & entityRemove)) {
  217. return false;
  218. }
  219. const auto& source = record.update.actorSource;
  220. if (!source.known) {
  221. return false;
  222. }
  223. if (source.present
  224. && (source.key == 0 || source.key == 0xFFFFFFFFU || source.type > 126
  225. || source.index > 32767)) {
  226. return false;
  227. }
  228. if (source.present) {
  229. output = source;
  230. }
  231. output.known = true;
  232. return true;
  233. }
  234. } // namespace sunrise::middleware::gameplay::external