@@ -16,6 +16,15 @@ struct IntegrityCheckPattern {
1616 uint32_t offset{};
1717};
1818
19+ std::shared_ptr<IntegrityCheckBypass> s_integrity_check_bypass_instance{nullptr };
20+
21+ std::shared_ptr<IntegrityCheckBypass>& IntegrityCheckBypass::get_shared_instance () {
22+ if (!s_integrity_check_bypass_instance) {
23+ s_integrity_check_bypass_instance = std::make_unique<IntegrityCheckBypass>();
24+ }
25+ return s_integrity_check_bypass_instance;
26+ }
27+
1928std::optional<std::string> IntegrityCheckBypass::on_initialize () {
2029 // Patterns for assigning or accessing of the integrity check boolean (RE3)
2130 // and for jumping past the integrity checks (RE8)
@@ -917,6 +926,8 @@ void IntegrityCheckBypass::restore_unencrypted_paks() {
917926 if (pak_load_check_start) {
918927 spdlog::info (" [IntegrityCheckBypass]: Found pak_load_check_function @ 0x{:X}, hook!" , (uintptr_t )*pak_load_check_start);
919928 s_pak_load_check_function_hook = safetyhook::create_mid ((void *)*pak_load_check_start, &IntegrityCheckBypass::pak_load_check_function);
929+
930+ find_try_hook_via_file_load_win32_create_file (*pak_load_check_start);
920931 }
921932
922933 const auto patch_version_start = utility::scan (game, " 48 89 ? 24 ? 48 85 FF 0F 84 ? ? ? ? 66 83 3F 72 0F 85 ? ? ? ? 66 BA 72 00" );
@@ -1041,6 +1052,12 @@ int IntegrityCheckBypass::scan_patch_files_count() {
10411052 highest_patch_num = 0 ;
10421053 }
10431054
1055+ auto integrity_shared_instance = IntegrityCheckBypass::get_shared_instance ();
1056+ auto other_custom_paks_count = integrity_shared_instance->cache_and_count_custom_pak_in_directory ();
1057+
1058+ s_base_directory_patch_count = highest_patch_num;
1059+ highest_patch_num += other_custom_paks_count;
1060+
10441061 s_patch_count_checked = true ;
10451062 s_patch_count = highest_patch_num;
10461063
@@ -1534,3 +1551,306 @@ void* IntegrityCheckBypass::rtl_exit_user_process_hook(uint32_t code) {
15341551 TerminateProcess (GetCurrentProcess (), code);
15351552 return nullptr ;
15361553}
1554+
1555+ #pragma region Custom PAK directory loading
1556+
1557+ #define ENABLE_PAK_DIRECTORY_LOAD (TDB_VER >= 81 )
1558+
1559+ static utility::ExhaustionResult do_exhaustion_scan_create_file_refs (utility::ExhaustionContext &ctx, uintptr_t target_search_func, std::vector<uintptr_t > &before_create_file_ptrs) {
1560+ if (ctx.instrux .Category == ND_CAT_CALL) {
1561+ if (ctx.instrux .Instruction == ND_INS_CALLNI) {
1562+ auto displacement_opt = utility::resolve_displacement (ctx.addr );
1563+ if (displacement_opt && *(uintptr_t *)(*displacement_opt) == target_search_func) {
1564+ spdlog::info (" [IntegrityCheckBypass]: Found stream open's call to CreateFileW at 0x{:X}, hooking it!" , ctx.addr );
1565+ before_create_file_ptrs.push_back (ctx.addr );
1566+ }
1567+ }
1568+
1569+ return utility::ExhaustionResult::STEP_OVER;
1570+ }
1571+
1572+ return utility::ExhaustionResult::CONTINUE;
1573+ }
1574+
1575+ void IntegrityCheckBypass::find_try_hook_via_file_load_win32_create_file (uintptr_t pak_load_func_addr) {
1576+ #if ENABLE_PAK_DIRECTORY_LOAD
1577+ // Find the first call instruction, thats our opening PAK file function
1578+ const int INSTRUCTION_SEARCH_COUNT = 60 ;
1579+
1580+ uint8_t *open_stream_func_addr = 0 ;
1581+ uint8_t *search_current = (uint8_t *)pak_load_func_addr;
1582+
1583+ for (int i = 0 ; i < INSTRUCTION_SEARCH_COUNT; i++) {
1584+ auto instr = utility::decode_one (search_current);
1585+ if (!instr) {
1586+ continue ;
1587+ }
1588+
1589+ if (instr->Instruction == ND_INS_CALLNR) {
1590+ if (auto resolved_opt = utility::resolve_displacement ((uintptr_t )search_current)) {
1591+ open_stream_func_addr = (uint8_t *)*resolved_opt;
1592+ break ;
1593+ }
1594+ }
1595+
1596+ search_current += instr->Length ;
1597+ }
1598+
1599+ if (open_stream_func_addr == nullptr ) {
1600+ spdlog::error (" [IntegrityCheckBypass]: Could not find call to stream open function!" );
1601+ return ;
1602+ }
1603+
1604+ uintptr_t target_search_func = (uintptr_t )&CreateFileW;
1605+ const std::size_t exhaustive_decode_max = 12000 ;
1606+
1607+ std::vector<uintptr_t > before_create_file_ptrs{};
1608+
1609+ spdlog::info (" [IntegrityCheckBypass]: Exhaustively decoding from 0x{:X} to find calls to CreateFileW..." , (uintptr_t )open_stream_func_addr);
1610+
1611+ utility::exhaustive_decode (open_stream_func_addr, exhaustive_decode_max, [target_search_func, &before_create_file_ptrs](utility::ExhaustionContext &ctx) {
1612+ return do_exhaustion_scan_create_file_refs (ctx, target_search_func, before_create_file_ptrs);
1613+ });
1614+
1615+ for (auto ptr : before_create_file_ptrs) {
1616+ auto hook = safetyhook::create_mid ((void *)ptr, &IntegrityCheckBypass::via_file_prepare_to_create_file_w_hook_wrappper);
1617+ if (hook) {
1618+ spdlog::info (" [IntegrityCheckBypass]: Successfully hooked instruction before CreateFileW at 0x{:X}!" , ptr);
1619+ s_before_create_file_w_hooks.push_back (std::move (hook));
1620+ } else {
1621+ spdlog::error (" [IntegrityCheckBypass]: Failed to hook instruction before CreateFileW at 0x{:X}!" , ptr);
1622+ }
1623+ }
1624+
1625+ const char *direct_storage_open_pak_pattern = " 48 8D 56 08 48 8D 7C 24 ? 48 C7 07 00 00 00 00 48 8B 0D ? ? ? ? 48 8B 01 4C 8D 05 ? ? ? ? 49 89 F9 FF 50 20 48 8B 0F 85 C0" ;
1626+ auto direct_storage_open_pak_func_addr = utility::scan (utility::get_executable (), direct_storage_open_pak_pattern);
1627+
1628+ if (!direct_storage_open_pak_func_addr) {
1629+ spdlog::error (" [IntegrityCheckBypass]: Could not find DirectStorage pak open block!" );
1630+ return ;
1631+ }
1632+
1633+ // Find the first CALLNI instruction, which is the call to open the pak file stream
1634+ uint8_t *direct_storage_before_open_pak_call = nullptr ;
1635+ search_current = (uint8_t *)*direct_storage_open_pak_func_addr;
1636+
1637+ const int DIRECT_STORAGE_OPEN_PAK_CALL_SEARCH_COUNT = 25 ;
1638+
1639+ for (int i = 0 ; i < DIRECT_STORAGE_OPEN_PAK_CALL_SEARCH_COUNT; i++) {
1640+ auto instr = utility::decode_one (search_current);
1641+ if (!instr) {
1642+ continue ;
1643+ }
1644+
1645+ if (instr->Instruction == ND_INS_CALLNI) {
1646+ direct_storage_before_open_pak_call = search_current;
1647+ break ;
1648+ }
1649+
1650+ search_current += instr->Length ;
1651+ }
1652+
1653+ if (direct_storage_before_open_pak_call == nullptr ) {
1654+ spdlog::error (" [IntegrityCheckBypass]: Could not find call to open pak file stream in DirectStorage pak open block!" );
1655+ return ;
1656+ }
1657+
1658+ s_directstorage_open_pak_hook = safetyhook::create_mid ((void *)direct_storage_before_open_pak_call, &IntegrityCheckBypass::directstorage_open_pak_hook_wrappper);
1659+ spdlog::info (" [IntegrityCheckBypass]: Hooked DirectStorage pak open function at 0x{:X}!" , (uintptr_t )direct_storage_before_open_pak_call);
1660+ #else
1661+ spdlog::info (" [IntegrityCheckBypass]: Custom pak directory loading is not supported for TDB version {}" , TDB_VER);
1662+ #endif
1663+ }
1664+
1665+ int IntegrityCheckBypass::cache_and_count_custom_pak_in_directory () {
1666+ #if !ENABLE_PAK_DIRECTORY_LOAD
1667+ return 0 ;
1668+ #else
1669+ if (!m_load_pak_directory || !m_load_pak_directory->value ()) {
1670+ spdlog::info (" [IntegrityCheckBypass]: Pak directory loading is disabled, skipping it." );
1671+ return 0 ;
1672+ }
1673+
1674+ if (m_custom_pak_in_directory_paths_cached) {
1675+ return static_cast <int >(m_custom_pak_in_directory_paths.size ());
1676+ }
1677+
1678+ spdlog::info (" [IntegrityCheckBypass]: Caching custom pak paths in executable directory..." );
1679+ m_custom_pak_in_directory_paths_cached = true ;
1680+
1681+ auto exe_module = utility::get_executable ();
1682+ auto exe_path = utility::get_module_pathw (exe_module);
1683+ auto exe_dir = std::filesystem::path (*exe_path).parent_path ();
1684+ auto pak_dir_fs_path = exe_dir / CUSTOM_PAK_DIRECTORY_PATH;
1685+
1686+ if (!std::filesystem::exists (pak_dir_fs_path)) {
1687+ spdlog::warn (" [IntegrityCheckBypass]: Custom pak directory does not exist at path: {}" , utility::narrow (pak_dir_fs_path.wstring ()));
1688+ return 0 ;
1689+ }
1690+
1691+ // Iterate through the directory (recursively), and cache paths of all .pak files
1692+ for (const auto & entry : std::filesystem::recursive_directory_iterator (pak_dir_fs_path)) {
1693+ if (entry.is_regular_file () && entry.path ().extension () == PAK_EXTENSION_NAME) {
1694+ m_custom_pak_in_directory_paths.push_back (entry.path ());
1695+ spdlog::info (" [IntegrityCheckBypass]: Cached custom pak with name: {} at path: {}" , entry.path ().filename ().string (), utility::narrow (entry.path ().wstring ()));
1696+ }
1697+ }
1698+
1699+ spdlog::info (" [IntegrityCheckBypass]: Finished caching custom pak paths. Total count: {}" , m_custom_pak_in_directory_paths.size ());
1700+ return static_cast <int >(m_custom_pak_in_directory_paths.size ());
1701+ #endif
1702+ }
1703+
1704+ std::optional<int > IntegrityCheckBypass::extract_patch_num_from_path (std::wstring &path) {
1705+ std::wsmatch match;
1706+ if (std::regex_match (path, match, m_sub_patch_scan_regex)) {
1707+ try {
1708+ const int patch_num = std::stoi (match[1 ].str ());
1709+ return patch_num;
1710+ } catch (const std::exception& e) {
1711+ return std::nullopt ;
1712+ }
1713+ }
1714+ return std::nullopt ;
1715+ }
1716+
1717+ void IntegrityCheckBypass::via_file_prepare_to_create_file_w_hook_wrappper (safetyhook::Context& context) {
1718+ auto instance = IntegrityCheckBypass::get_shared_instance ();
1719+ if (instance) {
1720+ instance->via_file_prepare_to_create_file_w_hook (context);
1721+ } else {
1722+ spdlog::error (" [IntegrityCheckBypass]: Shared instance is null in via_file_prepare_to_create_file_w_hook_wrapper!" );
1723+ }
1724+ }
1725+
1726+ void IntegrityCheckBypass::directstorage_open_pak_hook_wrappper (safetyhook::Context& context) {
1727+ auto instance = IntegrityCheckBypass::get_shared_instance ();
1728+ if (instance) {
1729+ instance->directstorage_open_pak_hook (context);
1730+ } else {
1731+ spdlog::error (" [IntegrityCheckBypass]: Shared instance is null in directstorage_open_pak_hook_wrapper!" );
1732+ }
1733+ }
1734+
1735+ template <typename T>
1736+ T get_register_value (safetyhook::Context& context, int reg) {
1737+ switch (reg) {
1738+ case NDR_RAX: return (T)context.rax ;
1739+ case NDR_RCX: return (T)context.rcx ;
1740+ case NDR_RDX: return (T)context.rdx ;
1741+ case NDR_RBX: return (T)context.rbx ;
1742+ case NDR_RSP: return (T)context.rsp ;
1743+ case NDR_RBP: return (T)context.rbp ;
1744+ case NDR_RSI: return (T)context.rsi ;
1745+ case NDR_RDI: return (T)context.rdi ;
1746+ case NDR_R8: return (T)context.r8 ;
1747+ case NDR_R9: return (T)context.r9 ;
1748+ case NDR_R10: return (T)context.r10 ;
1749+ case NDR_R11: return (T)context.r11 ;
1750+ case NDR_R12: return (T)context.r12 ;
1751+ case NDR_R13: return (T)context.r13 ;
1752+ case NDR_R14: return (T)context.r14 ;
1753+ case NDR_R15: return (T)context.r15 ;
1754+ default : return (T)0 ;
1755+ }
1756+ }
1757+
1758+ template <typename T>
1759+ void set_register_value (safetyhook::Context& context, int reg, T value) {
1760+ switch (reg) {
1761+ case NDR_RAX: context.rax = (uint64_t )value; break ;
1762+ case NDR_RCX: context.rcx = (uint64_t )value; break ;
1763+ case NDR_RDX: context.rdx = (uint64_t )value; break ;
1764+ case NDR_RBX: context.rbx = (uint64_t )value; break ;
1765+ case NDR_RSP: context.rsp = (uint64_t )value; break ;
1766+ case NDR_RBP: context.rbp = (uint64_t )value; break ;
1767+ case NDR_RSI: context.rsi = (uint64_t )value; break ;
1768+ case NDR_RDI: context.rdi = (uint64_t )value; break ;
1769+ case NDR_R8: context.r8 = (uint64_t )value; break ;
1770+ case NDR_R9: context.r9 = (uint64_t )value; break ;
1771+ case NDR_R10: context.r10 = (uint64_t )value; break ;
1772+ case NDR_R11: context.r11 = (uint64_t )value; break ;
1773+ case NDR_R12: context.r12 = (uint64_t )value; break ;
1774+ case NDR_R13: context.r13 = (uint64_t )value; break ;
1775+ case NDR_R14: context.r14 = (uint64_t )value; break ;
1776+ case NDR_R15: context.r15 = (uint64_t )value; break ;
1777+ }
1778+ }
1779+
1780+ void IntegrityCheckBypass::correct_pak_load_path (safetyhook::Context& context, int register_index) {
1781+ if (!m_load_pak_directory || !m_load_pak_directory->value () || m_custom_pak_in_directory_paths.empty ()) {
1782+ return ;
1783+ }
1784+
1785+ auto path_ptr = get_register_value<wchar_t *>(context, register_index);
1786+ if (path_ptr != nullptr ) {
1787+ std::wstring_view path_view (path_ptr);
1788+ if (path_view.ends_with (PAK_EXTENSION_NAME_W)) {
1789+ std::wstring filename_copy = std::filesystem::path (path_view).filename ().wstring ();
1790+ auto patch_num_opt = extract_patch_num_from_path (filename_copy);
1791+
1792+ if (patch_num_opt) {
1793+ int patch_num = *patch_num_opt;
1794+ if (patch_num > s_base_directory_patch_count) {
1795+ auto custom_directory_pak_index = patch_num - s_base_directory_patch_count - 1 ;
1796+ if (custom_directory_pak_index < m_custom_pak_in_directory_paths.size ()) {
1797+ auto &pak_path = m_custom_pak_in_directory_paths[custom_directory_pak_index];
1798+ spdlog::info (" [IntegrityCheckBypass]: Redirecting load of {} to custom pak at path: {}" , utility::narrow (filename_copy), utility::narrow (pak_path));
1799+
1800+ set_register_value (context, register_index, pak_path.c_str ());
1801+ } else {
1802+ spdlog::error (" [IntegrityCheckBypass]: Patch number {} is out of range for PAK directory's PAK! (index {})" , patch_num, custom_directory_pak_index);
1803+ }
1804+ }
1805+ }
1806+ }
1807+ }
1808+ }
1809+
1810+ void IntegrityCheckBypass::directstorage_open_pak_hook (safetyhook::Context& context) {
1811+ correct_pak_load_path (context, NDR_RDX);
1812+ }
1813+
1814+ void IntegrityCheckBypass::via_file_prepare_to_create_file_w_hook (safetyhook::Context& context) {
1815+ correct_pak_load_path (context, NDR_RCX);
1816+ }
1817+
1818+ #pragma endregion
1819+
1820+ void IntegrityCheckBypass::on_config_load (const utility::Config& cfg) {
1821+ for (IModValue& option : m_options) {
1822+ option.config_load (cfg);
1823+ }
1824+ }
1825+
1826+ void IntegrityCheckBypass::on_config_save (utility::Config& cfg) {
1827+ for (IModValue& option : m_options) {
1828+ option.config_save (cfg);
1829+ }
1830+ }
1831+
1832+ void IntegrityCheckBypass::on_draw_ui () {
1833+ #if ENABLE_PAK_DIRECTORY_LOAD
1834+ if (!ImGui::CollapsingHeader (" PAK Directory Loading" )) {
1835+ return ;
1836+ }
1837+
1838+ ImGui::Text (" Allow loading PAKs inside %s directory. PAKs can be of any filename and ends with .pak (case-sensitive)" , IntegrityCheckBypass::CUSTOM_PAK_DIRECTORY_PATH);
1839+ ImGui::Text (" Restart the game to apply changes." );
1840+
1841+ auto changed = false ;
1842+ changed |= m_load_pak_directory->draw (" Enable" );
1843+
1844+ if (changed) {
1845+ g_framework->request_save_config ();
1846+ }
1847+
1848+ if (ImGui::TreeNode (" List of custom PAKs loaded:" )) {
1849+ for (const auto & pak_path : m_custom_pak_in_directory_paths) {
1850+ auto pak_utf8 = utility::narrow (pak_path);
1851+ ImGui::BulletText (" %s" , pak_utf8.c_str ());
1852+ }
1853+ ImGui::TreePop ();
1854+ }
1855+ #endif
1856+ }
0 commit comments