investment_source.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. #include <algorithm>
  2. #include <array>
  3. #include <atomic>
  4. #include <cstddef>
  5. #include <cstdint>
  6. #include <cstdio>
  7. #include "../../../core/logging/log.h"
  8. #include "../../../state/content/content_catalog.h"
  9. #include "../../memory/current_process_memory.h"
  10. #include "../../targets/game/content.h"
  11. #include "../handles/layout.h"
  12. #include "internal.h"
  13. #include "layout.h"
  14. namespace sunrise::client::content::investment {
  15. namespace {
  16. /** FNV-1 hash of the investment-globals bootstrap name, so the name itself is not shipped. */
  17. constexpr std::uint32_t kInvestmentGlobalsNameHash = 0x6F7125CBU;
  18. /** The bootstrap name is not unique, so every match is collected. */
  19. // Keep this identical to the package extractor. The installed catalogue currently has more than
  20. // eight entries with this shared name; treating a truncated lookup as total failure made live
  21. // socket routing permanently defer even though the correct candidate was present.
  22. constexpr std::size_t kBootstrapMatchCapacity = 64;
  23. /** One native resolver prefix is enough to recover all descriptor field offsets. */
  24. constexpr std::size_t kResolverDiagnosticBytes = 128;
  25. /** Package handles name descriptor ids 1,024 slots above their package id. */
  26. constexpr std::uintptr_t kContentDescriptorBias = 1024;
  27. /** Package handles keep their package id above thirteen entry-index bits. */
  28. constexpr unsigned kPackageShift = 13;
  29. /** Installed definition tags begin at this package-handle base. */
  30. constexpr std::uint32_t kPackageTagBase = 0x80800000U;
  31. std::atomic_bool g_diagnosticsReported{false};
  32. /** Reads one complete scalar through the bounded live-process reader. */
  33. template <typename Value>
  34. [[nodiscard]] bool read(const Source& source, std::uintptr_t address, Value& value) noexcept {
  35. return source.handles.read != nullptr
  36. && source.handles.read(source.handles.context,
  37. address,
  38. std::span(reinterpret_cast<std::byte*>(&value), sizeof value));
  39. }
  40. /** Reads one scalar directly from the current process for layout diagnostics. */
  41. template <typename Value>
  42. [[nodiscard]] bool read_process(std::uintptr_t address, Value& value) noexcept {
  43. return memory::read_current_process(
  44. nullptr, address, std::span(reinterpret_cast<std::byte*>(&value), sizeof value));
  45. }
  46. /** Writes one bounded memory range as a single diagnostic line. */
  47. void report_bytes(const char* stage,
  48. std::uintptr_t address,
  49. std::span<const std::byte> bytes) noexcept {
  50. std::array<char, core::log::kLineCapacity> line{};
  51. const int prefix = std::snprintf(line.data(),
  52. line.size(),
  53. "ev=investment stage=%s address=0x%llX bytes=",
  54. stage,
  55. static_cast<unsigned long long>(address));
  56. if (prefix <= 0) {
  57. return;
  58. }
  59. std::size_t length = (std::min)(static_cast<std::size_t>(prefix), line.size() - 1U);
  60. static_cast<void>(core::log::append_hex(line, length, bytes));
  61. core::log::write(core::log::Channel::client, core::log::Level::warn, {line.data(), length});
  62. }
  63. /** Writes one candidate descriptor and its package identity as a diagnostic line. */
  64. void report_descriptor(unsigned depth,
  65. const state::content::Definition& candidate,
  66. std::uintptr_t address,
  67. std::span<const std::byte> bytes) noexcept {
  68. std::array<char, core::log::kLineCapacity> line{};
  69. const int prefix = std::snprintf(line.data(),
  70. line.size(),
  71. "ev=investment stage=descriptor depth=%u tag=0x%08X "
  72. "class=0x%08X address=0x%llX bytes=",
  73. depth,
  74. candidate.tag,
  75. candidate.classId,
  76. static_cast<unsigned long long>(address));
  77. if (prefix <= 0) {
  78. return;
  79. }
  80. std::size_t length = (std::min)(static_cast<std::size_t>(prefix), line.size() - 1U);
  81. static_cast<void>(core::log::append_hex(line, length, bytes));
  82. core::log::write(core::log::Channel::client, core::log::Level::warn, {line.data(), length});
  83. }
  84. /** Captures the native resolver and every plausible descriptor base once, without mutation. */
  85. void report_layout_diagnostics(const targets::game::content::Targets& targets,
  86. std::span<const state::content::Definition> candidates) noexcept {
  87. if (g_diagnosticsReported.exchange(true, std::memory_order_relaxed)) {
  88. return;
  89. }
  90. std::array<std::byte, kResolverDiagnosticBytes> resolver{};
  91. if (targets.queuezObjectResolver != nullptr
  92. && memory::read_current_process(
  93. nullptr, reinterpret_cast<std::uintptr_t>(targets.queuezObjectResolver), resolver)) {
  94. report_bytes("resolver_bytes",
  95. reinterpret_cast<std::uintptr_t>(targets.queuezObjectResolver),
  96. resolver);
  97. }
  98. const std::uintptr_t slot = reinterpret_cast<std::uintptr_t>(targets.contentHandleTablesSlot);
  99. std::array<std::uintptr_t, 3> bases{};
  100. const bool base0 = read_process(slot, bases[0]);
  101. const bool base1 = base0 && bases[0] != 0 && read_process(bases[0], bases[1]);
  102. const bool base2 = base1 && bases[1] != 0 && read_process(bases[1], bases[2]);
  103. std::array<char, core::log::kLineCapacity> line{};
  104. const int written = std::snprintf(line.data(),
  105. line.size(),
  106. "ev=investment stage=table_chain slot=0x%llX "
  107. "read=%u/%u/%u base=0x%llX/0x%llX/0x%llX",
  108. static_cast<unsigned long long>(slot),
  109. base0 ? 1U : 0U,
  110. base1 ? 1U : 0U,
  111. base2 ? 1U : 0U,
  112. static_cast<unsigned long long>(bases[0]),
  113. static_cast<unsigned long long>(bases[1]),
  114. static_cast<unsigned long long>(bases[2]));
  115. if (written > 0) {
  116. core::log::write(
  117. core::log::Channel::client,
  118. core::log::Level::warn,
  119. {line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1U)});
  120. }
  121. for (unsigned depth = 0; depth < bases.size(); ++depth) {
  122. if (bases[depth] == 0) {
  123. continue;
  124. }
  125. for (const state::content::Definition& candidate : candidates) {
  126. if (candidate.tag < kPackageTagBase) {
  127. continue;
  128. }
  129. const std::uintptr_t package =
  130. static_cast<std::uintptr_t>(candidate.tag - kPackageTagBase) >> kPackageShift;
  131. const std::uintptr_t descriptor =
  132. bases[depth]
  133. + (package + kContentDescriptorBias) * handles::layout::kTableDescriptorSize;
  134. std::array<std::byte, handles::layout::kTableDescriptorSize> bytes{};
  135. if (memory::read_current_process(nullptr, descriptor, bytes)) {
  136. report_descriptor(depth, candidate, descriptor, bytes);
  137. }
  138. }
  139. }
  140. }
  141. } // namespace
  142. /** Resolves the checked live investment source selected by its installed bootstrap name. */
  143. bool resolve_source(Source& source) noexcept {
  144. source = {};
  145. const auto& runtimeTargets = targets::game::content::get();
  146. if (!targets::game::content::is_resolved()
  147. || runtimeTargets.contentHandleTablesSlot == nullptr) {
  148. return false;
  149. }
  150. std::array<state::content::Definition, kBootstrapMatchCapacity> candidates{};
  151. std::size_t count = 0;
  152. // A shared bootstrap name may have more installed matches than this bounded scratch array.
  153. // Truncation is not a lookup failure: every copied candidate is still safe to validate, and
  154. // rejecting the whole set is what kept socket-category routing permanently deferred.
  155. if (!state::content::lookup_hash(kInvestmentGlobalsNameHash, candidates, count) && count == 0) {
  156. return false;
  157. }
  158. report_layout_diagnostics(runtimeTargets, std::span(candidates).first(count));
  159. std::size_t globalsResolved = 0;
  160. std::size_t rootTagsRead = 0;
  161. std::size_t rootsResolved = 0;
  162. std::size_t tableTagsRead = 0;
  163. std::size_t tablesResolved = 0;
  164. for (std::size_t index = 0; index < count; ++index) {
  165. Source candidate{};
  166. candidate.investmentGlobalsTag = candidates[index].tag;
  167. candidate.handles.tablesSlot =
  168. reinterpret_cast<std::uintptr_t>(runtimeTargets.contentHandleTablesSlot);
  169. candidate.handles.read = &memory::read_current_process;
  170. std::uintptr_t globals = 0;
  171. std::uintptr_t root = 0;
  172. std::uintptr_t table = 0;
  173. std::uint32_t rootTag = 0;
  174. std::uint32_t tableTag = 0;
  175. std::uint64_t rowCount = 0;
  176. if (!handles::resolve(candidate.handles, candidate.investmentGlobalsTag, globals)) {
  177. continue;
  178. }
  179. ++globalsResolved;
  180. if (!read(candidate, globals + layout::kGlobalsRootTagOffset, rootTag)) {
  181. continue;
  182. }
  183. ++rootTagsRead;
  184. if (!handles::resolve(candidate.handles, rootTag, root)) {
  185. continue;
  186. }
  187. ++rootsResolved;
  188. if (!read(candidate, root + layout::kItemTableTagOffset, tableTag)) {
  189. continue;
  190. }
  191. ++tableTagsRead;
  192. if (!handles::resolve(candidate.handles, tableTag, table)) {
  193. continue;
  194. }
  195. ++tablesResolved;
  196. if (!read(candidate, table + 8U, rowCount) || rowCount == 0 || rowCount > 32768U) {
  197. continue;
  198. }
  199. source = candidate;
  200. return true;
  201. }
  202. std::array<char, core::log::kLineCapacity> line{};
  203. const int written = std::snprintf(line.data(),
  204. line.size(),
  205. "ev=investment stage=source result=deferred candidates=%llu "
  206. "globals=%llu root_tags=%llu roots=%llu table_tags=%llu "
  207. "tables=%llu",
  208. static_cast<unsigned long long>(count),
  209. static_cast<unsigned long long>(globalsResolved),
  210. static_cast<unsigned long long>(rootTagsRead),
  211. static_cast<unsigned long long>(rootsResolved),
  212. static_cast<unsigned long long>(tableTagsRead),
  213. static_cast<unsigned long long>(tablesResolved));
  214. if (written > 0) {
  215. core::log::write(
  216. core::log::Channel::client,
  217. core::log::Level::warn,
  218. {line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1U)});
  219. }
  220. return false;
  221. }
  222. } // namespace sunrise::client::content::investment