activity_table.h 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. #pragma once
  2. #include <cstring>
  3. #include <limits>
  4. #include "../../../../state/build_data/activities/activity_catalog.h"
  5. #include "definition_index_table.h"
  6. namespace sunrise::middleware::content::packages::tables::activities {
  7. /** Public activity index, NOT the package-definition index in root slot 29. */
  8. inline constexpr std::size_t kRootSlot = 4;
  9. inline constexpr std::uint32_t kRowClass = 0x808076FCU;
  10. /** One index row: the definition hash, then a self-relative pointer to the record. */
  11. inline constexpr std::size_t kIndexRowStride = 16;
  12. inline constexpr std::size_t kIndexRecordField = 8;
  13. /** Self-relative pointer to the internal package name, or zero when the row has none. */
  14. inline constexpr std::size_t kRecordNameField = 0x68;
  15. /** Activity type, a row of the activity type table. */
  16. inline constexpr std::size_t kRecordTypeOffset = 0xDA;
  17. inline constexpr std::size_t kRecordGameplaySettingsOffset = 0xDC;
  18. /** Destination, a row of the destination table. */
  19. inline constexpr std::size_t kRecordDestinationOffset = 0xE0;
  20. /** Every field above sits inside this many bytes of a record. */
  21. inline constexpr std::size_t kRecordReadBytes = 0xE4;
  22. template <class T>
  23. [[nodiscard]] bool read(std::span<const std::byte> bytes, std::size_t offset, T& value) noexcept {
  24. if (offset > bytes.size() || sizeof(T) > bytes.size() - offset) {
  25. return false;
  26. }
  27. std::memcpy(&value, bytes.data() + offset, sizeof(T));
  28. return true;
  29. }
  30. /**
  31. * Resolves a self-relative field.
  32. * @param field Offset of the 8-byte signed delta.
  33. * @param target Receives the resolved offset.
  34. * @return False when the delta is zero or the target falls outside the blob.
  35. */
  36. [[nodiscard]] inline bool
  37. relative(std::span<const std::byte> bytes, std::size_t field, std::size_t& target) noexcept {
  38. std::int64_t delta{};
  39. if (!read(bytes, field, delta) || delta == 0
  40. || field > static_cast<std::size_t>((std::numeric_limits<std::int64_t>::max)())) {
  41. return false;
  42. }
  43. const auto base = static_cast<std::int64_t>(field);
  44. if (delta < -base || delta > (std::numeric_limits<std::int64_t>::max)() - base) {
  45. return false;
  46. }
  47. target = static_cast<std::size_t>(base + delta);
  48. return target < bytes.size();
  49. }
  50. /**
  51. * Parses the dense public activity table.
  52. * @param bytes The table blob.
  53. * @param output Receives one row per ordinal.
  54. * @param count Receives the row count.
  55. * @return False when a row fails its identity check or a name is malformed.
  56. */
  57. [[nodiscard]] inline bool decode(std::span<const std::byte> bytes,
  58. std::span<state::build_data::activities::Definition> output,
  59. std::size_t& count) noexcept {
  60. count = 0;
  61. Array array{};
  62. if (!find_array_at(bytes, kTableArrayDescriptor, array) || array.elementClass != kRowClass
  63. || array.count > output.size() || array.count > state::build_data::activities::kCapacity
  64. || array.dataOffset > bytes.size()
  65. || array.count > (bytes.size() - array.dataOffset) / kIndexRowStride) {
  66. return false;
  67. }
  68. for (std::size_t i = 0; i < array.count; ++i) {
  69. state::build_data::activities::Definition row{};
  70. row.index = static_cast<std::uint16_t>(i);
  71. const auto entry = array.dataOffset + i * kIndexRowStride;
  72. std::size_t record{};
  73. std::uint32_t identity{};
  74. // The record repeats the index row's hash; both copies must agree.
  75. if (!read(bytes, entry, row.hash) || row.hash == 0
  76. || !relative(bytes, entry + kIndexRecordField, record)
  77. || bytes.size() - record < kRecordReadBytes || !read(bytes, record, identity)
  78. || identity != row.hash
  79. || !read(bytes, record + kRecordGameplaySettingsOffset, row.gameplaySettingsHash)
  80. || !read(bytes, record + kRecordTypeOffset, row.nativeType)
  81. || !read(bytes, record + kRecordDestinationOffset, row.destination)) {
  82. return false;
  83. }
  84. std::int64_t nameDelta{};
  85. if (!read(bytes, record + kRecordNameField, nameDelta)) {
  86. return false;
  87. }
  88. if (nameDelta != 0) {
  89. std::size_t name{};
  90. if (!relative(bytes, record + kRecordNameField, name)) {
  91. return false;
  92. }
  93. bool terminated = false;
  94. for (std::size_t n = 0; n < row.package.size() && n < bytes.size() - name; ++n) {
  95. const auto c = static_cast<char>(bytes[name + n]);
  96. if (c == '\0') {
  97. terminated = true;
  98. break;
  99. }
  100. if (n + 1 == row.package.size()
  101. || (c != '_' && (c < 'a' || c > 'z') && (c < '0' || c > '9'))) {
  102. return false;
  103. }
  104. row.package[n] = c;
  105. }
  106. if (!terminated) {
  107. return false;
  108. }
  109. }
  110. output[i] = row;
  111. }
  112. count = static_cast<std::size_t>(array.count);
  113. return count != 0;
  114. }
  115. } // namespace sunrise::middleware::content::packages::tables::activities