web_service_actions.cpp 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  1. #include "web_service_actions.h"
  2. #include <array>
  3. #include <cstdio>
  4. #include <limits>
  5. #include <string_view>
  6. #include "../../core/logging/log.h"
  7. #include "../../middleware/encoding/bit_reader.h"
  8. #include "../../middleware/encoding/byte_order.h"
  9. #include "../../middleware/web_service/messages/opcode1901.h"
  10. #include "../../middleware/web_service/messages/opcode504.h"
  11. #include "../../middleware/web_service/messages/opcode903.h"
  12. #include "../../state/account/account_state.h"
  13. #include "../../state/build_data/runtime.h"
  14. #include "../../state/runtime/runtime.h"
  15. namespace sunrise::server::web_service {
  16. namespace {
  17. constexpr std::size_t kEquipmentActionPayloadSize = middleware::encoding::kU64Size + 1U;
  18. constexpr std::size_t kItemStatePayloadSize = 15;
  19. constexpr std::uint8_t kItemStateDefinitionIndexWidth = 15;
  20. constexpr std::uint8_t kItemStateValueWidth = 32;
  21. constexpr std::uint8_t kItemStatePaddingWidth = 7;
  22. constexpr std::uint64_t kItemStateValueBias = 0x80000000ULL;
  23. constexpr std::size_t kItemDismantlePayloadSize = 16;
  24. constexpr std::uint8_t kItemDismantleInstanceWidth = 64;
  25. constexpr std::uint8_t kItemDismantleDefinitionIndexWidth = 15;
  26. constexpr std::uint8_t kItemDismantleQuantityWidth = 32;
  27. constexpr std::uint32_t kItemDismantleSingleQuantityWire = 0x80000001U;
  28. constexpr std::uint8_t kItemDismantleRequiredFlagWidth = 1;
  29. constexpr std::uint8_t kItemDismantleNestedPaddingWidth = 6;
  30. constexpr std::uint8_t kItemDismantleOuterTrailerWidth = 2;
  31. constexpr std::uint8_t kItemDismantleFinalPaddingWidth = 6;
  32. constexpr std::uint64_t kEquipmentSelectorStride = 4;
  33. constexpr std::uint8_t kEquippedShaderModelSocketKind = 0;
  34. constexpr std::size_t kItemAcquisitionPayloadSize = 3;
  35. constexpr std::uint8_t kItemAcquisitionPresenceWidth = 1;
  36. constexpr std::uint8_t kItemAcquisitionCollectibleIndexWidth = 15;
  37. constexpr std::uint8_t kItemAcquisitionPaddingWidth = 8;
  38. constexpr std::uint32_t kUnavailableDefinitionIndex = (std::numeric_limits<std::uint16_t>::max)();
  39. } // namespace
  40. /** Logs one exact correlated equipment response after its Queuez update is staged. */
  41. void report_equip_response(const middleware::web_service::Message& message,
  42. std::int32_t family4Version,
  43. std::span<const std::byte> response) noexcept {
  44. std::array<char, core::log::kLineCapacity> line{};
  45. const int prefix = std::snprintf(line.data(),
  46. line.size(),
  47. "ev=equipment stage=response opcode=%u transaction=%u "
  48. "family_version=%d bytes=%zu hex=",
  49. static_cast<unsigned>(message.opcode),
  50. static_cast<unsigned>(message.transactionId),
  51. family4Version,
  52. response.size());
  53. if (prefix <= 0 || static_cast<std::size_t>(prefix) >= line.size()) {
  54. return;
  55. }
  56. constexpr char kHex[] = "0123456789ABCDEF";
  57. std::size_t length = static_cast<std::size_t>(prefix);
  58. for (const std::byte byte : response) {
  59. if (length + 2 >= line.size()) {
  60. break;
  61. }
  62. const unsigned value = std::to_integer<unsigned>(byte);
  63. line[length++] = kHex[(value >> 4U) & 0xFU];
  64. line[length++] = kHex[value & 0xFU];
  65. }
  66. core::log::write(core::log::Channel::server, core::log::Level::debug, {line.data(), length});
  67. }
  68. /** Logs the final item-creation status pair and the exact Family-4 revision it promises. */
  69. void report_item_acquisition_response(const middleware::web_service::Message& message,
  70. std::int32_t family4Version,
  71. std::uint64_t acquiredInstanceSoid,
  72. std::span<const std::byte> response) noexcept {
  73. std::array<char, core::log::kLineCapacity> line{};
  74. const int prefix = std::snprintf(
  75. line.data(),
  76. line.size(),
  77. "ev=acquire stage=response result=ok opcode=%u transaction=%u family_version=%d "
  78. "instance=0x%llX bytes=%zu hex=",
  79. static_cast<unsigned>(message.opcode),
  80. static_cast<unsigned>(message.transactionId),
  81. family4Version,
  82. static_cast<unsigned long long>(acquiredInstanceSoid),
  83. response.size());
  84. if (prefix <= 0 || static_cast<std::size_t>(prefix) >= line.size()) {
  85. return;
  86. }
  87. constexpr char kHex[] = "0123456789ABCDEF";
  88. std::size_t length = static_cast<std::size_t>(prefix);
  89. for (const std::byte byte : response) {
  90. if (length + 2 >= line.size()) {
  91. break;
  92. }
  93. const unsigned value = std::to_integer<unsigned>(byte);
  94. line[length++] = kHex[(value >> 4U) & 0xFU];
  95. line[length++] = kHex[value & 0xFU];
  96. }
  97. core::log::write(core::log::Channel::server, core::log::Level::debug, {line.data(), length});
  98. }
  99. /** Logs the final profile-stack status pair and the exact Family-4 account revision it promises. */
  100. void report_profile_item_acquisition_response(const middleware::web_service::Message& message,
  101. std::int32_t family4Version,
  102. std::uint32_t definitionHash,
  103. std::int32_t quantity,
  104. std::span<const std::byte> response) noexcept {
  105. std::array<char, core::log::kLineCapacity> line{};
  106. const int prefix =
  107. std::snprintf(line.data(),
  108. line.size(),
  109. "ev=profile_acquire stage=response result=ok opcode=%u transaction=%u "
  110. "family_version=%d definition_hash=0x%08X quantity=%d bytes=%zu hex=",
  111. static_cast<unsigned>(message.opcode),
  112. static_cast<unsigned>(message.transactionId),
  113. family4Version,
  114. definitionHash,
  115. quantity,
  116. response.size());
  117. if (prefix <= 0 || static_cast<std::size_t>(prefix) >= line.size()) {
  118. return;
  119. }
  120. constexpr char kHex[] = "0123456789ABCDEF";
  121. std::size_t length = static_cast<std::size_t>(prefix);
  122. for (const std::byte byte : response) {
  123. if (length + 2 >= line.size()) {
  124. break;
  125. }
  126. const unsigned value = std::to_integer<unsigned>(byte);
  127. line[length++] = kHex[(value >> 4U) & 0xFU];
  128. line[length++] = kHex[value & 0xFU];
  129. }
  130. core::log::write(core::log::Channel::server, core::log::Level::debug, {line.data(), length});
  131. }
  132. /** Logs the final dismantle status pair and the exact Family-4 revision it promises. */
  133. void report_item_dismantle_response(const middleware::web_service::Message& message,
  134. std::int32_t family4Version,
  135. std::uint64_t dismantledInstanceSoid,
  136. std::span<const std::byte> response) noexcept {
  137. std::array<char, core::log::kLineCapacity> line{};
  138. const int prefix = std::snprintf(
  139. line.data(),
  140. line.size(),
  141. "ev=dismantle stage=response result=ok opcode=%u transaction=%u family_version=%d "
  142. "instance=0x%llX bytes=%zu hex=",
  143. static_cast<unsigned>(message.opcode),
  144. static_cast<unsigned>(message.transactionId),
  145. family4Version,
  146. static_cast<unsigned long long>(dismantledInstanceSoid),
  147. response.size());
  148. if (prefix <= 0 || static_cast<std::size_t>(prefix) >= line.size()) {
  149. return;
  150. }
  151. constexpr char kHex[] = "0123456789ABCDEF";
  152. std::size_t length = static_cast<std::size_t>(prefix);
  153. for (const std::byte byte : response) {
  154. if (length + 2 >= line.size()) {
  155. break;
  156. }
  157. const unsigned value = std::to_integer<unsigned>(byte);
  158. line[length++] = kHex[(value >> 4U) & 0xFU];
  159. line[length++] = kHex[value & 0xFU];
  160. }
  161. core::log::write(core::log::Channel::server, core::log::Level::debug, {line.data(), length});
  162. }
  163. /** Logs the exact opcode-903 status pair and the item-instance revision it promises. */
  164. void report_socket_plug_response(const middleware::web_service::Message& message,
  165. std::int32_t family4Version,
  166. std::uint64_t targetInstanceSoid,
  167. std::uint8_t socketLane,
  168. std::uint16_t plugDefinitionIndex,
  169. std::span<const std::byte> response) noexcept {
  170. std::array<char, core::log::kLineCapacity> line{};
  171. const int prefix = std::snprintf(
  172. line.data(),
  173. line.size(),
  174. "ev=socket_plug stage=response result=ok opcode=%u transaction=%u family_version=%d "
  175. "instance=0x%llX lane=%u plug_definition=%u bytes=%zu hex=",
  176. static_cast<unsigned>(message.opcode),
  177. static_cast<unsigned>(message.transactionId),
  178. family4Version,
  179. static_cast<unsigned long long>(targetInstanceSoid),
  180. static_cast<unsigned>(socketLane),
  181. static_cast<unsigned>(plugDefinitionIndex),
  182. response.size());
  183. if (prefix <= 0 || static_cast<std::size_t>(prefix) >= line.size()) {
  184. return;
  185. }
  186. constexpr char kHex[] = "0123456789ABCDEF";
  187. std::size_t length = static_cast<std::size_t>(prefix);
  188. for (const std::byte byte : response) {
  189. if (length + 2 >= line.size()) {
  190. break;
  191. }
  192. const unsigned value = std::to_integer<unsigned>(byte);
  193. line[length++] = kHex[(value >> 4U) & 0xFU];
  194. line[length++] = kHex[value & 0xFU];
  195. }
  196. core::log::write(core::log::Channel::server, core::log::Level::debug, {line.data(), length});
  197. }
  198. /** One line carries the picked id and whether the selection moved. */
  199. constexpr std::size_t kSelectLineCapacity = 96;
  200. /**
  201. * Records the player's character pick, which arrives nowhere else.
  202. * A bad or unknown id leaves the selection alone. The reply is the status pair either way. The
  203. * Family-4 object move follows this call, and the family-zero pair after it.
  204. * @param message Parsed select-character request.
  205. * @param outcome Gets the picked key once the selection has moved in State.
  206. */
  207. void select_character(const middleware::web_service::Message& message, Outcome& outcome) noexcept {
  208. middleware::web_service::messages::opcode504::Request picked;
  209. if (!middleware::web_service::messages::opcode504::parse_request(message, picked)) {
  210. core::log::write(
  211. core::log::Channel::server, core::log::Level::warn, "ev=ws504 stage=parse result=fail");
  212. return;
  213. }
  214. bool changed = false;
  215. if (!state::set_selected_character(picked.characterSoid, changed)) {
  216. core::log::write(core::log::Channel::server,
  217. core::log::Level::warn,
  218. "ev=ws504 stage=select result=unknown");
  219. return;
  220. }
  221. outcome.hasSelectedCharacter = true;
  222. outcome.selectedCharacterSoid = picked.characterSoid;
  223. std::array<char, kSelectLineCapacity> line{};
  224. const int written = std::snprintf(line.data(),
  225. line.size(),
  226. "ev=ws504 stage=select result=ok soid=0x%llX changed=%u",
  227. static_cast<unsigned long long>(picked.characterSoid),
  228. static_cast<unsigned>(changed));
  229. if (written > 0) {
  230. core::log::write(core::log::Channel::server,
  231. core::log::Level::info,
  232. {line.data(), static_cast<std::size_t>(written)});
  233. }
  234. }
  235. /** Parses the exact shared opcode-403/404 SOID descriptor. */
  236. [[nodiscard]] bool parse_equipment_instance(const middleware::web_service::Message& message,
  237. std::uint64_t& instanceSoid) noexcept {
  238. instanceSoid = 0;
  239. if (message.payload.size() != kEquipmentActionPayloadSize
  240. || message.payload[middleware::encoding::kU64Size] != std::byte{}) {
  241. return false;
  242. }
  243. instanceSoid = middleware::encoding::read_u64_be(
  244. std::span<const std::byte, middleware::encoding::kU64Size>{message.payload.data(),
  245. middleware::encoding::kU64Size});
  246. return instanceSoid != 0;
  247. }
  248. /** Prepares one opcode-403/404 equipment mutation without publishing State early. */
  249. void mutate_equipment(const middleware::web_service::Message& message,
  250. bool unequip,
  251. Outcome& outcome) noexcept {
  252. std::uint64_t requestedInstanceSoid = 0;
  253. if (!parse_equipment_instance(message, requestedInstanceSoid)) {
  254. std::array<char, 112> line{};
  255. const int count = std::snprintf(line.data(),
  256. line.size(),
  257. "ev=equipment stage=parse result=fail opcode=%u "
  258. "payload_bytes=%zu",
  259. static_cast<unsigned>(message.opcode),
  260. message.payload.size());
  261. if (count > 0) {
  262. core::log::write(core::log::Channel::server,
  263. core::log::Level::warn,
  264. {line.data(), static_cast<std::size_t>(count)});
  265. }
  266. return;
  267. }
  268. state::PendingEquipmentSwap mutation;
  269. const bool prepared = unequip
  270. ? state::prepare_equipment_unequip(requestedInstanceSoid, mutation)
  271. : state::prepare_equipment_swap(requestedInstanceSoid, mutation);
  272. if (!prepared) {
  273. std::array<char, 144> line{};
  274. const int count = std::snprintf(
  275. line.data(),
  276. line.size(),
  277. "ev=equipment stage=prepare result=fail opcode=%u action=%s requested=0x%llX",
  278. static_cast<unsigned>(message.opcode),
  279. unequip ? "unequip" : "equip",
  280. static_cast<unsigned long long>(requestedInstanceSoid));
  281. if (count > 0) {
  282. core::log::write(core::log::Channel::server,
  283. core::log::Level::warn,
  284. {line.data(), static_cast<std::size_t>(count)});
  285. }
  286. return;
  287. }
  288. outcome.mutation = mutation;
  289. std::array<char, 224> line{};
  290. const int count =
  291. std::snprintf(line.data(),
  292. line.size(),
  293. "ev=equipment stage=prepare result=ok opcode=%u action=%s character=0x%llX "
  294. "previous=0x%llX requested=0x%llX native_slot=%u moved_items=%zu",
  295. static_cast<unsigned>(message.opcode),
  296. unequip ? "unequip" : "equip",
  297. static_cast<unsigned long long>(mutation.characterSoid),
  298. static_cast<unsigned long long>(mutation.previousInstanceSoid),
  299. static_cast<unsigned long long>(mutation.requestedInstanceSoid),
  300. static_cast<unsigned>(mutation.nativeEquipmentSlot),
  301. mutation.movedItemCount);
  302. if (count > 0) {
  303. core::log::write(core::log::Channel::server,
  304. core::log::Level::info,
  305. {line.data(), static_cast<std::size_t>(count)});
  306. }
  307. }
  308. /** Parses and prepares one exact selected-character opcode-903 socket selection. */
  309. void mutate_socket_plug(const middleware::web_service::Message& message,
  310. Outcome& outcome) noexcept {
  311. middleware::web_service::messages::opcode903::Request request{};
  312. if (!middleware::web_service::messages::opcode903::parse_request(message, request)
  313. || !request.hasInstance || request.instanceSoid == 0 || request.hasTargetDefinition
  314. || !request.hasPlugDefinition
  315. || request.socketIndex >= state::account::inventory::kPlugCapacity) {
  316. std::array<char, 192> line{};
  317. const int count = std::snprintf(
  318. line.data(),
  319. line.size(),
  320. "ev=ws903 stage=parse result=fail transaction=%u payload_bytes=%zu has_instance=%u "
  321. "instance=0x%llX has_target_definition=%u socket=%u has_plug_definition=%u",
  322. static_cast<unsigned>(message.transactionId),
  323. message.payload.size(),
  324. static_cast<unsigned>(request.hasInstance),
  325. static_cast<unsigned long long>(request.instanceSoid),
  326. static_cast<unsigned>(request.hasTargetDefinition),
  327. request.socketIndex,
  328. static_cast<unsigned>(request.hasPlugDefinition));
  329. if (count > 0) {
  330. core::log::write(core::log::Channel::server,
  331. core::log::Level::warn,
  332. {line.data(), static_cast<std::size_t>(count)});
  333. }
  334. return;
  335. }
  336. state::PendingSocketPlug mutation{};
  337. if (!state::prepare_socket_plug(request.instanceSoid,
  338. static_cast<std::uint8_t>(request.socketIndex),
  339. request.plugDefinitionIndex,
  340. mutation)) {
  341. std::array<char, 192> line{};
  342. const int count = std::snprintf(
  343. line.data(),
  344. line.size(),
  345. "ev=ws903 stage=prepare result=fail transaction=%u instance=0x%llX lane=%u "
  346. "plug_definition=%u",
  347. static_cast<unsigned>(message.transactionId),
  348. static_cast<unsigned long long>(request.instanceSoid),
  349. request.socketIndex,
  350. static_cast<unsigned>(request.plugDefinitionIndex));
  351. if (count > 0) {
  352. core::log::write(core::log::Channel::server,
  353. core::log::Level::warn,
  354. {line.data(), static_cast<std::size_t>(count)});
  355. }
  356. return;
  357. }
  358. outcome.mutation = mutation;
  359. std::array<char, 240> line{};
  360. const int count = std::snprintf(
  361. line.data(),
  362. line.size(),
  363. "ev=ws903 stage=prepare result=ok transaction=%u character=0x%llX instance=0x%llX "
  364. "target_definition=%u target_bucket=%u lane=%u plug_definition=%u plug_bucket=%u "
  365. "equipped=%u item_index=%zu",
  366. static_cast<unsigned>(message.transactionId),
  367. static_cast<unsigned long long>(mutation.characterSoid),
  368. static_cast<unsigned long long>(mutation.targetInstanceSoid),
  369. static_cast<unsigned>(mutation.targetDefinitionIndex),
  370. static_cast<unsigned>(mutation.targetBucketId),
  371. static_cast<unsigned>(mutation.socketLane),
  372. static_cast<unsigned>(mutation.plugDefinitionIndex),
  373. static_cast<unsigned>(mutation.plugBucketId),
  374. static_cast<unsigned>(mutation.targetEquipped),
  375. mutation.itemIndex);
  376. if (count > 0) {
  377. core::log::write(core::log::Channel::server,
  378. core::log::Level::info,
  379. {line.data(), static_cast<std::size_t>(count)});
  380. }
  381. }
  382. /** Parses and prepares one character-location opcode-1901 socket selection. */
  383. void mutate_equipped_socket_plug(const middleware::web_service::Message& message,
  384. Outcome& outcome) noexcept {
  385. middleware::web_service::messages::opcode1901::Request request{};
  386. if (!middleware::web_service::messages::opcode1901::parse_request(message, request)
  387. || request.canonicalSocketKind != request.socketIndex
  388. || request.modelSocketKind != kEquippedShaderModelSocketKind || request.auxiliary != 0
  389. || request.socketIndex >= state::account::inventory::kPlugCapacity
  390. || request.equipmentSelector == 0
  391. || request.equipmentSelector % kEquipmentSelectorStride != 0) {
  392. std::array<char, 256> line{};
  393. const int count = std::snprintf(
  394. line.data(),
  395. line.size(),
  396. "ev=ws1901 stage=parse result=fail transaction=%u payload_bytes=%zu "
  397. "plug_definition=%u canonical_kind=%u model_kind=%u socket=%u auxiliary=0x%llX "
  398. "equipment_selector=%llu",
  399. static_cast<unsigned>(message.transactionId),
  400. message.payload.size(),
  401. static_cast<unsigned>(request.plugDefinitionIndex),
  402. static_cast<unsigned>(request.canonicalSocketKind),
  403. static_cast<unsigned>(request.modelSocketKind),
  404. request.socketIndex,
  405. static_cast<unsigned long long>(request.auxiliary),
  406. static_cast<unsigned long long>(request.equipmentSelector));
  407. if (count > 0) {
  408. core::log::write(core::log::Channel::server,
  409. core::log::Level::warn,
  410. {line.data(), static_cast<std::size_t>(count)});
  411. }
  412. return;
  413. }
  414. const std::uint64_t identityToken = request.equipmentSelector / kEquipmentSelectorStride;
  415. state::PendingSocketPlug mutation{};
  416. if (!state::prepare_character_selector_socket_plug(
  417. request.equipmentSelector,
  418. static_cast<std::uint8_t>(request.socketIndex),
  419. request.plugDefinitionIndex,
  420. mutation)) {
  421. std::array<char, 224> line{};
  422. const int count = std::snprintf(
  423. line.data(),
  424. line.size(),
  425. "ev=ws1901 stage=prepare result=fail transaction=%u equipment_selector=%llu "
  426. "identity_token=%llu lane=%u plug_definition=%u canonical_kind=%u model_kind=%u "
  427. "auxiliary=0x%llX",
  428. static_cast<unsigned>(message.transactionId),
  429. static_cast<unsigned long long>(request.equipmentSelector),
  430. static_cast<unsigned long long>(identityToken),
  431. request.socketIndex,
  432. static_cast<unsigned>(request.plugDefinitionIndex),
  433. static_cast<unsigned>(request.canonicalSocketKind),
  434. static_cast<unsigned>(request.modelSocketKind),
  435. static_cast<unsigned long long>(request.auxiliary));
  436. if (count > 0) {
  437. core::log::write(core::log::Channel::server,
  438. core::log::Level::warn,
  439. {line.data(), static_cast<std::size_t>(count)});
  440. }
  441. return;
  442. }
  443. outcome.mutation = mutation;
  444. std::array<char, 288> line{};
  445. const int count = std::snprintf(
  446. line.data(),
  447. line.size(),
  448. "ev=ws1901 stage=prepare result=ok transaction=%u character=0x%llX instance=0x%llX "
  449. "equipment_selector=%llu identity_token=%llu target_definition=%u target_bucket=%u "
  450. "lane=%u plug_definition=%u plug_bucket=%u canonical_kind=%u model_kind=%u "
  451. "auxiliary=0x%llX",
  452. static_cast<unsigned>(message.transactionId),
  453. static_cast<unsigned long long>(mutation.characterSoid),
  454. static_cast<unsigned long long>(mutation.targetInstanceSoid),
  455. static_cast<unsigned long long>(request.equipmentSelector),
  456. static_cast<unsigned long long>(identityToken),
  457. static_cast<unsigned>(mutation.targetDefinitionIndex),
  458. static_cast<unsigned>(mutation.targetBucketId),
  459. static_cast<unsigned>(mutation.socketLane),
  460. static_cast<unsigned>(mutation.plugDefinitionIndex),
  461. static_cast<unsigned>(mutation.plugBucketId),
  462. static_cast<unsigned>(request.canonicalSocketKind),
  463. static_cast<unsigned>(request.modelSocketKind),
  464. static_cast<unsigned long long>(request.auxiliary));
  465. if (count > 0) {
  466. core::log::write(core::log::Channel::server,
  467. core::log::Level::info,
  468. {line.data(), static_cast<std::size_t>(count)});
  469. }
  470. }
  471. /** Parses and prepares one complete accumulated item-state value from opcode 406. */
  472. void mutate_item_state(const middleware::web_service::Message& message, Outcome& outcome) noexcept {
  473. middleware::encoding::bits::Reader reader(message.payload);
  474. std::uint64_t instancePresent = 0;
  475. std::uint64_t instanceSoid = 0;
  476. std::uint64_t definitionPresent = 0;
  477. std::uint64_t definitionIndex = 0;
  478. std::uint64_t encodedFlags = 0;
  479. std::uint64_t padding = 0;
  480. if (message.payload.size() != kItemStatePayloadSize || !reader.read(1, instancePresent)
  481. || !reader.read(64, instanceSoid) || !reader.read(1, definitionPresent)
  482. || !reader.read(kItemStateDefinitionIndexWidth, definitionIndex)
  483. || !reader.read(kItemStateValueWidth, encodedFlags)
  484. || !reader.read(kItemStatePaddingWidth, padding) || reader.remaining_bits() != 0
  485. || instancePresent == 0 || instanceSoid == 0 || definitionPresent == 0
  486. || definitionIndex > (std::numeric_limits<std::uint16_t>::max)()
  487. || encodedFlags < kItemStateValueBias || padding != 0
  488. || encodedFlags - kItemStateValueBias > 0x3U) {
  489. std::array<char, 224> line{};
  490. const int count = std::snprintf(
  491. line.data(),
  492. line.size(),
  493. "ev=ws406 stage=parse result=fail transaction=%u payload_bytes=%zu instance=0x%llX "
  494. "definition=%llu flags_wire=0x%llX padding=0x%llX",
  495. static_cast<unsigned>(message.transactionId),
  496. message.payload.size(),
  497. static_cast<unsigned long long>(instanceSoid),
  498. static_cast<unsigned long long>(definitionIndex),
  499. static_cast<unsigned long long>(encodedFlags),
  500. static_cast<unsigned long long>(padding));
  501. if (count > 0) {
  502. core::log::write(core::log::Channel::server,
  503. core::log::Level::warn,
  504. {line.data(), static_cast<std::size_t>(count)});
  505. }
  506. return;
  507. }
  508. const std::uint32_t flags = static_cast<std::uint32_t>(encodedFlags - kItemStateValueBias);
  509. state::PendingItemState mutation{};
  510. if (!state::prepare_item_state(
  511. instanceSoid, static_cast<std::uint16_t>(definitionIndex), flags, mutation)) {
  512. return;
  513. }
  514. outcome.mutation = mutation;
  515. std::array<char, 224> line{};
  516. const int count = std::snprintf(
  517. line.data(),
  518. line.size(),
  519. "ev=ws406 stage=prepare result=ok transaction=%u character=0x%llX instance=0x%llX "
  520. "definition=%u flags_before=0x%X flags_after=0x%X equipped=%u item_index=%zu",
  521. static_cast<unsigned>(message.transactionId),
  522. static_cast<unsigned long long>(mutation.characterSoid),
  523. static_cast<unsigned long long>(mutation.targetInstanceSoid),
  524. static_cast<unsigned>(mutation.targetDefinitionIndex),
  525. mutation.beforeFlags,
  526. mutation.afterFlags,
  527. mutation.targetEquipped ? 1U : 0U,
  528. mutation.itemIndex);
  529. if (count > 0) {
  530. core::log::write(core::log::Channel::server,
  531. core::log::Level::info,
  532. {line.data(), static_cast<std::size_t>(count)});
  533. }
  534. }
  535. /** Records strict opcode-402 parsing, identity checks, and State preparation outcomes. */
  536. void report_item_dismantle(const middleware::web_service::Message& message,
  537. std::string_view result,
  538. std::string_view reason,
  539. std::uint64_t instanceSoid,
  540. std::uint32_t definitionIndex,
  541. std::uint32_t definitionHash,
  542. std::uint32_t quantity) noexcept {
  543. std::array<char, core::log::kLineCapacity> line{};
  544. const int count = std::snprintf(
  545. line.data(),
  546. line.size(),
  547. "ev=ws402 stage=prepare result=%.*s reason=%.*s transaction=%u payload_bytes=%zu "
  548. "instance=0x%llX definition_index=%u definition_hash=0x%08X quantity=%u",
  549. static_cast<int>(result.size()),
  550. result.data(),
  551. static_cast<int>(reason.size()),
  552. reason.data(),
  553. static_cast<unsigned>(message.transactionId),
  554. message.payload.size(),
  555. static_cast<unsigned long long>(instanceSoid),
  556. definitionIndex,
  557. definitionHash,
  558. quantity);
  559. if (count > 0) {
  560. core::log::write(core::log::Channel::server,
  561. result == "ok" ? core::log::Level::info : core::log::Level::warn,
  562. {line.data(), static_cast<std::size_t>(count)});
  563. }
  564. }
  565. /** Prepares the exact fixed-width opcode-402 Character-inventory removal request. */
  566. void dismantle_item(const middleware::web_service::Message& message, Outcome& outcome) noexcept {
  567. if (message.payload.size() != kItemDismantlePayloadSize) {
  568. report_item_dismantle(
  569. message, "fail", "payload_size", 0, kUnavailableDefinitionIndex, 0, 0);
  570. return;
  571. }
  572. middleware::encoding::bits::Reader reader(message.payload);
  573. std::uint64_t instancePresent = 0;
  574. std::uint64_t instanceSoid = 0;
  575. std::uint64_t definitionPresent = 0;
  576. std::uint64_t encodedDefinitionIndex = 0;
  577. std::uint64_t encodedQuantity = 0;
  578. std::uint64_t requiredFlag = 0;
  579. std::uint64_t nestedPadding = 0;
  580. std::uint64_t outerTrailers = 0;
  581. std::uint64_t finalPadding = 0;
  582. if (!reader.read(1, instancePresent) || !reader.read(kItemDismantleInstanceWidth, instanceSoid)
  583. || !reader.read(1, definitionPresent)
  584. || !reader.read(kItemDismantleDefinitionIndexWidth, encodedDefinitionIndex)
  585. || !reader.read(kItemDismantleQuantityWidth, encodedQuantity)
  586. || !reader.read(kItemDismantleRequiredFlagWidth, requiredFlag)
  587. || !reader.read(kItemDismantleNestedPaddingWidth, nestedPadding)
  588. || !reader.read(kItemDismantleOuterTrailerWidth, outerTrailers)
  589. || !reader.read(kItemDismantleFinalPaddingWidth, finalPadding)
  590. || reader.remaining_bits() != 0) {
  591. report_item_dismantle(
  592. message, "fail", "payload_bits", 0, kUnavailableDefinitionIndex, 0, 0);
  593. return;
  594. }
  595. const auto definitionIndex = static_cast<std::uint16_t>(encodedDefinitionIndex);
  596. const auto quantityWire = static_cast<std::uint32_t>(encodedQuantity);
  597. constexpr std::uint32_t kSingleQuantity = 1;
  598. if (instancePresent == 0 || definitionPresent == 0 || instanceSoid == 0) {
  599. report_item_dismantle(
  600. message, "fail", "required_field", instanceSoid, definitionIndex, 0, 0);
  601. return;
  602. }
  603. if (nestedPadding != 0 || outerTrailers != 0 || finalPadding != 0) {
  604. report_item_dismantle(
  605. message, "fail", "padding_or_trailer", instanceSoid, definitionIndex, 0, 0);
  606. return;
  607. }
  608. if (quantityWire != kItemDismantleSingleQuantityWire || requiredFlag != 1) {
  609. report_item_dismantle(
  610. message, "fail", "quantity_or_flag", instanceSoid, definitionIndex, 0, 0);
  611. return;
  612. }
  613. state::build_data::items::Definition definition{};
  614. if (!state::build_data::find_item_definition_index(definitionIndex, definition)) {
  615. report_item_dismantle(
  616. message, "fail", "definition", instanceSoid, definitionIndex, 0, kSingleQuantity);
  617. return;
  618. }
  619. state::PendingItemDismantle mutation{};
  620. if (!state::prepare_item_dismantle(instanceSoid, mutation)) {
  621. report_item_dismantle(message,
  622. "fail",
  623. "state",
  624. instanceSoid,
  625. definitionIndex,
  626. definition.definitionHash,
  627. kSingleQuantity);
  628. return;
  629. }
  630. if (mutation.dismantledItem.definitionHash != definition.definitionHash
  631. || mutation.dismantledItem.quantity != static_cast<std::int32_t>(kSingleQuantity)) {
  632. report_item_dismantle(message,
  633. "fail",
  634. "identity",
  635. instanceSoid,
  636. definitionIndex,
  637. definition.definitionHash,
  638. kSingleQuantity);
  639. return;
  640. }
  641. outcome.mutation = mutation;
  642. report_item_dismantle(message,
  643. "ok",
  644. "ready",
  645. instanceSoid,
  646. definitionIndex,
  647. definition.definitionHash,
  648. kSingleQuantity);
  649. }
  650. /** Records strict opcode-1820 parsing, installed mapping, and State preparation outcomes. */
  651. void report_item_acquisition(const middleware::web_service::Message& message,
  652. std::string_view result,
  653. std::string_view reason,
  654. std::uint32_t collectibleIndex,
  655. std::uint32_t itemDefinitionIndex,
  656. std::uint32_t definitionHash,
  657. std::uint64_t instanceSoid) noexcept {
  658. std::array<char, core::log::kLineCapacity> line{};
  659. const int count = std::snprintf(
  660. line.data(),
  661. line.size(),
  662. "ev=ws1820 stage=prepare result=%.*s reason=%.*s transaction=%u payload_bytes=%zu "
  663. "collectible_index=%u item_definition_index=%u definition_hash=0x%08X instance=0x%llX",
  664. static_cast<int>(result.size()),
  665. result.data(),
  666. static_cast<int>(reason.size()),
  667. reason.data(),
  668. static_cast<unsigned>(message.transactionId),
  669. message.payload.size(),
  670. collectibleIndex,
  671. itemDefinitionIndex,
  672. definitionHash,
  673. static_cast<unsigned long long>(instanceSoid));
  674. if (count > 0) {
  675. core::log::write(core::log::Channel::server,
  676. result == "ok" ? core::log::Level::info : core::log::Level::warn,
  677. {line.data(), static_cast<std::size_t>(count)});
  678. }
  679. }
  680. /** Prepares the exact three-byte opcode-1820 Collections item request. */
  681. void acquire_item(const middleware::web_service::Message& message, Outcome& outcome) noexcept {
  682. if (message.payload.size() != kItemAcquisitionPayloadSize) {
  683. report_item_acquisition(message,
  684. "fail",
  685. "payload_size",
  686. kUnavailableDefinitionIndex,
  687. kUnavailableDefinitionIndex,
  688. 0,
  689. 0);
  690. return;
  691. }
  692. middleware::encoding::bits::Reader reader(message.payload);
  693. std::uint64_t present = 0;
  694. std::uint64_t encodedCollectibleIndex = 0;
  695. std::uint64_t padding = 0;
  696. if (!reader.read(kItemAcquisitionPresenceWidth, present)
  697. || !reader.read(kItemAcquisitionCollectibleIndexWidth, encodedCollectibleIndex)
  698. || !reader.read(kItemAcquisitionPaddingWidth, padding) || reader.remaining_bits() != 0) {
  699. report_item_acquisition(message,
  700. "fail",
  701. "payload_bits",
  702. kUnavailableDefinitionIndex,
  703. kUnavailableDefinitionIndex,
  704. 0,
  705. 0);
  706. return;
  707. }
  708. const auto collectibleIndex = static_cast<std::uint16_t>(encodedCollectibleIndex);
  709. if (present == 0) {
  710. report_item_acquisition(message,
  711. "fail",
  712. "collectible_absent",
  713. collectibleIndex,
  714. kUnavailableDefinitionIndex,
  715. 0,
  716. 0);
  717. return;
  718. }
  719. if (padding != 0) {
  720. report_item_acquisition(
  721. message, "fail", "padding", collectibleIndex, kUnavailableDefinitionIndex, 0, 0);
  722. return;
  723. }
  724. std::uint16_t itemDefinitionIndex = 0;
  725. if (!state::build_data::find_collectible_item_definition_index(collectibleIndex,
  726. itemDefinitionIndex)) {
  727. report_item_acquisition(message,
  728. "fail",
  729. "collectible_definition",
  730. collectibleIndex,
  731. kUnavailableDefinitionIndex,
  732. 0,
  733. 0);
  734. return;
  735. }
  736. state::build_data::items::Definition definition{};
  737. if (!state::build_data::find_item_definition_index(itemDefinitionIndex, definition)) {
  738. report_item_acquisition(
  739. message, "fail", "item_definition", collectibleIndex, itemDefinitionIndex, 0, 0);
  740. return;
  741. }
  742. state::build_data::items::details::Definition detail{};
  743. state::build_data::inventory::buckets::Descriptor bucket{};
  744. if (!state::build_data::find_configured_item_detail(itemDefinitionIndex, detail)
  745. || detail.definitionIndex != itemDefinitionIndex
  746. || detail.definitionHash != definition.definitionHash
  747. || detail.bucketId != definition.bucketId
  748. || !state::build_data::find_inventory_bucket_descriptor(detail.bucketId, bucket)) {
  749. report_item_acquisition(message,
  750. "fail",
  751. "item_detail_or_bucket",
  752. collectibleIndex,
  753. itemDefinitionIndex,
  754. definition.definitionHash,
  755. 0);
  756. return;
  757. }
  758. namespace bucket_domain = state::build_data::inventory::buckets;
  759. namespace detail_domain = state::build_data::items::details;
  760. if (bucket.arraySelector == bucket_domain::ArraySelector::profile) {
  761. if (detail.instancedDefinitionState != detail_domain::InstancedDefinitionState::stackable) {
  762. report_item_acquisition(message,
  763. "fail",
  764. "profile_item_instanced",
  765. collectibleIndex,
  766. itemDefinitionIndex,
  767. definition.definitionHash,
  768. 0);
  769. return;
  770. }
  771. state::PendingProfileItemAcquisition mutation{};
  772. if (!state::prepare_profile_item_acquisition(
  773. collectibleIndex, definition.definitionHash, mutation)) {
  774. report_item_acquisition(message,
  775. "fail",
  776. "profile_state",
  777. collectibleIndex,
  778. itemDefinitionIndex,
  779. definition.definitionHash,
  780. 0);
  781. return;
  782. }
  783. outcome.mutation = mutation;
  784. report_item_acquisition(message,
  785. "ok",
  786. "profile_ready",
  787. collectibleIndex,
  788. itemDefinitionIndex,
  789. definition.definitionHash,
  790. 0);
  791. return;
  792. }
  793. if (bucket.arraySelector != bucket_domain::ArraySelector::character) {
  794. report_item_acquisition(message,
  795. "fail",
  796. "unsupported_inventory_array",
  797. collectibleIndex,
  798. itemDefinitionIndex,
  799. definition.definitionHash,
  800. 0);
  801. return;
  802. }
  803. state::PendingItemAcquisition mutation{};
  804. if (!state::prepare_item_acquisition(collectibleIndex, definition.definitionHash, mutation)) {
  805. report_item_acquisition(message,
  806. "fail",
  807. "state",
  808. collectibleIndex,
  809. itemDefinitionIndex,
  810. definition.definitionHash,
  811. 0);
  812. return;
  813. }
  814. outcome.mutation = mutation;
  815. report_item_acquisition(message,
  816. "ok",
  817. "ready",
  818. collectibleIndex,
  819. itemDefinitionIndex,
  820. definition.definitionHash,
  821. mutation.acquiredInstanceSoid);
  822. }
  823. } // namespace sunrise::server::web_service