Skip to content

Commit 4285afc

Browse files
authored
Different Directory Pak File Managment/Loading (#1548)
* Different Directory Pak File Managment/Loading Fix #1544 * TDB version wrapping * Review changes * Rename folder * Fix naming * Review changes
1 parent c5c1f22 commit 4285afc

File tree

4 files changed

+360
-2
lines changed

4 files changed

+360
-2
lines changed

src/Mods.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Mods::Mods() {
2626
m_mods.emplace_back(REFrameworkConfig::get());
2727

2828
#if defined(REENGINE_AT)
29-
m_mods.emplace_back(std::make_unique<IntegrityCheckBypass>());
29+
m_mods.emplace_back(IntegrityCheckBypass::get_shared_instance());
3030
#endif
3131

3232
#ifndef BAREBONES

src/REFramework.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,11 +558,13 @@ REFramework::REFramework(HMODULE reframework_module)
558558

559559
if (sdk::RETypeDB::get() != nullptr) {
560560
auto& loader = LooseFileLoader::get(); // Initialize this really early
561+
auto &integrity_bypass = IntegrityCheckBypass::get_shared_instance();
561562

562563
const auto config_path = get_persistent_dir(REFrameworkConfig::REFRAMEWORK_CONFIG_NAME.data()).string();
563564
if (fs::exists(utility::widen(config_path))) {
564565
utility::Config cfg{ config_path };
565566
loader->on_config_load(cfg);
567+
integrity_bypass->on_config_load(cfg);
566568
}
567569

568570
if (loader->is_enabled()) {

src/mods/IntegrityCheckBypass.cpp

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
1928
std::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

Comments
 (0)