item_definition_reader.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #include <algorithm>
  2. #include <cstring>
  3. #include "definition_index_table.h"
  4. #include "items.h"
  5. namespace sunrise::middleware::content::packages::tables::items {
  6. namespace {
  7. /** The signed stack limit comes before the bucket id. */
  8. constexpr std::size_t kMaxStackSizeOffset = 180;
  9. /** A nonzero predicate byte marks an instanced definition. */
  10. constexpr std::size_t kInstancedOffset = 187;
  11. /** The equipment block is self-relative from this offset, zero when absent. */
  12. constexpr std::size_t kEquipmentBlockOffset = 16;
  13. /** The equipment block stores its signed slot id here. */
  14. constexpr std::size_t kEquipmentSlotOffset = 24;
  15. /** The socket entry list block is self-relative from this offset, zero when absent. */
  16. constexpr std::size_t kSocketEntryListBlockOffset = 128;
  17. /** A present block declares 12 bytes. A shorter tail is not a block. */
  18. constexpr std::size_t kSocketEntryListBlockSize = 12;
  19. /** Equipment slots run from 0 through 19. */
  20. constexpr std::int32_t kEquipmentSlotCount = 20;
  21. /** A socket entry stores its type index first and its initial plug next. */
  22. constexpr std::size_t kSocketTypeOffset = 0;
  23. constexpr std::size_t kSocketPlugOffset = 2;
  24. /** Fixed fields end after the instanced predicate. */
  25. constexpr std::size_t kFixedFieldEnd = kInstancedOffset + 1;
  26. /** Optional plug category used to expand three native reusable plug families. */
  27. constexpr std::size_t kPlugCategoryOffset = 392;
  28. /** Embedded reusable-list array descriptor inside one 80-byte ordinary socket entry. */
  29. constexpr std::size_t kEmbeddedPlugListOffset = 64;
  30. /** Reusable and randomized shared plug-set row indices inside one socket entry. */
  31. constexpr std::size_t kReusablePlugSetIndexOffset = 12;
  32. constexpr std::size_t kRandomizedPlugSetIndexOffset = 32;
  33. /** One shared plug-set table row is 24 bytes and carries its member descriptor at byte 8. */
  34. constexpr std::size_t kPlugSetRowStride = 24;
  35. constexpr std::size_t kPlugSetMemberDescriptorOffset = 8;
  36. /** Both embedded and shared plug member rows name an item index first and occupy 32 bytes. */
  37. constexpr std::size_t kPlugMemberStride = 32;
  38. constexpr std::size_t kPlugMemberIndexOffset = 0;
  39. /** Native arrays use 16-bit definition indices even though their serialized field is 32-bit. */
  40. constexpr std::uint64_t kMaximumPlugMemberCount = 65535;
  41. /** @param blob Source bytes. @param offset Field offset. @param value Receives the field. */
  42. template <typename Value>
  43. [[nodiscard]] bool
  44. read(std::span<const std::byte> blob, std::size_t offset, Value& value) noexcept {
  45. if (offset > blob.size() || blob.size() - offset < sizeof value) {
  46. return false;
  47. }
  48. std::memcpy(&value, blob.data() + offset, sizeof value);
  49. return true;
  50. }
  51. /**
  52. * Reads the socket entry list index a definition declares.
  53. * @param definition Whole item definition bytes.
  54. * @param row Receives list presence and index.
  55. */
  56. void read_socket_entry_list(std::span<const std::byte> definition, Row& row) noexcept {
  57. row.hasSocketEntryList = false;
  58. row.socketEntryListIndex = 0;
  59. std::int64_t relative = 0;
  60. if (!read(definition, kSocketEntryListBlockOffset, relative) || relative == 0) {
  61. return;
  62. }
  63. const std::int64_t block = static_cast<std::int64_t>(kSocketEntryListBlockOffset) + relative;
  64. if (block < 0) {
  65. return;
  66. }
  67. const auto blockOffset = static_cast<std::uint64_t>(block);
  68. if (blockOffset + kSocketEntryListBlockSize > definition.size()) {
  69. return;
  70. }
  71. std::uint16_t index = 0;
  72. if (read(definition, static_cast<std::size_t>(block), index)) {
  73. row.hasSocketEntryList = true;
  74. row.socketEntryListIndex = index;
  75. }
  76. }
  77. /**
  78. * Reads the optional equipment slot a definition declares.
  79. * @param definition Whole item definition bytes.
  80. * @param slot Receives the slot when the block is present and readable.
  81. * @param raw Receives the unvalidated declared value, or -1 when the block is absent.
  82. */
  83. void read_equipment_slot(std::span<const std::byte> definition,
  84. std::optional<std::int8_t>& slot,
  85. std::int32_t& raw) noexcept {
  86. slot.reset();
  87. raw = -1;
  88. std::int64_t relative = 0;
  89. if (!read(definition, kEquipmentBlockOffset, relative) || relative == 0) {
  90. return;
  91. }
  92. const std::int64_t block = static_cast<std::int64_t>(kEquipmentBlockOffset) + relative;
  93. if (block < 0) {
  94. return;
  95. }
  96. // A value outside the equippable range marks an item that takes no slot.
  97. std::int32_t value = 0;
  98. if (read(definition, static_cast<std::size_t>(block) + kEquipmentSlotOffset, value)
  99. && value >= 0 && value < static_cast<std::int32_t>(kEquipmentSlotCount)) {
  100. slot = static_cast<std::int8_t>(value);
  101. }
  102. }
  103. /**
  104. * Reads the ordinary socket lanes a definition declares.
  105. * The lane's type index picks the overflow-hash priority the character record applies, so it is
  106. * read from the same entry as the plug rather than worked out later.
  107. * @param definition Whole item definition bytes.
  108. * @param row Receives socket presence, count, types and initial plugs.
  109. */
  110. void read_sockets(std::span<const std::byte> definition, Row& row) noexcept {
  111. row.hasSockets = false;
  112. row.socketCount = 0;
  113. std::fill(std::begin(row.initialPlugs), std::end(row.initialPlugs), kUnavailablePlug);
  114. std::fill(std::begin(row.socketTypes), std::end(row.socketTypes), kUnavailableSocketType);
  115. std::int64_t relative = 0;
  116. if (!read(definition, kSocketBlockOffset, relative) || relative == 0) {
  117. return;
  118. }
  119. const std::int64_t block = static_cast<std::int64_t>(kSocketBlockOffset) + relative;
  120. if (block < 0 || static_cast<std::uint64_t>(block) >= definition.size()) {
  121. return;
  122. }
  123. Array array{};
  124. if (!find_array_at(definition, static_cast<std::size_t>(block), array)
  125. || array.elementClass != kOrdinarySocketClass) {
  126. return;
  127. }
  128. row.hasSockets = true;
  129. const auto lanes =
  130. static_cast<std::size_t>((std::min)(array.count, std::uint64_t{kSocketCapacity}));
  131. for (std::size_t lane = 0; lane < lanes; ++lane) {
  132. const std::size_t entry = array.dataOffset + lane * kSocketEntryStride;
  133. std::uint16_t plug = kUnavailablePlug;
  134. std::uint16_t type = kUnavailableSocketType;
  135. if (!read(definition, entry + kSocketPlugOffset, plug)
  136. || !read(definition, entry + kSocketTypeOffset, type)) {
  137. return;
  138. }
  139. row.initialPlugs[lane] = plug;
  140. row.socketTypes[lane] = type;
  141. row.socketCount = static_cast<std::uint8_t>(lane + 1);
  142. }
  143. }
  144. /** Walks one checked array of 32-byte plug rows. */
  145. [[nodiscard]] bool visit_plug_array(std::span<const std::byte> blob,
  146. const Array& array,
  147. AllowedPlugVisitor visitor,
  148. void* context) noexcept {
  149. if (array.count > kMaximumPlugMemberCount || array.dataOffset > blob.size()
  150. || array.count > (blob.size() - array.dataOffset) / kPlugMemberStride) {
  151. return false;
  152. }
  153. for (std::uint64_t index = 0; index < array.count; ++index) {
  154. std::uint32_t itemDefinitionIndex = 0;
  155. const std::size_t row =
  156. array.dataOffset + static_cast<std::size_t>(index) * kPlugMemberStride;
  157. if (!read(blob, row + kPlugMemberIndexOffset, itemDefinitionIndex)
  158. || !visitor(context, itemDefinitionIndex)) {
  159. return false;
  160. }
  161. }
  162. return true;
  163. }
  164. /** Walks one reusable or randomized shared plug-set row, when the socket declares it. */
  165. [[nodiscard]] bool visit_shared_plug_set(std::span<const std::byte> definition,
  166. std::size_t socketEntry,
  167. std::size_t setIndexOffset,
  168. std::span<const std::byte> plugSetTable,
  169. const Array& sets,
  170. AllowedPlugVisitor visitor,
  171. void* context) noexcept {
  172. std::uint16_t setIndex = kUnavailablePlug;
  173. if (!read(definition, socketEntry + setIndexOffset, setIndex)) {
  174. return false;
  175. }
  176. if (setIndex == kUnavailablePlug) {
  177. return true;
  178. }
  179. if (setIndex >= sets.count || sets.dataOffset > plugSetTable.size()
  180. || sets.count > (plugSetTable.size() - sets.dataOffset) / kPlugSetRowStride) {
  181. return false;
  182. }
  183. const std::size_t descriptor = sets.dataOffset
  184. + static_cast<std::size_t>(setIndex) * kPlugSetRowStride
  185. + kPlugSetMemberDescriptorOffset;
  186. std::uint64_t memberCount = 0;
  187. if (!read(plugSetTable, descriptor, memberCount)) {
  188. return false;
  189. }
  190. if (memberCount == 0) {
  191. return true;
  192. }
  193. Array members{};
  194. return find_array_at(plugSetTable, descriptor, members)
  195. && visit_plug_array(plugSetTable, members, visitor, context);
  196. }
  197. /** The block header carries its own self-relative pointer to the entries at byte 8. */
  198. constexpr std::size_t kStatDataMember = 8;
  199. /** One stat entry is 40 blob bytes. */
  200. constexpr std::size_t kStatEntryStride = 40;
  201. /** A stat entry names its stat table row at byte 16. */
  202. constexpr std::size_t kStatEntryRow = 16;
  203. /** A stat entry carries its signed contribution at byte 20. */
  204. constexpr std::size_t kStatEntryValue = 20;
  205. /**
  206. * Reads the per-item stat contributions declared inside the definition blob.
  207. * Two self-relative hops: the definition points at a header, the header points at the entries.
  208. * Both are blob offsets and are valid only against the serialized definition.
  209. * @param definition Whole item definition bytes.
  210. * @param row Receives the declared stat rows and values.
  211. */
  212. void read_stats(std::span<const std::byte> definition, Row& row) noexcept {
  213. row.statCount = 0;
  214. std::int64_t blockRelative = 0;
  215. if (!read(definition, kStatBlockOffset, blockRelative)) {
  216. return;
  217. }
  218. const auto header = static_cast<std::int64_t>(kStatBlockOffset) + blockRelative;
  219. if (header < 0 || static_cast<std::size_t>(header) + kStatDataMember > definition.size()) {
  220. return;
  221. }
  222. std::uint64_t count = 0;
  223. std::int64_t dataRelative = 0;
  224. if (!read(definition, static_cast<std::size_t>(header), count) || count == 0
  225. || count > kStatCapacity
  226. || !read(definition, static_cast<std::size_t>(header) + kStatDataMember, dataRelative)) {
  227. return;
  228. }
  229. const std::int64_t data = header + static_cast<std::int64_t>(kStatDataMember) + dataRelative;
  230. if (data < 0 || static_cast<std::size_t>(data) + kStatEntryStride * count > definition.size()) {
  231. return;
  232. }
  233. for (std::uint64_t entry = 0; entry < count; ++entry) {
  234. const auto base = static_cast<std::size_t>(data) + kStatEntryStride * entry;
  235. if (!read(definition, base + kStatEntryRow, row.statRows[entry])
  236. || !read(definition, base + kStatEntryValue, row.statValues[entry])) {
  237. row.statCount = 0;
  238. return;
  239. }
  240. }
  241. row.statCount = static_cast<std::uint8_t>(count);
  242. }
  243. } // namespace
  244. /** Reads the fixed fields of one item definition blob. */
  245. bool read_definition(std::span<const std::byte> definition, Row& row) noexcept {
  246. const std::uint32_t hash = row.definitionHash;
  247. const std::uint16_t index = row.definitionIndex;
  248. row = {};
  249. row.definitionHash = hash;
  250. row.definitionIndex = index;
  251. row.insertionMaterialRequirementSetIndex = kUnavailableMaterialRequirementSetIndex;
  252. row.enabledMaterialRequirementSetIndex = kUnavailableMaterialRequirementSetIndex;
  253. std::fill(std::begin(row.initialPlugs), std::end(row.initialPlugs), kUnavailablePlug);
  254. std::fill(std::begin(row.socketTypes), std::end(row.socketTypes), kUnavailableSocketType);
  255. if (definition.size() < kFixedFieldEnd) {
  256. return false;
  257. }
  258. std::uint8_t instanced = 0;
  259. if (!read(definition, kBucketIdOffset, row.bucketId)
  260. || !read(definition, kMaxStackSizeOffset, row.maxStackSize)
  261. || !read(definition, kInstancedOffset, instanced)) {
  262. return false;
  263. }
  264. row.instanced = instanced != 0;
  265. // Short legacy definitions simply do not declare a plug category.
  266. (void)read(definition, kPlugCategoryOffset, row.plugCategoryHash);
  267. (void)read(definition,
  268. kInsertionMaterialRequirementSetIndexOffset,
  269. row.insertionMaterialRequirementSetIndex);
  270. (void)read(definition,
  271. kEnabledMaterialRequirementSetIndexOffset,
  272. row.enabledMaterialRequirementSetIndex);
  273. read_stats(definition, row);
  274. read_appearance(definition, row);
  275. read_socket_entry_list(definition, row);
  276. read_equipment_slot(definition, row.equipmentSlot, row.rawEquipmentSlot);
  277. read_sockets(definition, row);
  278. return true;
  279. }
  280. /** Visits every list-backed allowed plug for one exact ordinary socket lane. */
  281. bool visit_allowed_plugs(std::span<const std::byte> definition,
  282. std::span<const std::byte> plugSetTable,
  283. std::uint8_t lane,
  284. AllowedPlugVisitor visitor,
  285. void* context) noexcept {
  286. if (visitor == nullptr || lane >= kSocketCapacity) {
  287. return false;
  288. }
  289. std::int64_t socketBlockRelative = 0;
  290. if (!read(definition, kSocketBlockOffset, socketBlockRelative) || socketBlockRelative == 0) {
  291. return false;
  292. }
  293. const std::int64_t socketBlock =
  294. static_cast<std::int64_t>(kSocketBlockOffset) + socketBlockRelative;
  295. if (socketBlock < 0 || static_cast<std::uint64_t>(socketBlock) >= definition.size()) {
  296. return false;
  297. }
  298. Array sockets{};
  299. if (!find_array_at(definition, static_cast<std::size_t>(socketBlock), sockets)
  300. || sockets.elementClass != kOrdinarySocketClass || lane >= sockets.count
  301. || sockets.dataOffset > definition.size()
  302. || sockets.count > (definition.size() - sockets.dataOffset) / kSocketEntryStride) {
  303. return false;
  304. }
  305. Array sets{};
  306. if (!find_array_at(plugSetTable, kTableArrayDescriptor, sets)) {
  307. return false;
  308. }
  309. const std::size_t socketEntry =
  310. sockets.dataOffset + static_cast<std::size_t>(lane) * kSocketEntryStride;
  311. Array embedded{};
  312. std::uint64_t embeddedCount = 0;
  313. if (!read(definition, socketEntry + kEmbeddedPlugListOffset, embeddedCount)
  314. || (embeddedCount != 0
  315. && (!find_array_at(definition, socketEntry + kEmbeddedPlugListOffset, embedded)
  316. || !visit_plug_array(definition, embedded, visitor, context)))) {
  317. return false;
  318. }
  319. return visit_shared_plug_set(definition,
  320. socketEntry,
  321. kReusablePlugSetIndexOffset,
  322. plugSetTable,
  323. sets,
  324. visitor,
  325. context)
  326. && visit_shared_plug_set(definition,
  327. socketEntry,
  328. kRandomizedPlugSetIndexOffset,
  329. plugSetTable,
  330. sets,
  331. visitor,
  332. context);
  333. }
  334. } // namespace sunrise::middleware::content::packages::tables::items