activity_sdk_relation_validation.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. #include <array>
  2. #include <cmath>
  3. #include <cstddef>
  4. #include <cstdio>
  5. #include <tuple>
  6. #include "actor_sequences.h"
  7. #include "internal.h"
  8. #include "squad_profiles.h"
  9. #include "validation_internal.h"
  10. namespace sunrise::state::activity_sdk::validation {
  11. namespace {
  12. /** @return True when every member certificate belongs to its declared squad lane. */
  13. [[nodiscard]] bool squad_profiles(const Catalog& catalog) noexcept {
  14. const auto squads = catalog.squads();
  15. const auto members = catalog.squad_members();
  16. for (std::size_t index = 0; index < members.size(); ++index) {
  17. const auto& member = members[index];
  18. if (member.squadIndex >= squads.size()
  19. || member.memberOrdinal >= squads[member.squadIndex].members.count
  20. || squads[member.squadIndex].members.first + member.memberOrdinal != index
  21. || !valid_member_spawn_profile(member, catalog.actor_classes())) {
  22. return false;
  23. }
  24. }
  25. return true;
  26. }
  27. // An empty authored name uses the FNV-1 basis.
  28. constexpr std::uint32_t kAbsentDefinitionHash = 0x811C9DC5U;
  29. // Ability owners and targets use these exact package slot classes and schemas.
  30. constexpr std::uint32_t kAbilitySlotType = 2U;
  31. constexpr std::uint32_t kAbilityComponentClass = 0x8080834EU;
  32. constexpr std::uint32_t kAbilitySenseSchema = 0x80807DA2U;
  33. constexpr std::uint32_t kAbilityAuthSchema = 0x80807DA1U;
  34. constexpr std::uint32_t kAbilityTargetSlotType = 58U;
  35. constexpr std::uint32_t kAbilityTargetComponentClass = 0x80807D9BU;
  36. /**
  37. * @return True when every scene event key belongs to a resourced type-43 slot, names that
  38. * slot's resource, and the rows come in slot then gate order without repeats.
  39. */
  40. [[nodiscard]] bool authored_scene_event_keys(const Catalog& catalog) noexcept {
  41. const auto keys = catalog.authored_scene_event_keys();
  42. const auto slots = catalog.slots();
  43. for (std::size_t index = 0; index < keys.size(); ++index) {
  44. const format::AuthoredSceneEventKey& row = keys[index];
  45. if (row.sceneSlotIndex >= slots.size() || row.flags != format::kAuthoredSceneEventKeyExact
  46. || row.reserved != 0 || row.key == 0 || row.key == format::kAbsentIndex
  47. || row.graphTag == 0 || row.graphTag == format::kAbsentIndex) {
  48. return false;
  49. }
  50. const format::Slot& slot = slots[row.sceneSlotIndex];
  51. if (slot.slotType != format::kAuthoredSceneSlotType) {
  52. return false;
  53. }
  54. const auto resources = slot_authored_scene_resources(catalog, slot);
  55. if (resources.size() != 1 || resources.front().resourceTag != row.resourceTag) {
  56. return false;
  57. }
  58. if (index != 0) {
  59. const format::AuthoredSceneEventKey& previous = keys[index - 1];
  60. if (previous.sceneSlotIndex > row.sceneSlotIndex
  61. || (previous.sceneSlotIndex == row.sceneSlotIndex
  62. && previous.gateOffset >= row.gateOffset)) {
  63. return false;
  64. }
  65. }
  66. }
  67. return true;
  68. }
  69. /** @return True when every task target names slots and objectives the catalog holds. */
  70. [[nodiscard]] bool task_targets(const Catalog& catalog) noexcept {
  71. const auto targets = catalog.task_targets();
  72. const auto slots = catalog.slots();
  73. std::uint32_t previousTask{};
  74. bool first = true;
  75. for (const format::TaskTarget& row : targets) {
  76. if (row.taskSlotIndex >= slots.size() || row.objectiveSlotIndex >= slots.size()
  77. || row.flags != format::kTaskTargetExact || row.reserved != 0
  78. || row.referenceFieldOffset
  79. != row.descriptorOffset + format::kTaskReferenceRelativeOffset
  80. || row.bitIndex >= 24U || (!first && row.taskSlotIndex < previousTask)) {
  81. return false;
  82. }
  83. const format::Slot& task = slots[row.taskSlotIndex];
  84. const format::Slot& objective = slots[row.objectiveSlotIndex];
  85. if (task.slotType != format::kTaskSlotType
  86. || task.componentClass != format::kTaskComponentClass
  87. || task.authSchema != format::kTaskAuthSchema
  88. || objective.slotType != format::kObjectiveSlotType
  89. || objective.componentClass != format::kObjectiveComponentClass
  90. || objective.senseSchema != format::kObjectiveSenseSchema
  91. || objective.authSchema != format::kObjectiveAuthSchema
  92. || objective.objectIndex >= catalog.objects().size()
  93. || catalog.objects()[objective.objectIndex].objectKey != row.targetObjectKey) {
  94. return false;
  95. }
  96. previousTask = row.taskSlotIndex;
  97. first = false;
  98. }
  99. return true;
  100. }
  101. /** @return True when authored controls belong to their exact slots. */
  102. [[nodiscard]] bool authored_text(const Catalog& catalog) noexcept {
  103. const auto slots = catalog.slots();
  104. const format::ActorAbility* priorAbility = nullptr;
  105. for (const format::ActorAbility& row : catalog.actor_abilities()) {
  106. if (row.slotIndex >= slots.size() || slots[row.slotIndex].slotType != kAbilitySlotType
  107. || slots[row.slotIndex].componentClass != kAbilityComponentClass
  108. || slots[row.slotIndex].senseSchema != kAbilitySenseSchema
  109. || slots[row.slotIndex].authSchema != kAbilityAuthSchema
  110. || (slots[row.slotIndex].flags & format::kSlotSchemaJoinExact) == 0
  111. || row.actorClassIndex >= catalog.actor_classes().size() || row.definitionTag == 0
  112. || row.groupHash == 0 || row.groupHash == kAbsentDefinitionHash || row.requestHash == 0
  113. || row.requestHash == kAbsentDefinitionHash
  114. || (priorAbility != nullptr
  115. && std::tie(row.slotIndex, row.groupHash, row.requestHash)
  116. <= std::tie(priorAbility->slotIndex,
  117. priorAbility->groupHash,
  118. priorAbility->requestHash))) {
  119. return false;
  120. }
  121. priorAbility = &row;
  122. }
  123. const format::ActorAbilityTarget* priorTarget = nullptr;
  124. for (const format::ActorAbilityTarget& row : catalog.actor_ability_targets()) {
  125. if (row.slotIndex >= slots.size() || slots[row.slotIndex].slotType != kAbilityTargetSlotType
  126. || slots[row.slotIndex].componentClass != kAbilityTargetComponentClass
  127. || slots[row.slotIndex].senseSchema != format::kAbsentIndex
  128. || slots[row.slotIndex].authSchema != format::kAbsentIndex
  129. || (slots[row.slotIndex].flags & format::kSlotSchemaJoinExact) == 0
  130. || row.resourceTag == 0 || row.resourceTag == format::kAbsentIndex
  131. || (priorTarget != nullptr && row.slotIndex <= priorTarget->slotIndex)) {
  132. return false;
  133. }
  134. priorTarget = &row;
  135. }
  136. const format::CombatObjectiveGroup* priorGroup = nullptr;
  137. for (const format::CombatObjectiveGroup& row : catalog.combat_objective_groups()) {
  138. if (row.slotIndex >= slots.size()
  139. || slots[row.slotIndex].slotType != format::kObjectiveSlotType
  140. || slots[row.slotIndex].componentClass != format::kObjectiveComponentClass
  141. || slots[row.slotIndex].senseSchema != format::kObjectiveSenseSchema
  142. || slots[row.slotIndex].authSchema != format::kObjectiveAuthSchema
  143. || (priorGroup != nullptr && row.slotIndex < priorGroup->slotIndex)
  144. || row.groupIndex
  145. != (priorGroup != nullptr && row.slotIndex == priorGroup->slotIndex
  146. ? priorGroup->groupIndex + 1U
  147. : 0U)) {
  148. return false;
  149. }
  150. priorGroup = &row;
  151. }
  152. const format::DialogueCue* previous = nullptr;
  153. for (const format::DialogueCue& row : catalog.dialogue_cues()) {
  154. if (row.slotIndex >= slots.size() || row.cueIndex >= slots[row.slotIndex].reserved
  155. || slots[row.slotIndex].slotType != format::kDialogueSlotType
  156. || (slots[row.slotIndex].flags & format::kSlotDialogueCuesExact) == 0
  157. || row.definitionHash == 0 || row.definitionHash == kAbsentDefinitionHash
  158. || !std::isfinite(row.authoredWindowSeconds) || row.authoredWindowSeconds < 0.0F
  159. || (previous != nullptr
  160. && (row.slotIndex < previous->slotIndex
  161. || (row.slotIndex == previous->slotIndex
  162. && row.cueIndex <= previous->cueIndex)))) {
  163. return false;
  164. }
  165. previous = &row;
  166. }
  167. for (const format::DialogueCueText& row : catalog.dialogue_cue_texts()) {
  168. if (row.slotIndex >= slots.size() || row.cueIndex >= slots[row.slotIndex].reserved
  169. || slots[row.slotIndex].slotType != format::kDialogueSlotType || row.definitionHash == 0
  170. || row.definitionHash == kAbsentDefinitionHash || row.containerTag == 0
  171. || row.stringHash == 0) {
  172. return false;
  173. }
  174. }
  175. const format::DirectiveElement* priorElement = nullptr;
  176. for (const format::DirectiveElement& row : catalog.directive_elements()) {
  177. // An authored field has both source tags; an absent one has neither.
  178. const bool descriptionPaired =
  179. (row.descriptionContainerTag == 0) == (row.descriptionStringHash == 0);
  180. const bool progressPaired =
  181. (row.progressContainerTag == 0) == (row.progressStringHash == 0);
  182. // Slot, name hash then element order, without repeats, backs the per-slot range lookup
  183. // and makes every (slot, name hash, element) identity unique.
  184. if (row.slotIndex >= slots.size()
  185. || slots[row.slotIndex].slotType != format::kDirectiveSlotType || row.elementIndex < 0
  186. || static_cast<std::uint32_t>(row.elementIndex) >= row.elementCount
  187. || row.titleContainerTag == 0 || row.titleStringHash == 0 || !descriptionPaired
  188. || !progressPaired
  189. || (row.descriptionContainerTag == 0 && row.progressContainerTag == 0)
  190. || (priorElement != nullptr
  191. && std::tie(row.slotIndex, row.nameHash, row.elementIndex)
  192. <= std::tie(priorElement->slotIndex,
  193. priorElement->nameHash,
  194. priorElement->elementIndex))) {
  195. return false;
  196. }
  197. priorElement = &row;
  198. }
  199. return true;
  200. }
  201. /** @return True when every behavior edge names nodes of its own program. */
  202. [[nodiscard]] bool behavior_edges(const Catalog& catalog) noexcept {
  203. const std::size_t programs = catalog.behavior_programs().size();
  204. for (const format::BehaviorInput& row : catalog.behavior_inputs()) {
  205. if (row.programIndex >= programs || row.reserved != 0) {
  206. return false;
  207. }
  208. }
  209. for (const format::BehaviorChannelWrite& row : catalog.behavior_channel_writes()) {
  210. if (row.programIndex >= programs || row.reserved != 0) {
  211. return false;
  212. }
  213. }
  214. const auto owners = catalog.behavior_owners();
  215. for (const format::BehaviorOwner& row : owners) {
  216. if (row.programIndex >= programs || row.actorClassIndex >= catalog.actor_classes().size()) {
  217. return false;
  218. }
  219. }
  220. for (const format::BehaviorActivityBinding& row : catalog.behavior_activity_bindings()) {
  221. if (row.ownerIndex >= owners.size() || row.squadIndex >= catalog.squads().size()
  222. || row.squadMemberIndex >= catalog.squad_members().size()
  223. || row.scenarioIndex >= catalog.scenarios().size()
  224. || row.occurrenceIndex >= catalog.occurrences().size()
  225. || row.stateIndex >= catalog.states().size()
  226. || row.objectIndex >= catalog.objects().size() || row.reserved != 0) {
  227. return false;
  228. }
  229. }
  230. return true;
  231. }
  232. /** @return True when actor rows, their commands and their states resolve against each other. */
  233. [[nodiscard]] bool actor_semantics(const Catalog& catalog) noexcept {
  234. const auto messages = catalog.actor_message_schemas();
  235. const auto commands = catalog.actor_command_definitions();
  236. const auto profiles = catalog.actor_behavior_profiles();
  237. const auto schemas = catalog.runtime_schemas();
  238. if (messages.size() != 1 || commands.size() != 98
  239. || profiles.size() != catalog.actor_classes().size()) {
  240. return false;
  241. }
  242. const format::ActorMessageSchema& message = messages.front();
  243. if (message.definitionHandle == 0 || message.durableKey == 0 || message.ownerClass == 0
  244. || message.provenance != format::ActorSemanticProvenance::executableStatic
  245. || message.commands.first != 0 || message.commands.count != commands.size()
  246. || message.flags != format::kActorMessageSchemaExact || message.reserved != 0) {
  247. return false;
  248. }
  249. for (std::size_t index = 0; index < commands.size(); ++index) {
  250. const format::ActorCommandDefinition& command = commands[index];
  251. const bool setFaction = index == 45U;
  252. if (command.selector != index || command.payloadHandle == 0
  253. || command.effect
  254. != (setFaction ? format::ActorCommandEffect::setFaction
  255. : format::ActorCommandEffect::opaque)
  256. || command.provenance != format::ActorSemanticProvenance::executableStatic
  257. || command.flags != format::kActorCommandDefinitionExact
  258. || (!setFaction
  259. && (command.factionNoneName.length != 0 || command.factionRemovedName.length != 0
  260. || command.factionHostileToAllName.length != 0 || command.factionNone != 0
  261. || command.factionRemoved != 0 || command.factionHostileToAll != 0))
  262. || (setFaction
  263. && (command.factionNone >= 0 || command.factionRemoved >= 0
  264. || command.factionHostileToAll >= 0
  265. || command.factionNone == command.factionRemoved
  266. || command.factionNone == command.factionHostileToAll
  267. || command.factionRemoved == command.factionHostileToAll))) {
  268. return false;
  269. }
  270. if (std::none_of(
  271. schemas.begin(), schemas.end(), [&command](const format::RuntimeSchema& row) {
  272. return row.handle == command.payloadHandle;
  273. })) {
  274. return false;
  275. }
  276. }
  277. for (std::size_t index = 0; index < profiles.size(); ++index) {
  278. const format::ActorBehaviorProfile& profile = profiles[index];
  279. const bool absent =
  280. profile.behaviorConfigTag == 0 || profile.behaviorConfigTag == format::kAbsentIndex;
  281. if (profile.actorClassIndex != index
  282. || (absent
  283. && (profile.behaviorConfigClass != format::kAbsentIndex
  284. || profile.behaviorProvenance != format::ActorSemanticProvenance::notPresent))
  285. || (!absent
  286. && (profile.behaviorConfigClass != format::kActorBehaviorConfigClass
  287. || profile.behaviorProvenance != format::ActorSemanticProvenance::packageField))
  288. || profile.behaviorConfigOffset != format::kActorBehaviorConfigRelativeOffset
  289. || profile.defaultFaction != 0
  290. || profile.factionProvenance != format::ActorSemanticProvenance::engineZeroDefault
  291. || profile.flags != format::kActorBehaviorProfileExact) {
  292. return false;
  293. }
  294. }
  295. return true;
  296. }
  297. /** @return True when every runtime type, schema and codec family agrees with its users. */
  298. [[nodiscard]] bool runtime_semantics(const Catalog& catalog) noexcept {
  299. const auto events = catalog.simulation_event_definitions();
  300. const auto schemas = catalog.runtime_schemas();
  301. const auto fields = catalog.runtime_fields();
  302. const auto types = catalog.runtime_type_definitions();
  303. if (events.size() != 22 || schemas.empty() || fields.empty() || types.empty()
  304. || types.size() % 47 != 0) {
  305. return false;
  306. }
  307. // Every bit a runtime type row may set; any other bit means an unknown extraction.
  308. constexpr std::uint32_t kTypeFlagMask =
  309. format::kRuntimeTypeDefinitionExact | format::kRuntimeTypeFixed
  310. | format::kRuntimeTypeParametric | format::kRuntimeTypeValueDependent
  311. | format::kRuntimeTypeNullable | format::kRuntimeTypeNested | format::kRuntimeTypeUnion
  312. | format::kRuntimeTypeResolved | format::kRuntimeTypeRemapped
  313. | format::kRuntimeTypeUnsupported | format::kRuntimeTypeObfuscated
  314. | format::kRuntimeTypeSelectorValidationRequired;
  315. for (std::size_t index = 0; index < types.size(); ++index) {
  316. const format::RuntimeTypeDefinition& type = types[index];
  317. const bool fixed = (type.flags & format::kRuntimeTypeFixed) != 0;
  318. const bool unsupported = (type.flags & format::kRuntimeTypeUnsupported) != 0;
  319. const bool hasFixedMeasurement = type.fixedBits != format::kAbsentIndex;
  320. const bool unsupportedNoopMeasurement =
  321. unsupported && type.fixedBits == 0 && type.minimumBits == 0 && type.maximumBits == 0;
  322. if ((type.codecFamilies
  323. & ~(static_cast<std::uint32_t>(format::RuntimeCodecFamily::activity)
  324. | static_cast<std::uint32_t>(format::RuntimeCodecFamily::sobjectModeZero)
  325. | static_cast<std::uint32_t>(format::RuntimeCodecFamily::sobjectModeOne)))
  326. != 0
  327. || type.codecFamilies == 0 || type.typeCode != index % 47
  328. || catalog.string(type.name).empty() || type.writerEvidenceAddress == 0
  329. || type.readerEvidenceAddress == 0 || (type.flags & ~kTypeFlagMask) != 0
  330. || (type.flags & format::kRuntimeTypeDefinitionExact) == 0 || type.reserved != 0
  331. || (fixed && !hasFixedMeasurement)
  332. || (!fixed && hasFixedMeasurement && !unsupportedNoopMeasurement)
  333. || (fixed && (type.minimumBits != type.fixedBits || type.maximumBits != type.fixedBits))
  334. || (type.minimumBits != format::kAbsentIndex && type.maximumBits != format::kAbsentIndex
  335. && type.minimumBits > type.maximumBits)) {
  336. return false;
  337. }
  338. }
  339. for (std::size_t index = 0; index < events.size(); ++index) {
  340. const format::SimulationEventDefinition& row = events[index];
  341. const bool primaryAbsent = row.primarySchema == format::kAbsentIndex;
  342. const bool secondaryAbsent = row.secondarySchema == format::kAbsentIndex;
  343. if (row.provenance != format::ActorSemanticProvenance::executableStatic
  344. || row.descriptorEvidenceAddress == 0
  345. || primaryAbsent != ((row.flags & format::kSimulationEventPrimaryAbsent) != 0)
  346. || secondaryAbsent != ((row.flags & format::kSimulationEventSecondaryAbsent) != 0)
  347. || (!primaryAbsent
  348. && (row.primaryEvidenceAddress == 0
  349. || runtime_schema_by_handle(catalog, row.primarySchema) == nullptr))
  350. || (!secondaryAbsent
  351. && (row.secondaryEvidenceAddress == 0
  352. || runtime_schema_by_handle(catalog, row.secondarySchema) == nullptr))
  353. || (row.flags
  354. & ~(format::kSimulationEventDefinitionExact | format::kSimulationEventPrimaryAbsent
  355. | format::kSimulationEventSecondaryAbsent))
  356. != 0
  357. || row.reserved != 0) {
  358. return false;
  359. }
  360. for (std::size_t prior = 0; prior < index; ++prior) {
  361. if (events[prior].eventType == row.eventType
  362. || catalog.string(events[prior].name) == catalog.string(row.name)) {
  363. return false;
  364. }
  365. }
  366. }
  367. std::size_t fieldCursor = 0;
  368. for (std::size_t index = 0; index < schemas.size(); ++index) {
  369. const format::RuntimeSchema& schema = schemas[index];
  370. if (schema.handle == 0 || schema.handle == format::kAbsentIndex
  371. || (schema.decodedSize == 0 && schema.fields.count != 0) || schema.definitionHash == 0
  372. || schema.definitionClass == format::kAbsentIndex || schema.codecFamilies == 0
  373. || (schema.codecFamilies
  374. & ~(static_cast<std::uint32_t>(format::RuntimeCodecFamily::activity)
  375. | static_cast<std::uint32_t>(format::RuntimeCodecFamily::sobjectModeZero)
  376. | static_cast<std::uint32_t>(format::RuntimeCodecFamily::sobjectModeOne)))
  377. != 0
  378. || schema.provenance != format::ActorSemanticProvenance::executableStatic
  379. || schema.fields.first != fieldCursor
  380. || schema.fields.count > fields.size() - fieldCursor || schema.evidenceAddress == 0
  381. || (schema.flags & ~(format::kRuntimeSchemaExact | format::kRuntimeSchemaArrayRegion))
  382. != 0
  383. || (schema.flags & format::kRuntimeSchemaExact) == 0
  384. || (schema.arrayElementCount != 0)
  385. != ((schema.flags & format::kRuntimeSchemaArrayRegion) != 0)
  386. || (index != 0 && schemas[index - 1U].handle >= schema.handle)) {
  387. return false;
  388. }
  389. for (std::uint32_t ordinal = 0; ordinal < schema.fields.count; ++ordinal) {
  390. const format::RuntimeField& field = fields[fieldCursor + ordinal];
  391. if (field.schemaIndex != index || field.ordinal != ordinal
  392. || field.structOffset >= schema.decodedSize
  393. || (field.flags
  394. & ~(format::kRuntimeFieldExact | format::kRuntimeFieldPresenceBit
  395. | format::kRuntimeFieldDynamicActorCommand
  396. | format::kRuntimeFieldNestedSchema | format::kRuntimeFieldDynamicArray
  397. | format::kRuntimeFieldCustomCodec))
  398. != 0
  399. || (field.flags & format::kRuntimeFieldExact) == 0
  400. || ((field.nestedHandle != format::kAbsentIndex)
  401. != ((field.flags & format::kRuntimeFieldNestedSchema) != 0))
  402. || ((field.flags & format::kRuntimeFieldDynamicArray) != 0
  403. && field.typeCode
  404. != static_cast<std::uint32_t>(format::RuntimeFieldType::nested))
  405. || (schema.codecFamilies
  406. & static_cast<std::uint32_t>(format::RuntimeCodecFamily::activity))
  407. != 0
  408. && runtime_type_by_code(
  409. catalog, format::RuntimeCodecFamily::activity, field.typeCode)
  410. == nullptr
  411. || (schema.codecFamilies
  412. & static_cast<std::uint32_t>(format::RuntimeCodecFamily::sobjectModeZero))
  413. != 0
  414. && runtime_type_by_code(
  415. catalog, format::RuntimeCodecFamily::sobjectModeZero, field.typeCode)
  416. == nullptr
  417. || (schema.codecFamilies
  418. & static_cast<std::uint32_t>(format::RuntimeCodecFamily::sobjectModeOne))
  419. != 0
  420. && runtime_type_by_code(
  421. catalog, format::RuntimeCodecFamily::sobjectModeOne, field.typeCode)
  422. == nullptr) {
  423. return false;
  424. }
  425. if (field.nestedHandle != format::kAbsentIndex
  426. && runtime_schema_by_handle(catalog, field.nestedHandle) == nullptr) {
  427. return false;
  428. }
  429. }
  430. fieldCursor += schema.fields.count;
  431. }
  432. if (fieldCursor != fields.size()) {
  433. return false;
  434. }
  435. const format::ActorMessageSchema& message = catalog.actor_message_schemas().front();
  436. const auto command =
  437. std::find_if(catalog.actor_command_definitions().begin(),
  438. catalog.actor_command_definitions().end(),
  439. [](const format::ActorCommandDefinition& row) {
  440. return row.effect == format::ActorCommandEffect::setFaction;
  441. });
  442. if (command == catalog.actor_command_definitions().end()) {
  443. return false;
  444. }
  445. const format::RuntimeSchema* const messageSchema =
  446. runtime_schema_by_handle(catalog, message.definitionHandle);
  447. const format::RuntimeSchema* const payloadSchema =
  448. runtime_schema_by_handle(catalog, command->payloadHandle);
  449. if (messageSchema == nullptr || payloadSchema == nullptr) {
  450. return false;
  451. }
  452. const auto messageFields = runtime_schema_fields(catalog, *messageSchema);
  453. const auto payloadFields = runtime_schema_fields(catalog, *payloadSchema);
  454. return messageFields.size() == 1 && messageFields.front().typeCode == message.bodyType
  455. && (messageFields.front().flags & format::kRuntimeFieldDynamicActorCommand) != 0
  456. && payloadFields.size() == 1 && payloadFields.front().typeCode == 5
  457. && payloadFields.front().bits == 32;
  458. }
  459. /** @return True when every SObject row, its RSAT and its descriptors resolve. */
  460. [[nodiscard]] bool sobject_semantics(const Catalog& catalog) noexcept {
  461. const auto rsats = catalog.sobject_rsats();
  462. const auto descriptors = catalog.sobject_rsat_descriptors();
  463. const auto schemas = catalog.rsat_schemas();
  464. const auto fields = catalog.rsat_fields();
  465. const auto bindings = catalog.sobject_rsat_field_bindings();
  466. if (rsats.empty() || descriptors.empty() || bindings.size() != fields.size()) {
  467. return false;
  468. }
  469. std::size_t descriptorCursor = 0;
  470. for (std::size_t rsatIndex = 0; rsatIndex < rsats.size(); ++rsatIndex) {
  471. const format::SobjectRsat& rsat = rsats[rsatIndex];
  472. if (rsat.rsatTag == 0 || rsat.rsatTag == format::kAbsentIndex
  473. || rsat.reverseDefinitionTag == 0 || rsat.reverseDefinitionTag == format::kAbsentIndex
  474. || rsat.provenance != format::ActorSemanticProvenance::packageField
  475. || rsat.descriptors.first != descriptorCursor || rsat.flags != format::kSobjectRsatExact
  476. || rsat.reserved != 0
  477. || (rsatIndex != 0 && rsats[rsatIndex - 1U].rsatTag >= rsat.rsatTag)) {
  478. return false;
  479. }
  480. std::uint32_t tailOrdinal = 0;
  481. for (std::uint32_t ordinal = 0; ordinal < rsat.descriptors.count; ++ordinal) {
  482. const format::SobjectRsatDescriptor& descriptor =
  483. descriptors[descriptorCursor + ordinal];
  484. const bool dynamic =
  485. (descriptor.flags & format::kSobjectRsatDescriptorDynamicPresenceEligible) != 0;
  486. if (descriptor.rsatIndex != rsatIndex || descriptor.descriptorOrdinal != ordinal
  487. || descriptor.schemaIndex >= schemas.size()
  488. || descriptor.schemaTag != schemas[descriptor.schemaIndex].schemaTag
  489. || descriptor.schemaFieldCount != schemas[descriptor.schemaIndex].fieldCount
  490. || descriptor.schemaFirstFieldRuntimeGate
  491. != schemas[descriptor.schemaIndex].firstFieldRuntimeGate
  492. || (dynamic && descriptor.dynamicPresenceTailOrdinal != tailOrdinal)
  493. || (!dynamic && descriptor.dynamicPresenceTailOrdinal != format::kAbsentIndex)) {
  494. return false;
  495. }
  496. tailOrdinal += dynamic ? 1U : 0U;
  497. }
  498. if (tailOrdinal != rsat.dynamicPresenceTailCount) {
  499. return false;
  500. }
  501. descriptorCursor += rsat.descriptors.count;
  502. }
  503. if (descriptorCursor != descriptors.size()) {
  504. return false;
  505. }
  506. for (std::size_t index = 0; index < bindings.size(); ++index) {
  507. const format::SobjectRsatFieldBinding& binding = bindings[index];
  508. const bool hasSchema = binding.runtimeSchemaHandle != format::kAbsentIndex;
  509. const format::RuntimeSchema* const runtime =
  510. hasSchema ? runtime_schema_by_handle(catalog, binding.runtimeSchemaHandle) : nullptr;
  511. if (binding.rsatFieldIndex != index
  512. || binding.provenance != format::ActorSemanticProvenance::packageField
  513. || (binding.flags & format::kSobjectRsatFieldBindingExact) == 0
  514. || hasSchema
  515. != ((binding.flags & format::kSobjectRsatFieldBindingHasRuntimeSchema) != 0)
  516. || (hasSchema
  517. && (runtime == nullptr || binding.definitionClass != runtime->definitionClass
  518. || binding.codecFamilies != runtime->codecFamilies))
  519. || (!hasSchema && (binding.definitionClass != 0 || binding.codecFamilies != 0))) {
  520. return false;
  521. }
  522. }
  523. return true;
  524. }
  525. /** @return True when every entity type names a live schema and a unique generated name. */
  526. [[nodiscard]] bool entity_types(const Catalog& catalog) noexcept {
  527. const auto rows = catalog.entity_type_definitions();
  528. if (rows.empty()) {
  529. return false;
  530. }
  531. for (std::size_t index = 0; index < rows.size(); ++index) {
  532. const format::EntityTypeDefinition& row = rows[index];
  533. const bool baselinePresent = row.baselineSchema != format::kAbsentIndex;
  534. const bool updatePresent = row.updateSchema != format::kAbsentIndex;
  535. const bool dynamicUpdate = (row.flags & format::kEntityTypeUpdateUsesSobjectRsat) != 0;
  536. if (row.provenance != format::ActorSemanticProvenance::executableStatic
  537. || row.vtableEvidenceAddress == 0
  538. || baselinePresent != (row.baselineEvidenceAddress != 0)
  539. || (updatePresent && row.updateEvidenceAddress == 0)
  540. || ((row.flags & format::kEntityTypeUpdateSupported) != 0 && !updatePresent
  541. && !dynamicUpdate)
  542. || (baselinePresent && runtime_schema_by_handle(catalog, row.baselineSchema) == nullptr)
  543. || (updatePresent && runtime_schema_by_handle(catalog, row.updateSchema) == nullptr)
  544. || (row.flags
  545. & ~(format::kEntityTypeDefinitionExact | format::kEntityTypeStockEmittable
  546. | format::kEntityTypeUpdateSupported
  547. | format::kEntityTypeUpdateUsesSobjectRsat))
  548. != 0
  549. || row.reserved != 0) {
  550. return false;
  551. }
  552. for (std::size_t prior = 0; prior < index; ++prior) {
  553. if (rows[prior].entityType == row.entityType
  554. || catalog.string(rows[prior].name) == catalog.string(row.name)) {
  555. return false;
  556. }
  557. }
  558. }
  559. return true;
  560. }
  561. /**
  562. * State names are grouped by actor class, contiguous from ordinal zero, and never empty names.
  563. * The refused row's index reaches the caller so the failure can name it.
  564. */
  565. [[nodiscard]] bool actor_state_names(const Catalog& catalog, std::size_t& refused) noexcept {
  566. const auto rows = catalog.actor_state_names();
  567. const auto actors = catalog.actor_classes();
  568. for (std::size_t index = 0; index < rows.size(); ++index) {
  569. const format::ActorStateName& row = rows[index];
  570. const bool first = index == 0 || rows[index - 1U].actorClassIndex != row.actorClassIndex;
  571. const bool ordered =
  572. first ? (index == 0 || rows[index - 1U].actorClassIndex < row.actorClassIndex)
  573. : rows[index - 1U].ordinal + 1U == row.ordinal;
  574. if (!ordered || (first && row.ordinal != 0) || row.actorClassIndex >= actors.size()
  575. || row.definitionTag == 0 || row.definitionTag == format::kAbsentIndex
  576. || row.groupHash != format::kActorStateMachineGroupHash || row.nameHash == 0
  577. || row.nameHash == format::kAbsentIndex || row.flags != format::kActorStateNameExact) {
  578. refused = index;
  579. return false;
  580. }
  581. }
  582. return true;
  583. }
  584. /** Holds the last refusal so a rejected estate names its cause instead of one opaque status. */
  585. std::array<char, 128> g_reason{};
  586. void remember(const char* check) noexcept {
  587. const int written = std::snprintf(g_reason.data(), g_reason.size(), "%s", check);
  588. if (written <= 0) {
  589. g_reason[0] = '\0';
  590. }
  591. }
  592. /** Adds the refused row's own fields, which is what tells a producer bug from a wire bug. */
  593. void remember_state_name(const Catalog& catalog, std::size_t refused) noexcept {
  594. const auto rows = catalog.actor_state_names();
  595. if (refused >= rows.size()) {
  596. remember("actor_state_names");
  597. return;
  598. }
  599. const format::ActorStateName& row = rows[refused];
  600. const int written = std::snprintf(g_reason.data(),
  601. g_reason.size(),
  602. "actor_state_names/row=%zu/actor=%u/actors=%zu/ordinal=%u"
  603. "/name=%08X/def=%08X/group=%08X/flags=%u",
  604. refused,
  605. row.actorClassIndex,
  606. catalog.actor_classes().size(),
  607. row.ordinal,
  608. row.nameHash,
  609. row.definitionTag,
  610. row.groupHash,
  611. row.flags);
  612. if (written <= 0) {
  613. remember("actor_state_names");
  614. }
  615. }
  616. } // namespace
  617. /** Runs every cross-section relation check in order, remembering the first that refuses. */
  618. bool relations(const Catalog& catalog) {
  619. if (!valid_actor_sequences(catalog)) {
  620. remember("actor_sequences");
  621. return false;
  622. }
  623. std::size_t refused = 0;
  624. if (!actor_state_names(catalog, refused)) {
  625. remember_state_name(catalog, refused);
  626. return false;
  627. }
  628. struct Check final {
  629. const char* name;
  630. bool (*run)(const Catalog&) noexcept;
  631. };
  632. // Every relation check a catalog must pass, named so a refusal reports which one failed.
  633. static constexpr std::array<Check, 9> kChecks{
  634. {{"authored_scene_event_keys", &authored_scene_event_keys},
  635. {"squad_profiles", &squad_profiles},
  636. {"task_targets", &task_targets},
  637. {"authored_text", &authored_text},
  638. {"behavior_edges", &behavior_edges},
  639. {"actor_semantics", &actor_semantics},
  640. {"runtime_semantics", &runtime_semantics},
  641. {"sobject_semantics", &sobject_semantics},
  642. {"entity_types", &entity_types}}};
  643. for (const Check& check : kChecks) {
  644. if (!check.run(catalog)) {
  645. remember(check.name);
  646. return false;
  647. }
  648. }
  649. return true;
  650. }
  651. } // namespace sunrise::state::activity_sdk::validation
  652. namespace sunrise::state::activity_sdk {
  653. /** Validates one catalog: structure first, then relations. @return False on the first refusal. */
  654. bool valid_catalog(const Catalog& value) noexcept {
  655. try {
  656. if (!validation::structure(value)) {
  657. validation::remember("structure");
  658. return false;
  659. }
  660. return validation::relations(value);
  661. } catch (...) {
  662. validation::remember("threw");
  663. return false;
  664. }
  665. }
  666. const char* last_catalog_reason() noexcept {
  667. return validation::g_reason.data();
  668. }
  669. } // namespace sunrise::state::activity_sdk