Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Mods.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Mods::Mods() {
m_mods.emplace_back(REFrameworkConfig::get());

#if defined(REENGINE_AT)
m_mods.emplace_back(std::make_unique<IntegrityCheckBypass>());
m_mods.emplace_back(IntegrityCheckBypass::get_shared_instance());
#endif

#ifndef BAREBONES
Expand Down
2 changes: 2 additions & 0 deletions src/REFramework.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -558,11 +558,13 @@ REFramework::REFramework(HMODULE reframework_module)

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

const auto config_path = get_persistent_dir(REFrameworkConfig::REFRAMEWORK_CONFIG_NAME.data()).string();
if (fs::exists(utility::widen(config_path))) {
utility::Config cfg{ config_path };
loader->on_config_load(cfg);
integrity_bypass->on_config_load(cfg);
}

if (loader->is_enabled()) {
Expand Down
318 changes: 318 additions & 0 deletions src/mods/IntegrityCheckBypass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ struct IntegrityCheckPattern {
uint32_t offset{};
};

std::shared_ptr<IntegrityCheckBypass> s_integrity_check_bypass_instance{nullptr};

std::shared_ptr<IntegrityCheckBypass>& IntegrityCheckBypass::get_shared_instance() {
if (!s_integrity_check_bypass_instance) {
s_integrity_check_bypass_instance = std::make_unique<IntegrityCheckBypass>();
}
return s_integrity_check_bypass_instance;
}

std::optional<std::string> IntegrityCheckBypass::on_initialize() {
// Patterns for assigning or accessing of the integrity check boolean (RE3)
// and for jumping past the integrity checks (RE8)
Expand Down Expand Up @@ -917,6 +926,8 @@ void IntegrityCheckBypass::restore_unencrypted_paks() {
if (pak_load_check_start) {
spdlog::info("[IntegrityCheckBypass]: Found pak_load_check_function @ 0x{:X}, hook!", (uintptr_t)*pak_load_check_start);
s_pak_load_check_function_hook = safetyhook::create_mid((void*)*pak_load_check_start, &IntegrityCheckBypass::pak_load_check_function);

find_try_hook_via_file_load_win32_create_file(*pak_load_check_start);
}

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");
Expand Down Expand Up @@ -1041,6 +1052,12 @@ int IntegrityCheckBypass::scan_patch_files_count() {
highest_patch_num = 0;
}

auto integrity_shared_instance = IntegrityCheckBypass::get_shared_instance();
auto other_custom_paks_count = integrity_shared_instance->cache_and_count_custom_pak_in_directory();

s_base_directory_patch_count = highest_patch_num;
highest_patch_num += other_custom_paks_count;

s_patch_count_checked = true;
s_patch_count = highest_patch_num;

Expand Down Expand Up @@ -1534,3 +1551,304 @@ void* IntegrityCheckBypass::rtl_exit_user_process_hook(uint32_t code) {
TerminateProcess(GetCurrentProcess(), code);
return nullptr;
}

#pragma region Custom PAK directory loading

#define ENABLE_PAK_DIRECTORY_LOAD (TDB_VER >= 81)

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) {
if (ctx.instrux.Category == ND_CAT_CALL) {
auto displacement_opt = utility::resolve_displacement(ctx.addr);
if (displacement_opt && *(uintptr_t*)(*displacement_opt) == target_search_func) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ND_CAT_CALL isn't guaranteed to always be indirect CALL instructions like you're looking for here. Use an inner if statement looking for if (ctx.instrux.Instruction == ND_INS_CALLNI)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted that, thanks!

spdlog::info("[IntegrityCheckBypass]: Found stream open's call to CreateFileW at 0x{:X}, hooking it!", ctx.addr);
before_create_file_ptrs.push_back(ctx.addr);
}

return utility::ExhaustionResult::STEP_OVER;
}

return utility::ExhaustionResult::CONTINUE;
}

void IntegrityCheckBypass::find_try_hook_via_file_load_win32_create_file(uintptr_t pak_load_func_addr) {
#if ENABLE_PAK_DIRECTORY_LOAD
// Find the first call instruction, thats our opening PAK file function
const int INSTRUCTION_SEARCH_COUNT = 60;

uint8_t *open_stream_func_addr = 0;
uint8_t *search_current = (uint8_t*)pak_load_func_addr;

for (int i = 0; i < INSTRUCTION_SEARCH_COUNT; i++) {
auto instr = utility::decode_one(search_current);
if (!instr) {
continue;
}

if (instr->Instruction == ND_INS_CALLNR) {
if (auto resolved_opt = utility::resolve_displacement((uintptr_t)search_current)) {
open_stream_func_addr = (uint8_t*)*resolved_opt;
break;
}
}

search_current += instr->Length;
}

if (open_stream_func_addr == nullptr) {
spdlog::error("[IntegrityCheckBypass]: Could not find call to stream open function!");
return;
}

uintptr_t target_search_func = (uintptr_t)&CreateFileW;
const std::size_t exhaustive_decode_max = 12000;

std::vector<uintptr_t> before_create_file_ptrs{};

spdlog::info("[IntegrityCheckBypass]: Exhaustively decoding from 0x{:X} to find calls to CreateFileW...", (uintptr_t)open_stream_func_addr);

utility::exhaustive_decode(open_stream_func_addr, exhaustive_decode_max, [target_search_func, &before_create_file_ptrs](utility::ExhaustionContext &ctx) {
return do_exhaustion_scan_create_file_refs(ctx, target_search_func, before_create_file_ptrs);
});

for (auto ptr : before_create_file_ptrs) {
auto hook = safetyhook::create_mid((void*)ptr, &IntegrityCheckBypass::via_file_prepare_to_create_file_w_hook_wrappper);
if (hook) {
spdlog::info("[IntegrityCheckBypass]: Successfully hooked instruction before CreateFileW at 0x{:X}!", ptr);
s_before_create_file_w_hooks.push_back(std::move(hook));
} else {
spdlog::error("[IntegrityCheckBypass]: Failed to hook instruction before CreateFileW at 0x{:X}!", ptr);
}
}

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";
auto direct_storage_open_pak_func_addr = utility::scan(utility::get_executable(), direct_storage_open_pak_pattern);

if (!direct_storage_open_pak_func_addr) {
spdlog::error("[IntegrityCheckBypass]: Could not find DirectStorage pak open block!");
return;
}

// Find the first CALLNI instruction, which is the call to open the pak file stream
uint8_t *direct_storage_before_open_pak_call = nullptr;
search_current = (uint8_t*)*direct_storage_open_pak_func_addr;

const int DIRECT_STORAGE_OPEN_PAK_CALL_SEARCH_COUNT = 25;

for (int i = 0; i < DIRECT_STORAGE_OPEN_PAK_CALL_SEARCH_COUNT; i++) {
auto instr = utility::decode_one(search_current);
if (!instr) {
continue;
}

if (instr->Instruction == ND_INS_CALLNI) {
direct_storage_before_open_pak_call = search_current;
break;
}

search_current += instr->Length;
}

if (direct_storage_before_open_pak_call == nullptr) {
spdlog::error("[IntegrityCheckBypass]: Could not find call to open pak file stream in DirectStorage pak open block!");
return;
}

s_directstorage_open_pak_hook = safetyhook::create_mid((void*)direct_storage_before_open_pak_call, &IntegrityCheckBypass::directstorage_open_pak_hook_wrappper);
spdlog::info("[IntegrityCheckBypass]: Hooked DirectStorage pak open function at 0x{:X}!", (uintptr_t)direct_storage_before_open_pak_call);
#else
spdlog::info("[IntegrityCheckBypass]: Custom pak directory loading is not supported for TDB version {}", TDB_VER);
#endif
}

int IntegrityCheckBypass::cache_and_count_custom_pak_in_directory() {
#if !ENABLE_PAK_DIRECTORY_LOAD
return 0;
#else
if (!m_load_pak_directory || !m_load_pak_directory->value()) {
spdlog::info("[IntegrityCheckBypass]: Pak directory loading is disabled, skipping it.");
return 0;
}

if (m_custom_pak_in_directory_paths_cached) {
return static_cast<int>(m_custom_pak_in_directory_paths.size());
}

spdlog::info("[IntegrityCheckBypass]: Caching custom pak paths in executable directory...");
m_custom_pak_in_directory_paths_cached = true;

auto exe_module = utility::get_executable();
auto exe_path = utility::get_module_pathw(exe_module);
auto exe_dir = std::filesystem::path(*exe_path).parent_path();
auto pak_dir_fs_path = exe_dir / CUSTOM_PAK_DIRECTORY_PATH;

if (!std::filesystem::exists(pak_dir_fs_path)) {
spdlog::warn("[IntegrityCheckBypass]: Custom pak directory does not exist at path: {}", utility::narrow(pak_dir_fs_path.wstring()));
return 0;
}

// Iterate through the directory (recursively), and cache paths of all .pak files
for (const auto& entry : std::filesystem::recursive_directory_iterator(pak_dir_fs_path)) {
if (entry.is_regular_file() && entry.path().extension() == PAK_EXTENSION_NAME) {
m_custom_pak_in_directory_paths.push_back(entry.path());
spdlog::info("[IntegrityCheckBypass]: Cached custom pak with name: {} at path: {}", entry.path().filename().string(), utility::narrow(entry.path().wstring()));
}
}

spdlog::info("[IntegrityCheckBypass]: Finished caching custom pak paths. Total count: {}", m_custom_pak_in_directory_paths.size());
return static_cast<int>(m_custom_pak_in_directory_paths.size());
#endif
}

std::optional<int> IntegrityCheckBypass::extract_patch_num_from_path(std::wstring &path) {
std::wsmatch match;
if (std::regex_match(path, match, m_sub_patch_scan_regex)) {
try {
const int patch_num = std::stoi(match[1].str());
return patch_num;
} catch (const std::exception& e) {
return std::nullopt;
}
}
return std::nullopt;
}

void IntegrityCheckBypass::via_file_prepare_to_create_file_w_hook_wrappper(safetyhook::Context& context) {
auto instance = IntegrityCheckBypass::get_shared_instance();
if (instance) {
instance->via_file_prepare_to_create_file_w_hook(context);
} else {
spdlog::error("[IntegrityCheckBypass]: Shared instance is null in via_file_prepare_to_create_file_w_hook_wrapper!");
}
}

void IntegrityCheckBypass::directstorage_open_pak_hook_wrappper(safetyhook::Context& context) {
auto instance = IntegrityCheckBypass::get_shared_instance();
if (instance) {
instance->directstorage_open_pak_hook(context);
} else {
spdlog::error("[IntegrityCheckBypass]: Shared instance is null in directstorage_open_pak_hook_wrapper!");
}
}

template <typename T>
T get_register_value(safetyhook::Context& context, int reg) {
switch (reg) {
case NDR_RAX: return (T)context.rax;
case NDR_RCX: return (T)context.rcx;
case NDR_RDX: return (T)context.rdx;
case NDR_RBX: return (T)context.rbx;
case NDR_RSP: return (T)context.rsp;
case NDR_RBP: return (T)context.rbp;
case NDR_RSI: return (T)context.rsi;
case NDR_RDI: return (T)context.rdi;
case NDR_R8: return (T)context.r8;
case NDR_R9: return (T)context.r9;
case NDR_R10: return (T)context.r10;
case NDR_R11: return (T)context.r11;
case NDR_R12: return (T)context.r12;
case NDR_R13: return (T)context.r13;
case NDR_R14: return (T)context.r14;
case NDR_R15: return (T)context.r15;
default: return (T)0;
}
}

template <typename T>
void set_register_value(safetyhook::Context& context, int reg, T value) {
switch (reg) {
case NDR_RAX: context.rax = (uint64_t)value; break;
case NDR_RCX: context.rcx = (uint64_t)value; break;
case NDR_RDX: context.rdx = (uint64_t)value; break;
case NDR_RBX: context.rbx = (uint64_t)value; break;
case NDR_RSP: context.rsp = (uint64_t)value; break;
case NDR_RBP: context.rbp = (uint64_t)value; break;
case NDR_RSI: context.rsi = (uint64_t)value; break;
case NDR_RDI: context.rdi = (uint64_t)value; break;
case NDR_R8: context.r8 = (uint64_t)value; break;
case NDR_R9: context.r9 = (uint64_t)value; break;
case NDR_R10: context.r10 = (uint64_t)value; break;
case NDR_R11: context.r11 = (uint64_t)value; break;
case NDR_R12: context.r12 = (uint64_t)value; break;
case NDR_R13: context.r13 = (uint64_t)value; break;
case NDR_R14: context.r14 = (uint64_t)value; break;
case NDR_R15: context.r15 = (uint64_t)value; break;
}
}

void IntegrityCheckBypass::correct_pak_load_path(safetyhook::Context& context, int register_index) {
if (!m_load_pak_directory || !m_load_pak_directory->value() || m_custom_pak_in_directory_paths.empty()) {
return;
}

auto path_ptr = get_register_value<wchar_t*>(context, register_index);
if (path_ptr != nullptr) {
std::wstring_view path_view(path_ptr);
if (path_view.ends_with(PAK_EXTENSION_NAME_W)) {
std::wstring filename_copy = std::filesystem::path(path_view).filename().wstring();
auto patch_num_opt = extract_patch_num_from_path(filename_copy);

if (patch_num_opt) {
int patch_num = *patch_num_opt;
if (patch_num > s_base_directory_patch_count) {
auto custom_directory_pak_index = patch_num - s_base_directory_patch_count - 1;
if (custom_directory_pak_index < m_custom_pak_in_directory_paths.size()) {
auto &pak_path = m_custom_pak_in_directory_paths[custom_directory_pak_index];
spdlog::info("[IntegrityCheckBypass]: Redirecting load of {} to custom pak at path: {}", utility::narrow(filename_copy), utility::narrow(pak_path));

set_register_value(context, register_index, pak_path.c_str());
} else {
spdlog::error("[IntegrityCheckBypass]: Patch number {} is out of range for PAK directory's PAK! (index {})", patch_num, custom_directory_pak_index);
}
}
}
}
}
}

void IntegrityCheckBypass::directstorage_open_pak_hook(safetyhook::Context& context) {
correct_pak_load_path(context, NDR_RDX);
}

void IntegrityCheckBypass::via_file_prepare_to_create_file_w_hook(safetyhook::Context& context) {
correct_pak_load_path(context, NDR_RCX);
}

#pragma endregion

void IntegrityCheckBypass::on_config_load(const utility::Config& cfg) {
for (IModValue& option : m_options) {
option.config_load(cfg);
}
}

void IntegrityCheckBypass::on_config_save(utility::Config& cfg) {
for (IModValue& option : m_options) {
option.config_save(cfg);
}
}

void IntegrityCheckBypass::on_draw_ui() {
#if ENABLE_PAK_DIRECTORY_LOAD
if (!ImGui::CollapsingHeader("PAK Directory Loading")) {
return;
}

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);
ImGui::Text("Restart the game to apply changes.");

auto changed = false;
changed |= m_load_pak_directory->draw("Enable");

if (changed) {
g_framework->request_save_config();
}

if (ImGui::TreeNode("List of custom PAKs loaded:")) {
for (const auto& pak_path : m_custom_pak_in_directory_paths) {
auto pak_utf8 = utility::narrow(pak_path);
ImGui::BulletText("%s", pak_utf8.c_str());
}
ImGui::TreePop();
}
#endif
}
Loading