package_starter_set_build.cpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. #include <array>
  2. #include <cstddef>
  3. #include <cstring>
  4. #include "../../../../core/logging/log.h"
  5. #include "internal.h"
  6. namespace sunrise::client::content::items::packages {
  7. namespace {
  8. namespace domain = state::build_data::starter_sets;
  9. /** Package class of the new-character table blob. */
  10. constexpr std::uint32_t kStarterSetTableClass = 0x808075BAU;
  11. /** Element class of the table's per-class rows. */
  12. constexpr std::uint32_t kStarterSetRowClass = 0x808074E7U;
  13. /** Element class of an array of 16-bit item-table indices. */
  14. constexpr std::uint32_t kItemIndexArrayClass = 0x808087DBU;
  15. /** An array field is a 64-bit count and a 64-bit self-relative offset. */
  16. constexpr std::size_t kArrayFieldSize = 16;
  17. /** The installed class rows are 40 bytes apart. */
  18. constexpr std::size_t kStarterSetRowSize = 40;
  19. /** One class row: the class byte, then two arrays of item-table indices. */
  20. struct StarterSetRow {
  21. std::uint8_t characterClass;
  22. std::array<std::byte, 7> opaque01;
  23. std::array<std::byte, kArrayFieldSize> firstItems;
  24. std::array<std::byte, kArrayFieldSize> secondItems;
  25. };
  26. static_assert(sizeof(StarterSetRow) == kStarterSetRowSize);
  27. /**
  28. * Appends one item-index array of a class row to that row's definition.
  29. * @param blob Whole table blob.
  30. * @param descriptorOffset Offset of the array's count and self-relative offset.
  31. * @param output Row whose item list grows.
  32. * @return False when the array does not read or overflows the row's storage.
  33. */
  34. [[nodiscard]] bool append_items(std::span<const std::byte> blob,
  35. std::size_t descriptorOffset,
  36. domain::Definition& output) noexcept {
  37. tables::Array items{};
  38. if (!tables::find_optional_array_at(blob, descriptorOffset, items)) {
  39. return false;
  40. }
  41. if (items.count == 0) {
  42. return true;
  43. }
  44. const std::size_t size = static_cast<std::size_t>(items.count) * sizeof(std::uint16_t);
  45. if (items.elementClass != kItemIndexArrayClass
  46. || items.count > domain::kItemCapacity - output.itemCount || items.dataOffset > blob.size()
  47. || size > blob.size() - items.dataOffset) {
  48. return false;
  49. }
  50. for (std::size_t index = 0; index < items.count; ++index) {
  51. std::uint16_t itemIndex = 0;
  52. std::memcpy(&itemIndex,
  53. blob.data() + items.dataOffset + index * sizeof(std::uint16_t),
  54. sizeof itemIndex);
  55. output.itemIndices[output.itemCount++] = itemIndex;
  56. }
  57. return true;
  58. }
  59. /**
  60. * Reads every class row of the new-character table.
  61. * @param blob Whole table blob.
  62. * @param output Receives one definition per row.
  63. * @param count Receives the row count.
  64. * @return False when the rows do not match the installed layout.
  65. */
  66. [[nodiscard]] bool read_rows(std::span<const std::byte> blob,
  67. std::array<domain::Definition, domain::kClassCount>& output,
  68. std::size_t& count) noexcept {
  69. count = 0;
  70. tables::Array rows{};
  71. if (!tables::find_array(blob, kStarterSetRowClass, rows) || rows.count > output.size()
  72. || rows.dataOffset > blob.size()
  73. || static_cast<std::size_t>(rows.count) * sizeof(StarterSetRow)
  74. > blob.size() - rows.dataOffset) {
  75. return false;
  76. }
  77. for (std::size_t row = 0; row < rows.count; ++row) {
  78. const std::size_t base = rows.dataOffset + row * sizeof(StarterSetRow);
  79. domain::Definition& definition = output[row];
  80. definition = {};
  81. definition.characterClass =
  82. std::to_integer<std::uint8_t>(blob[base + offsetof(StarterSetRow, characterClass)]);
  83. if (!append_items(blob, base + offsetof(StarterSetRow, firstItems), definition)
  84. || !append_items(blob, base + offsetof(StarterSetRow, secondItems), definition)) {
  85. return false;
  86. }
  87. }
  88. count = static_cast<std::size_t>(rows.count);
  89. return true;
  90. }
  91. } // namespace
  92. /**
  93. * Reads and publishes the new-character table: per class, the items a new character gets.
  94. * A table that reads but does not match the layout publishes empty, so the pass settles.
  95. * @param source Package source.
  96. * @param scratch Reader scratch.
  97. * @param root Investment root bytes.
  98. * @param blob Scratch storage for the table blob.
  99. * @return False only when the tag does not read, so a later slice retries it.
  100. */
  101. bool build_starter_sets(const reader::Source& source,
  102. reader::Scratch& scratch,
  103. std::span<const std::byte> root,
  104. std::vector<std::byte>& blob) noexcept {
  105. std::uint32_t tag = 0;
  106. std::uint32_t tableClass = 0;
  107. if (!tables::slot_tag(root, tables::kStarterSetTableSlot, tag) || tag == 0
  108. || !reader::read_tag(source, scratch, tag, blob, tableClass)) {
  109. return false;
  110. }
  111. std::array<domain::Definition, domain::kClassCount> rows{};
  112. std::size_t count = 0;
  113. if (tableClass != kStarterSetTableClass
  114. || !read_rows(std::span<const std::byte>{blob}, rows, count)) {
  115. count = 0;
  116. core::log::writef(core::log::Channel::client,
  117. core::log::Level::warn,
  118. "ev=starter_sets stage=read result=fail tag=0x%08X class=0x%08X",
  119. tag,
  120. tableClass);
  121. }
  122. return state::build_data::publish_starter_sets(std::span(rows).first(count));
  123. }
  124. } // namespace sunrise::client::content::items::packages