bootflow_texture_override.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. #include "bootflow_texture_override.h"
  2. #include <Windows.h>
  3. #include <algorithm>
  4. #include <array>
  5. #include <cstddef>
  6. #include <cstdint>
  7. #include <cstdio>
  8. #include <cstring>
  9. #include <span>
  10. #include <string_view>
  11. #include "../../../../resources/resource.h"
  12. #include "../../../core/logging/log.h"
  13. #include "../../../core/settings/settings.h"
  14. #include "../../hooking/detour.h"
  15. #include "../../patterns/image_scan.h"
  16. namespace sunrise::client::hooks::bootflow::texture_override {
  17. namespace {
  18. using patterns::scan_main_image_unique;
  19. using patterns::signature;
  20. using patterns::signature_length;
  21. /**
  22. * Resourcerer's GPU-entry dispatcher. Its second argument is the TagHash and its third and fourth
  23. * arguments are the decoded entry pointer and byte count. The switch immediately after this
  24. * prologue distinguishes the GPU resource classes.
  25. */
  26. constexpr std::string_view kGpuEntryDispatcherText =
  27. "48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 48 83 EC 20 "
  28. "49 8B F9 49 8B F0 8B DA 8B E9 E8 ? ? ? ? 84 C0 0F 85 ? ? ? ? "
  29. "8D 45 FF 83 F8 12";
  30. constexpr auto kGpuEntryDispatcher =
  31. signature<signature_length(kGpuEntryDispatcherText)>(kGpuEntryDispatcherText);
  32. constexpr std::size_t kTigerTextureHeaderSize = 0x28;
  33. constexpr std::size_t kDdsLegacyHeaderSize = 4 + 124;
  34. constexpr std::size_t kDdsDx10HeaderSize = kDdsLegacyHeaderSize + 20;
  35. constexpr std::uint32_t kDdsMagic = 0x20534444U;
  36. constexpr std::uint32_t kDx10FourCc = 0x30315844U;
  37. constexpr std::uint32_t kDdsPixelAlphaPixels = 0x1U;
  38. constexpr std::uint32_t kDdsPixelAlpha = 0x2U;
  39. constexpr std::uint32_t kDdsPixelFourCc = 0x4U;
  40. constexpr std::uint32_t kDdsPixelRgb = 0x40U;
  41. constexpr std::uint32_t kDdsPixelLuminance = 0x20000U;
  42. constexpr std::uint32_t kDdsResourceTexture2d = 3U;
  43. constexpr std::uint32_t kDdsResourceMiscCube = 0x4U;
  44. constexpr std::uint16_t kTigerTextureMarker = 0xCAFEU;
  45. constexpr std::uint32_t kGpuTextureClass = 1U;
  46. constexpr std::size_t kTigerDataSizeOffset = 0x00;
  47. constexpr std::size_t kTigerFormatOffset = 0x04;
  48. constexpr std::size_t kTigerMarkerOffset = 0x0C;
  49. constexpr std::size_t kTigerWidthOffset = 0x0E;
  50. constexpr std::size_t kTigerHeightOffset = 0x10;
  51. constexpr std::size_t kTigerDepthOffset = 0x12;
  52. constexpr std::size_t kTigerArraySizeOffset = 0x14;
  53. /** Exact stock texture-header/data pairs selected from package 0x010A. */
  54. struct AssetSpec final {
  55. std::uint32_t headerTag{};
  56. std::uint32_t dataTag{};
  57. int resourceId{};
  58. };
  59. constexpr std::array kAssetSpecs{
  60. AssetSpec{0x80A145FEU, 0x80A145FFU, IDR_BOOTFLOW_TEXTURE_80A145FF},
  61. AssetSpec{0x80A14602U, 0x80A14601U, IDR_BOOTFLOW_TEXTURE_80A14601},
  62. AssetSpec{0x80A14608U, 0x80A14607U, IDR_BOOTFLOW_TEXTURE_80A14607},
  63. AssetSpec{0x80A1460DU, 0x80A1460EU, IDR_BOOTFLOW_TEXTURE_80A1460E},
  64. AssetSpec{0x80A1461CU, 0x80A1461DU, IDR_BOOTFLOW_TEXTURE_80A1461D},
  65. AssetSpec{0x80A14620U, 0x80A1461FU, IDR_BOOTFLOW_TEXTURE_80A1461F},
  66. AssetSpec{0x80A14622U, 0x80A14621U, IDR_BOOTFLOW_TEXTURE_80A14621},
  67. AssetSpec{0x80A14623U, 0x80A14624U, IDR_BOOTFLOW_TEXTURE_80A14624},
  68. AssetSpec{0x80A14626U, 0x80A14625U, IDR_BOOTFLOW_TEXTURE_80A14625},
  69. AssetSpec{0x80A14627U, 0x80A14628U, IDR_BOOTFLOW_TEXTURE_80A14628},
  70. AssetSpec{0x80A1462AU, 0x80A14629U, IDR_BOOTFLOW_TEXTURE_80A14629},
  71. AssetSpec{0x80A1462CU, 0x80A1462BU, IDR_BOOTFLOW_TEXTURE_80A1462B},
  72. AssetSpec{0x80A1462DU, 0x80A1462EU, IDR_BOOTFLOW_TEXTURE_80A1462E},
  73. AssetSpec{0x80A14630U, 0x80A1462FU, IDR_BOOTFLOW_TEXTURE_80A1462F},
  74. AssetSpec{0x80A14632U, 0x80A14631U, IDR_BOOTFLOW_TEXTURE_80A14631},
  75. AssetSpec{0x80A14634U, 0x80A14633U, IDR_BOOTFLOW_TEXTURE_80A14633},
  76. AssetSpec{0x80A14635U, 0x80A14636U, IDR_BOOTFLOW_TEXTURE_80A14636},
  77. AssetSpec{0x80A146D4U, 0x80A146D5U, IDR_BOOTFLOW_TEXTURE_80A146D5},
  78. };
  79. struct DdsView final {
  80. const std::byte* pixels{};
  81. std::uint32_t pixelSize{};
  82. std::uint32_t format{};
  83. std::uint16_t width{};
  84. std::uint16_t height{};
  85. std::uint16_t depth{};
  86. std::uint16_t arraySize{};
  87. };
  88. struct Asset final {
  89. AssetSpec spec{};
  90. DdsView dds{};
  91. std::array<std::byte, kTigerTextureHeaderSize> header{};
  92. bool headerReady{};
  93. bool reported{};
  94. };
  95. using GpuEntryDispatcher = std::uint64_t(__fastcall*)(std::uint32_t resourceClass,
  96. std::uint32_t tag,
  97. const void* decoded,
  98. std::uint64_t decodedSize) noexcept;
  99. hooking::detour::Handle g_handle{};
  100. SRWLOCK g_assetLock{SRWLOCK_INIT};
  101. std::array<Asset, kAssetSpecs.size()> g_assets{};
  102. template <typename Value>
  103. [[nodiscard]] bool
  104. load_value(const std::byte* bytes, std::size_t size, std::size_t offset, Value& output) noexcept {
  105. if (bytes == nullptr || offset > size || sizeof(Value) > size - offset) {
  106. return false;
  107. }
  108. std::memcpy(&output, bytes + offset, sizeof output);
  109. return true;
  110. }
  111. template <typename Value>
  112. void store_value(std::span<std::byte> bytes, std::size_t offset, Value value) noexcept {
  113. if (offset <= bytes.size() && sizeof(Value) <= bytes.size() - offset) {
  114. std::memcpy(bytes.data() + offset, &value, sizeof value);
  115. }
  116. }
  117. [[nodiscard]] constexpr std::uint32_t
  118. four_cc(char first, char second, char third, char fourth) noexcept {
  119. return static_cast<std::uint32_t>(static_cast<unsigned char>(first))
  120. | (static_cast<std::uint32_t>(static_cast<unsigned char>(second)) << 8U)
  121. | (static_cast<std::uint32_t>(static_cast<unsigned char>(third)) << 16U)
  122. | (static_cast<std::uint32_t>(static_cast<unsigned char>(fourth)) << 24U);
  123. }
  124. /** Converts the legacy DDS formats used by the supplied bootflow assets to DXGI values. */
  125. [[nodiscard]] bool
  126. legacy_format(const std::byte* bytes, std::size_t size, std::uint32_t& output) noexcept {
  127. std::uint32_t flags = 0;
  128. std::uint32_t formatFourCc = 0;
  129. std::uint32_t bits = 0;
  130. std::uint32_t red = 0;
  131. std::uint32_t green = 0;
  132. std::uint32_t blue = 0;
  133. std::uint32_t alpha = 0;
  134. if (!load_value(bytes, size, 0x50, flags) || !load_value(bytes, size, 0x54, formatFourCc)
  135. || !load_value(bytes, size, 0x58, bits) || !load_value(bytes, size, 0x5C, red)
  136. || !load_value(bytes, size, 0x60, green) || !load_value(bytes, size, 0x64, blue)
  137. || !load_value(bytes, size, 0x68, alpha)) {
  138. return false;
  139. }
  140. if ((flags & kDdsPixelFourCc) != 0) {
  141. switch (formatFourCc) {
  142. case four_cc('D', 'X', 'T', '1'):
  143. output = 71U;
  144. return true;
  145. case four_cc('D', 'X', 'T', '3'):
  146. output = 74U;
  147. return true;
  148. case four_cc('D', 'X', 'T', '5'):
  149. output = 77U;
  150. return true;
  151. case four_cc('A', 'T', 'I', '1'):
  152. case four_cc('B', 'C', '4', 'U'):
  153. output = 80U;
  154. return true;
  155. case four_cc('A', 'T', 'I', '2'):
  156. case four_cc('B', 'C', '5', 'U'):
  157. output = 83U;
  158. return true;
  159. default:
  160. return false;
  161. }
  162. }
  163. if ((flags & kDdsPixelRgb) != 0 && bits == 32U) {
  164. if (red == 0x000000FFU && green == 0x0000FF00U && blue == 0x00FF0000U
  165. && alpha == 0xFF000000U) {
  166. output = 28U;
  167. return true;
  168. }
  169. if (red == 0x00FF0000U && green == 0x0000FF00U && blue == 0x000000FFU) {
  170. output = alpha == 0xFF000000U ? 87U : 88U;
  171. return alpha == 0xFF000000U || alpha == 0U;
  172. }
  173. }
  174. if ((flags & kDdsPixelRgb) != 0 && bits == 16U) {
  175. if (red == 0xF800U && green == 0x07E0U && blue == 0x001FU && alpha == 0U) {
  176. output = 85U;
  177. return true;
  178. }
  179. if (red == 0x7C00U && green == 0x03E0U && blue == 0x001FU && alpha == 0x8000U) {
  180. output = 86U;
  181. return true;
  182. }
  183. if (red == 0x0F00U && green == 0x00F0U && blue == 0x000FU && alpha == 0xF000U) {
  184. output = 115U;
  185. return true;
  186. }
  187. }
  188. if ((flags & kDdsPixelLuminance) != 0 && bits == 8U && red == 0xFFU) {
  189. output = 61U;
  190. return true;
  191. }
  192. if ((flags & kDdsPixelLuminance) != 0 && (flags & kDdsPixelAlphaPixels) != 0 && bits == 16U
  193. && red == 0x00FFU && alpha == 0xFF00U) {
  194. output = 49U;
  195. return true;
  196. }
  197. if ((flags & kDdsPixelAlpha) != 0 && bits == 8U && alpha == 0xFFU) {
  198. output = 65U;
  199. return true;
  200. }
  201. return false;
  202. }
  203. /** Parses one embedded 2D DDS without allocating or copying its pixel payload. */
  204. [[nodiscard]] bool parse_dds(const std::byte* bytes, std::size_t size, DdsView& output) noexcept {
  205. output = {};
  206. std::uint32_t magic = 0;
  207. std::uint32_t headerSize = 0;
  208. std::uint32_t pixelHeaderSize = 0;
  209. std::uint32_t pixelFlags = 0;
  210. std::uint32_t formatFourCc = 0;
  211. if (size < kDdsLegacyHeaderSize || !load_value(bytes, size, 0x00, magic)
  212. || !load_value(bytes, size, 0x04, headerSize)
  213. || !load_value(bytes, size, 0x4C, pixelHeaderSize)
  214. || !load_value(bytes, size, 0x50, pixelFlags)
  215. || !load_value(bytes, size, 0x54, formatFourCc) || magic != kDdsMagic || headerSize != 124U
  216. || pixelHeaderSize != 32U) {
  217. return false;
  218. }
  219. const bool dx10 = (pixelFlags & kDdsPixelFourCc) != 0 && formatFourCc == kDx10FourCc;
  220. const std::size_t pixelOffset = dx10 ? kDdsDx10HeaderSize : kDdsLegacyHeaderSize;
  221. std::uint32_t width = 0;
  222. std::uint32_t height = 0;
  223. std::uint32_t depth = 0;
  224. std::uint32_t arraySize = 1;
  225. std::uint32_t resourceDimension = kDdsResourceTexture2d;
  226. std::uint32_t miscFlag = 0;
  227. std::uint32_t format = 0;
  228. if (size <= pixelOffset || !load_value(bytes, size, 0x10, width)
  229. || !load_value(bytes, size, 0x0C, height) || !load_value(bytes, size, 0x18, depth)) {
  230. return false;
  231. }
  232. if (dx10) {
  233. if (!load_value(bytes, size, 0x80, format)
  234. || !load_value(bytes, size, 0x84, resourceDimension)
  235. || !load_value(bytes, size, 0x88, miscFlag)
  236. || !load_value(bytes, size, 0x8C, arraySize)) {
  237. return false;
  238. }
  239. } else if (!legacy_format(bytes, size, format)) {
  240. return false;
  241. }
  242. const std::size_t pixelSize = size - pixelOffset;
  243. if (width == 0 || height == 0 || width > 0xFFFFU || height > 0xFFFFU || pixelSize == 0
  244. || pixelSize > 0xFFFFFFFFULL || format == 0 || resourceDimension != kDdsResourceTexture2d
  245. || (miscFlag & kDdsResourceMiscCube) != 0 || arraySize == 0 || arraySize > 0xFFFFU) {
  246. return false;
  247. }
  248. output = DdsView{bytes + pixelOffset,
  249. static_cast<std::uint32_t>(pixelSize),
  250. format,
  251. static_cast<std::uint16_t>(width),
  252. static_cast<std::uint16_t>(height),
  253. 1,
  254. static_cast<std::uint16_t>(arraySize)};
  255. return true;
  256. }
  257. /** Loads and validates every embedded DDS before the detour can expose any of them. */
  258. [[nodiscard]] bool load_assets(HMODULE module) noexcept {
  259. if (module == nullptr) {
  260. return false;
  261. }
  262. for (std::size_t index = 0; index < kAssetSpecs.size(); ++index) {
  263. const AssetSpec& spec = kAssetSpecs[index];
  264. const HRSRC resource = FindResourceW(module, MAKEINTRESOURCEW(spec.resourceId), RT_RCDATA);
  265. if (resource == nullptr) {
  266. return false;
  267. }
  268. const HGLOBAL loaded = LoadResource(module, resource);
  269. const DWORD size = SizeofResource(module, resource);
  270. const auto* bytes = static_cast<const std::byte*>(LockResource(loaded));
  271. Asset asset{};
  272. asset.spec = spec;
  273. if (loaded == nullptr || bytes == nullptr || size == 0
  274. || !parse_dds(bytes, static_cast<std::size_t>(size), asset.dds)) {
  275. return false;
  276. }
  277. g_assets[index] = asset;
  278. }
  279. return true;
  280. }
  281. /** @return The asset owning this exact header or data TagHash. */
  282. [[nodiscard]] Asset* find_asset(std::uint32_t tag, bool& header) noexcept {
  283. for (Asset& asset : g_assets) {
  284. if (asset.spec.headerTag == tag) {
  285. header = true;
  286. return &asset;
  287. }
  288. if (asset.spec.dataTag == tag) {
  289. header = false;
  290. return &asset;
  291. }
  292. }
  293. return nullptr;
  294. }
  295. /** Builds one replacement Tiger descriptor from its stock descriptor and embedded DDS. */
  296. [[nodiscard]] const void*
  297. prepare_header(Asset& asset, const void* stock, std::uint64_t stockSize, bool& report) noexcept {
  298. report = false;
  299. if (stock == nullptr || stockSize < kTigerTextureHeaderSize) {
  300. return stock;
  301. }
  302. AcquireSRWLockExclusive(&g_assetLock);
  303. if (!asset.headerReady) {
  304. std::memcpy(asset.header.data(), stock, asset.header.size());
  305. std::uint16_t marker = 0;
  306. std::memcpy(&marker, asset.header.data() + kTigerMarkerOffset, sizeof marker);
  307. if (marker == kTigerTextureMarker) {
  308. const std::span header(asset.header);
  309. store_value(header, kTigerDataSizeOffset, asset.dds.pixelSize);
  310. store_value(header, kTigerFormatOffset, asset.dds.format);
  311. store_value(header, kTigerWidthOffset, asset.dds.width);
  312. store_value(header, kTigerHeightOffset, asset.dds.height);
  313. store_value(header, kTigerDepthOffset, asset.dds.depth);
  314. store_value(header, kTigerArraySizeOffset, asset.dds.arraySize);
  315. asset.headerReady = true;
  316. }
  317. }
  318. if (asset.headerReady && !asset.reported) {
  319. asset.reported = true;
  320. report = true;
  321. }
  322. const void* result = asset.headerReady ? asset.header.data() : stock;
  323. ReleaseSRWLockExclusive(&g_assetLock);
  324. return result;
  325. }
  326. void report_override(const Asset& asset) noexcept {
  327. std::array<char, 160> line{};
  328. const int written = std::snprintf(line.data(),
  329. line.size(),
  330. "ev=bootflow_texture stage=entry tag=0x%08X size=%u "
  331. "width=%u height=%u result=override",
  332. static_cast<unsigned>(asset.spec.dataTag),
  333. static_cast<unsigned>(asset.dds.pixelSize),
  334. static_cast<unsigned>(asset.dds.width),
  335. static_cast<unsigned>(asset.dds.height));
  336. if (written > 0) {
  337. core::log::write(
  338. core::log::Channel::client,
  339. core::log::Level::info,
  340. {line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1)});
  341. }
  342. }
  343. /** Replaces only selected decoded GPU texture entries, then preserves the native dispatcher. */
  344. std::uint64_t __fastcall dispatch(std::uint32_t resourceClass,
  345. std::uint32_t tag,
  346. const void* decoded,
  347. std::uint64_t decodedSize) noexcept {
  348. const auto original = reinterpret_cast<GpuEntryDispatcher>(g_handle.original);
  349. if (original == nullptr) {
  350. return 7;
  351. }
  352. bool header = false;
  353. Asset* const asset = resourceClass == kGpuTextureClass ? find_asset(tag, header) : nullptr;
  354. if (asset == nullptr) {
  355. return original(resourceClass, tag, decoded, decodedSize);
  356. }
  357. if (!header) {
  358. return original(resourceClass, tag, asset->dds.pixels, asset->dds.pixelSize);
  359. }
  360. bool report = false;
  361. const void* const replacement = prepare_header(*asset, decoded, decodedSize, report);
  362. if (report) {
  363. report_override(*asset);
  364. }
  365. return original(resourceClass,
  366. tag,
  367. replacement,
  368. replacement == decoded ? decodedSize : kTigerTextureHeaderSize);
  369. }
  370. void clear_assets() noexcept {
  371. AcquireSRWLockExclusive(&g_assetLock);
  372. g_assets = {};
  373. ReleaseSRWLockExclusive(&g_assetLock);
  374. }
  375. } // namespace
  376. /** Loads embedded DDS files and attaches the decoded GPU-entry dispatcher. */
  377. bool install(void* module) noexcept {
  378. if (g_handle.attached) {
  379. return true;
  380. }
  381. if (!core::settings::get().client.customBootflowTextures) {
  382. core::log::write(core::log::Channel::client,
  383. core::log::Level::info,
  384. "ev=bootflow_texture stage=setting enabled=0 result=skip");
  385. return true;
  386. }
  387. if (!load_assets(static_cast<HMODULE>(module))) {
  388. clear_assets();
  389. core::log::write(core::log::Channel::client,
  390. core::log::Level::error,
  391. "ev=bootflow_texture stage=resources result=fail");
  392. return false;
  393. }
  394. std::byte* const target =
  395. scan_main_image_unique(kGpuEntryDispatcher, "bootflow_gpu_entry_dispatcher");
  396. const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&dispatch)};
  397. if (target == nullptr || !hooking::detour::install(spec, g_handle)) {
  398. clear_assets();
  399. core::log::write(core::log::Channel::client,
  400. core::log::Level::error,
  401. "ev=bootflow_texture stage=attach result=fail");
  402. return false;
  403. }
  404. std::array<char, 80> line{};
  405. const int written = std::snprintf(line.data(),
  406. line.size(),
  407. "ev=bootflow_texture stage=attach count=%zu result=ok",
  408. kAssetSpecs.size());
  409. if (written > 0) {
  410. core::log::write(
  411. core::log::Channel::client,
  412. core::log::Level::info,
  413. {line.data(), (std::min)(static_cast<std::size_t>(written), line.size() - 1)});
  414. }
  415. return true;
  416. }
  417. /** Detaches before releasing the resource views and generated Tiger descriptors. */
  418. bool uninstall() noexcept {
  419. if (g_handle.attached && !hooking::detour::uninstall(g_handle)) {
  420. return false;
  421. }
  422. clear_assets();
  423. return true;
  424. }
  425. /** @return True while the decoded GPU-entry dispatcher is attached. */
  426. bool is_installed() noexcept {
  427. return g_handle.attached;
  428. }
  429. } // namespace sunrise::client::hooks::bootflow::texture_override