configured_equipment_identity.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. #include "configured_equipment_identity.h"
  2. #include <limits>
  3. #include <optional>
  4. namespace sunrise::state::runtime::equipment {
  5. namespace {
  6. /** FNV-1a's 64-bit offset basis gives the equipment fingerprint a stable nonzero start. */
  7. constexpr std::uint64_t kEquipmentHashOffsetBasis = 14695981039346656037ULL;
  8. /** FNV-1a's 64-bit prime mixes each ordered equipment byte without keeping source data. */
  9. constexpr std::uint64_t kEquipmentHashPrime = 1099511628211ULL;
  10. /** Marker 0 marks an empty semantic equipment slot. */
  11. constexpr std::uint8_t kAbsentItemMarker = 0;
  12. /** Marker 1 marks a present item, even when its authored definition hash is 0. */
  13. constexpr std::uint8_t kPresentItemMarker = 1;
  14. /**
  15. * Mixes one ordered byte into the settings-sensitive equipment fingerprint.
  16. * @param hash Mutable 64-bit FNV-1a accumulator.
  17. * @param value Next canonical byte.
  18. */
  19. void mix_byte(std::uint64_t& hash, std::uint8_t value) noexcept {
  20. hash ^= value;
  21. hash *= kEquipmentHashPrime;
  22. }
  23. /**
  24. * Mixes one 16-bit policy value in explicit least-significant-byte order.
  25. * @param hash Mutable 64-bit FNV-1a accumulator.
  26. * @param value Configured score selector.
  27. */
  28. void mix_value(std::uint64_t& hash, std::uint16_t value) noexcept {
  29. for (std::size_t byteIndex = 0; byteIndex < sizeof value; ++byteIndex) {
  30. const std::size_t shift = byteIndex * (std::numeric_limits<std::uint8_t>::digits);
  31. mix_byte(hash, static_cast<std::uint8_t>(value >> shift));
  32. }
  33. }
  34. /**
  35. * Mixes one 32-bit authored or policy value in explicit least-significant-byte order.
  36. * @param hash Mutable 64-bit FNV-1a accumulator.
  37. * @param value Authored base hash, authored level, or score-policy revision.
  38. */
  39. void mix_value(std::uint64_t& hash, std::uint32_t value) noexcept {
  40. for (std::size_t byteIndex = 0; byteIndex < sizeof value; ++byteIndex) {
  41. const std::size_t shift = byteIndex * (std::numeric_limits<std::uint8_t>::digits);
  42. mix_byte(hash, static_cast<std::uint8_t>(value >> shift));
  43. }
  44. }
  45. /**
  46. * Mixes one item's socket policy and every authored plug lane.
  47. * The extraction pass reads a detail row for each authored plug, so a changed plug must rebuild.
  48. * @param hash Mutable 64-bit FNV-1a accumulator.
  49. * @param sockets Authored socket policy and lanes.
  50. */
  51. void mix_sockets(std::uint64_t& hash, const account::inventory::Sockets& sockets) noexcept {
  52. mix_byte(hash, static_cast<std::uint8_t>(sockets.policy));
  53. mix_byte(hash, static_cast<std::uint8_t>(sockets.plugCount));
  54. for (std::size_t lane = 0; lane < sockets.plugCount && lane < sockets.plugs.size(); ++lane) {
  55. if (!sockets.plugs[lane].has_value()) {
  56. mix_byte(hash, kAbsentItemMarker);
  57. continue;
  58. }
  59. mix_byte(hash, kPresentItemMarker);
  60. mix_value(hash, *sockets.plugs[lane]);
  61. }
  62. }
  63. /** Mixes the installed-detail inputs shared by equipped and unequipped authored items. */
  64. void mix_item(std::uint64_t& hash, const account::inventory::Item& item) noexcept {
  65. // SOIDs, quantity, gates and secrets stay outside build identity.
  66. mix_value(hash, item.definitionHash);
  67. mix_value(hash, static_cast<std::uint32_t>(item.level));
  68. mix_sockets(hash, item.sockets);
  69. }
  70. /**
  71. * Mixes one character's 5 selected subclass entries.
  72. * The ability bucket rows are keyed by these, so a changed pick must rebuild.
  73. * @param hash Mutable 64-bit FNV-1a accumulator.
  74. * @param character Authored character.
  75. */
  76. void mix_ability_selection(std::uint64_t& hash, const CharacterState& character) noexcept {
  77. mix_byte(hash, character.movementAbilityEntry);
  78. mix_byte(hash, character.grenadeAbilityEntry);
  79. mix_byte(hash, character.superAbilityEntry);
  80. mix_byte(hash, character.meleeAbilityEntry);
  81. mix_byte(hash, character.classAbilityEntry);
  82. }
  83. } // namespace
  84. /** Builds a nonsecret cache identity from ordered authored equipment. */
  85. std::uint64_t configured_hash(const AccountState& accountState) noexcept {
  86. std::uint64_t hash = kEquipmentHashOffsetBasis;
  87. mix_byte(hash, static_cast<std::uint8_t>(accountState.characterCount));
  88. for (std::size_t characterIndex = 0; characterIndex < accountState.characterCount;
  89. ++characterIndex) {
  90. const CharacterState& character = accountState.characters[characterIndex];
  91. mix_ability_selection(hash, character);
  92. for (const std::optional<account::inventory::Item>& item : character.equipment.slots) {
  93. if (!item.has_value()) {
  94. mix_byte(hash, kAbsentItemMarker);
  95. continue;
  96. }
  97. mix_byte(hash, kPresentItemMarker);
  98. mix_item(hash, *item);
  99. }
  100. static_assert(account::inventory::kCharacterItemCapacity
  101. <= (std::numeric_limits<std::uint8_t>::max)());
  102. mix_byte(hash, static_cast<std::uint8_t>(character.inventory.count));
  103. for (std::size_t itemIndex = 0; itemIndex < character.inventory.count; ++itemIndex) {
  104. mix_item(hash, character.inventory.values[itemIndex]);
  105. }
  106. }
  107. return hash;
  108. }
  109. } // namespace sunrise::state::runtime::equipment