Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ec16ccd80c | ||
|
d42d4fb6cd | ||
|
ca2a3c3eb6 | ||
|
2e392bb7a3 | ||
|
a51fa416cf | ||
|
f8eb92fff4 | ||
|
45d1617d22 | ||
|
5cdc2dc100 | ||
|
df12711248 | ||
|
a5e6c257c0 | ||
|
931d5ac295 | ||
|
8472b1958a | ||
|
027cabb071 | ||
|
1bc60390a5 | ||
|
ba55395e99 | ||
|
1c1a543a9c | ||
|
820aa6788e | ||
|
24a8c68abf |
12
README.md
12
README.md
@ -1,7 +1,7 @@
|
|||||||

|

|
||||||
[](https://github.com/h1-mod/h1-mod/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
[](https://github.com/auroramod/h1-mod/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
||||||
[](https://github.com/h1-mod/h1-mod/actions)
|
[](https://github.com/auroramod/h1-mod/actions)
|
||||||
[](https://ci.appveyor.com/project/h1-mod/h1-mod)
|
[](https://ci.appveyor.com/project/auroramod/h1-mod)
|
||||||
[](https://discord.gg/RzzXu5EVnh)
|
[](https://discord.gg/RzzXu5EVnh)
|
||||||
|
|
||||||
# H1-Mod
|
# H1-Mod
|
||||||
@ -27,9 +27,9 @@ NOTE: You must legally own Call of Duty®: Modern Warfare Remastered to run this
|
|||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- [S1x](https://github.com/XLabsProject/s1x-client) - codebase and research (predecessor of MWR)
|
- [s1x-client](https://github.com/HeartbeatingForCenturies/s1x-client) - codebase and research (predecessor of MWR)
|
||||||
- [h2-mod](https://github.com/fedddddd/h2-mod) - research (successor of MWR)
|
- [h2-mod](https://github.com/fedddddd/h2-mod) - research (successor of MWR)
|
||||||
- [momo5502](https://github.com/momo5502) - Arxan/Steam research, developer of [XLabsProject](https://github.com/XLabsProject)
|
- [momo5502](https://github.com/momo5502) - Arxan/Steam research, former lead developer of [XLabsProject](https://github.com/XLabsProject)
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ balance_teams_stub()
|
|||||||
{
|
{
|
||||||
handle_lowest_score_player(allied_players, "axis");
|
handle_lowest_score_player(allied_players, "axis");
|
||||||
}
|
}
|
||||||
else if (is_team_bigger_than(axis_players, team))
|
else if (is_team_bigger_than(axis_players, allied_players))
|
||||||
{
|
{
|
||||||
handle_lowest_score_player(axis_players, "allies");
|
handle_lowest_score_player(axis_players, "allies");
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ get_valid_team_array(team)
|
|||||||
if (isdefined(players[i].pers["team"]) && players[i].pers["team"] == team)
|
if (isdefined(players[i].pers["team"]) && players[i].pers["team"] == team)
|
||||||
team_array[team_array.size] = players[i];
|
team_array[team_array.size] = players[i];
|
||||||
}
|
}
|
||||||
return team_arary;
|
return team_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_team_bigger_than(team_one, team_two)
|
is_team_bigger_than(team_one, team_two)
|
||||||
@ -56,13 +56,13 @@ handle_lowest_score_player(team, new_team)
|
|||||||
// move the player that has the lowest score (highest teamTime value)
|
// move the player that has the lowest score (highest teamTime value)
|
||||||
for (i = 0; i < team.size; i++)
|
for (i = 0; i < team.size; i++)
|
||||||
{
|
{
|
||||||
if (isdefined(team[j].dont_auto_balance))
|
if (isdefined(team[i].dont_auto_balance))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!isdefined(lowest_score_player))
|
if (!isdefined(lowest_score_player))
|
||||||
lowest_score_player = team[j];
|
lowest_score_player = team[i];
|
||||||
else if (team[j].pers["score"] < lowest_score_player.pers["score"])
|
else if (team[i].pers["score"] < lowest_score_player.pers["score"])
|
||||||
lowest_score_player = team[j];
|
lowest_score_player = team[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
lowest_score_player set_team(new_team);
|
lowest_score_player set_team(new_team);
|
||||||
|
@ -3,10 +3,23 @@ local ending_reasons = {"MP_DRAW", "LUA_MENU_REPORT_DRAW", "MP_ROUND_WIN", "MP_R
|
|||||||
"LUA_MENU_REPORT_DEFEAT", "MP_HALFTIME", "MP_OVERTIME", "MP_ROUNDEND", "MP_INTERMISSION",
|
"LUA_MENU_REPORT_DEFEAT", "MP_HALFTIME", "MP_OVERTIME", "MP_ROUNDEND", "MP_INTERMISSION",
|
||||||
"MP_SWITCHING_SIDES", "MP_MATCH_BONUS_IS", "MP_MATCH_TIE", "MP_GAME_END", "SPLASHES_BLANK"}
|
"MP_SWITCHING_SIDES", "MP_MATCH_BONUS_IS", "MP_MATCH_TIE", "MP_GAME_END", "SPLASHES_BLANK"}
|
||||||
|
|
||||||
|
-- score gamemodes
|
||||||
|
local score_var_gamemodes = {"conf", "war", "hp", "koth", "dom", "sab"}
|
||||||
|
|
||||||
local function starts_with(str, start)
|
local function starts_with(str, start)
|
||||||
return str:sub(1, #start) == start
|
return str:sub(1, #start) == start
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function has_value(tab, val)
|
||||||
|
for index, value in ipairs(tab) do
|
||||||
|
if value == val then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
local player_old_score = 0
|
local player_old_score = 0
|
||||||
|
|
||||||
local scoreboard_orig = LUI.MenuBuilder.m_types_build["scoreboard"]
|
local scoreboard_orig = LUI.MenuBuilder.m_types_build["scoreboard"]
|
||||||
@ -28,7 +41,7 @@ local scoreboard = function(unk1, unk2)
|
|||||||
local unlocalized_string = ending_reasons[Game.GetOmnvar("ui_round_end_title")]
|
local unlocalized_string = ending_reasons[Game.GetOmnvar("ui_round_end_title")]
|
||||||
local is_round_based = starts_with(unlocalized_string, "MP_ROUND") or IsGameTypeRoundBased(gamemode)
|
local is_round_based = starts_with(unlocalized_string, "MP_ROUND") or IsGameTypeRoundBased(gamemode)
|
||||||
|
|
||||||
if is_round_based or gamemode == "conf" or gamemode == "war" then
|
if is_round_based or has_value(score_var_gamemodes, gamemode) then
|
||||||
player_score = player_stats.score
|
player_score = player_stats.score
|
||||||
else
|
else
|
||||||
player_score = player_stats.extrascore0
|
player_score = player_stats.extrascore0
|
||||||
|
@ -18,7 +18,7 @@ local columns = {{
|
|||||||
text = "@MENU_TYPE1",
|
text = "@MENU_TYPE1",
|
||||||
dataindex = 3
|
dataindex = 3
|
||||||
}, {
|
}, {
|
||||||
offset = 780,
|
offset = 790,
|
||||||
text = "@EXE_SV_INFO_MOD",
|
text = "@EXE_SV_INFO_MOD",
|
||||||
dataindex = 6
|
dataindex = 6
|
||||||
}, {
|
}, {
|
||||||
|
2
deps/asmjit
vendored
2
deps/asmjit
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 7c10a14d347879f889c6d11a9398f1d453acc690
|
Subproject commit 118ae6ced160f68dd142a0d758aa7efb4356196a
|
2
deps/curl
vendored
2
deps/curl
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 102de7aa8d5bfc6ed5fe85e89c7b943d0c186f03
|
Subproject commit 3378d2bd0931433999ef4c0ca95040c394146484
|
2
deps/sol2
vendored
2
deps/sol2
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 9c882a28fdb6f4ad79a53a4191b43ce48a661175
|
Subproject commit e8e122e9ce46f4f1c0b04003d8b703fe1b89755a
|
@ -1,151 +0,0 @@
|
|||||||
#include <std_include.hpp>
|
|
||||||
#include "loader/component_loader.hpp"
|
|
||||||
|
|
||||||
#include "arxan.hpp"
|
|
||||||
#include "scheduler.hpp"
|
|
||||||
|
|
||||||
#include "game/game.hpp"
|
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
|
||||||
|
|
||||||
namespace arxan
|
|
||||||
{
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
utils::hook::detour nt_close_hook;
|
|
||||||
utils::hook::detour nt_query_information_process_hook;
|
|
||||||
|
|
||||||
NTSTATUS WINAPI nt_query_information_process_stub(const HANDLE handle, const PROCESSINFOCLASS info_class,
|
|
||||||
const PVOID info,
|
|
||||||
const ULONG info_length, const PULONG ret_length)
|
|
||||||
{
|
|
||||||
auto* orig = static_cast<decltype(NtQueryInformationProcess)*>(nt_query_information_process_hook.
|
|
||||||
get_original());
|
|
||||||
const auto status = orig(handle, info_class, info, info_length, ret_length);
|
|
||||||
|
|
||||||
if (NT_SUCCESS(status))
|
|
||||||
{
|
|
||||||
if (info_class == ProcessBasicInformation)
|
|
||||||
{
|
|
||||||
static DWORD explorer_pid = 0;
|
|
||||||
if (!explorer_pid)
|
|
||||||
{
|
|
||||||
auto* const shell_window = GetShellWindow();
|
|
||||||
GetWindowThreadProcessId(shell_window, &explorer_pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
static_cast<PPROCESS_BASIC_INFORMATION>(info)->Reserved3 = PVOID(DWORD64(explorer_pid));
|
|
||||||
}
|
|
||||||
else if (info_class == 30) // ProcessDebugObjectHandle
|
|
||||||
{
|
|
||||||
*static_cast<HANDLE*>(info) = nullptr;
|
|
||||||
|
|
||||||
return 0xC0000353;
|
|
||||||
}
|
|
||||||
else if (info_class == 7) // ProcessDebugPort
|
|
||||||
{
|
|
||||||
*static_cast<HANDLE*>(info) = nullptr;
|
|
||||||
}
|
|
||||||
else if (info_class == 31)
|
|
||||||
{
|
|
||||||
*static_cast<ULONG*>(info) = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess
|
|
||||||
}
|
|
||||||
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
NTSTATUS NTAPI nt_close_stub(const HANDLE handle)
|
|
||||||
{
|
|
||||||
char info[16];
|
|
||||||
if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS(4), &info, 2, nullptr) >= 0 && size_t(handle) != 0x12345)
|
|
||||||
{
|
|
||||||
auto* orig = static_cast<decltype(NtClose)*>(nt_close_hook.get_original());
|
|
||||||
return orig(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return STATUS_INVALID_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info)
|
|
||||||
{
|
|
||||||
if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE)
|
|
||||||
{
|
|
||||||
return EXCEPTION_CONTINUE_EXECUTION;
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hide_being_debugged()
|
|
||||||
{
|
|
||||||
auto* const peb = PPEB(__readgsqword(0x60));
|
|
||||||
peb->BeingDebugged = false;
|
|
||||||
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove_hardware_breakpoints()
|
|
||||||
{
|
|
||||||
CONTEXT context;
|
|
||||||
ZeroMemory(&context, sizeof(context));
|
|
||||||
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
|
||||||
|
|
||||||
auto* const thread = GetCurrentThread();
|
|
||||||
GetThreadContext(thread, &context);
|
|
||||||
|
|
||||||
context.Dr0 = 0;
|
|
||||||
context.Dr1 = 0;
|
|
||||||
context.Dr2 = 0;
|
|
||||||
context.Dr3 = 0;
|
|
||||||
context.Dr6 = 0;
|
|
||||||
context.Dr7 = 0;
|
|
||||||
|
|
||||||
SetThreadContext(thread, &context);
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOL WINAPI set_thread_context_stub(const HANDLE thread, CONTEXT* context)
|
|
||||||
{
|
|
||||||
return SetThreadContext(thread, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class component final : public component_interface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void* load_import(const std::string& library, const std::string& function) override
|
|
||||||
{
|
|
||||||
if (function == "SetThreadContext")
|
|
||||||
{
|
|
||||||
//return set_thread_context_stub;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void post_load() override
|
|
||||||
{
|
|
||||||
hide_being_debugged();
|
|
||||||
scheduler::loop(hide_being_debugged, scheduler::pipeline::async);
|
|
||||||
|
|
||||||
const utils::nt::library ntdll("ntdll.dll");
|
|
||||||
nt_close_hook.create(ntdll.get_proc<void*>("NtClose"), nt_close_stub);
|
|
||||||
nt_query_information_process_hook.create(ntdll.get_proc<void*>("NtQueryInformationProcess"),
|
|
||||||
nt_query_information_process_stub);
|
|
||||||
// https://www.geoffchappell.com/studies/windows/win32/ntdll/api/index.htm
|
|
||||||
AddVectoredExceptionHandler(1, exception_filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
void post_unpack() override
|
|
||||||
{
|
|
||||||
// cba to implement sp, not sure if it's even needed
|
|
||||||
if (game::environment::is_sp())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_COMPONENT(arxan::component)
|
|
587
src/client/component/arxan/arxan.cpp
Normal file
587
src/client/component/arxan/arxan.cpp
Normal file
@ -0,0 +1,587 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
#include "component/game_module.hpp"
|
||||||
|
#include "component/scheduler.hpp"
|
||||||
|
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
#include <utils/string.hpp>
|
||||||
|
|
||||||
|
#include "integrity.hpp"
|
||||||
|
#include "breakpoints.hpp"
|
||||||
|
|
||||||
|
#define PRECOMPUTED_INTEGRITY_CHECKS
|
||||||
|
#define PRECOMPUTED_BREAKPOINTS
|
||||||
|
|
||||||
|
#define ProcessDebugPort 7
|
||||||
|
#define ProcessDebugObjectHandle 30
|
||||||
|
#define ProcessDebugFlags 31
|
||||||
|
|
||||||
|
namespace arxan
|
||||||
|
{
|
||||||
|
namespace integrity
|
||||||
|
{
|
||||||
|
const std::vector<std::pair<uint8_t*, size_t>>& get_text_sections()
|
||||||
|
{
|
||||||
|
static const std::vector<std::pair<uint8_t*, size_t>> text = []
|
||||||
|
{
|
||||||
|
std::vector<std::pair<uint8_t*, size_t>> texts{};
|
||||||
|
|
||||||
|
const utils::nt::library game{ game_module::get_game_module() };
|
||||||
|
for (const auto& section : game.get_section_headers())
|
||||||
|
{
|
||||||
|
if (section->Characteristics & IMAGE_SCN_MEM_EXECUTE)
|
||||||
|
{
|
||||||
|
texts.emplace_back(game.get_ptr() + section->VirtualAddress, section->Misc.VirtualSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return texts;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_in_texts(const uint64_t addr)
|
||||||
|
{
|
||||||
|
const auto& texts = get_text_sections();
|
||||||
|
for (const auto& text : texts)
|
||||||
|
{
|
||||||
|
const auto start = reinterpret_cast<ULONG_PTR>(text.first);
|
||||||
|
if (addr >= start && addr <= (start + text.second))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_in_texts(const void* addr)
|
||||||
|
{
|
||||||
|
return is_in_texts(reinterpret_cast<uint64_t>(addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct integrity_handler_context
|
||||||
|
{
|
||||||
|
uint32_t* computed_checksum;
|
||||||
|
uint32_t* original_checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool is_on_stack(uint8_t* stack_frame, const void* pointer)
|
||||||
|
{
|
||||||
|
const auto stack_value = reinterpret_cast<uint64_t>(stack_frame);
|
||||||
|
const auto pointer_value = reinterpret_cast<uint64_t>(pointer);
|
||||||
|
|
||||||
|
const auto diff = static_cast<int64_t>(stack_value - pointer_value);
|
||||||
|
return std::abs(diff) < 0x1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty trashy, but working, heuristic to search the integrity handler context
|
||||||
|
bool is_handler_context(uint8_t* stack_frame, const uint32_t computed_checksum, const uint32_t frame_offset)
|
||||||
|
{
|
||||||
|
const auto* potential_context = reinterpret_cast<integrity_handler_context*>(stack_frame + frame_offset);
|
||||||
|
return is_on_stack(stack_frame, potential_context->computed_checksum)
|
||||||
|
&& *potential_context->computed_checksum == computed_checksum
|
||||||
|
&& is_in_texts(potential_context->original_checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
integrity_handler_context* search_handler_context(uint8_t* stack_frame, const uint32_t computed_checksum)
|
||||||
|
{
|
||||||
|
for (uint32_t frame_offset = 0; frame_offset < 0x90; frame_offset += 8)
|
||||||
|
{
|
||||||
|
if (is_handler_context(stack_frame, computed_checksum, frame_offset))
|
||||||
|
{
|
||||||
|
return reinterpret_cast<integrity_handler_context*>(stack_frame + frame_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t adjust_integrity_checksum(const uint64_t return_address, uint8_t* stack_frame,
|
||||||
|
const uint32_t current_checksum)
|
||||||
|
{
|
||||||
|
[[maybe_unused]] const auto handler_address = return_address - 5;
|
||||||
|
const auto* context = search_handler_context(stack_frame, current_checksum);
|
||||||
|
|
||||||
|
if (!context)
|
||||||
|
{
|
||||||
|
OutputDebugStringA(utils::string::va("Unable to find frame offset for: %llX", return_address));
|
||||||
|
return current_checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto correct_checksum = *context->original_checksum;
|
||||||
|
*context->computed_checksum = correct_checksum;
|
||||||
|
|
||||||
|
if (current_checksum != correct_checksum)
|
||||||
|
{
|
||||||
|
#ifdef DEV_BUILD
|
||||||
|
OutputDebugStringA(utils::string::va("Adjusting checksum (%llX): %X -> %X", handler_address,
|
||||||
|
current_checksum, correct_checksum));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return correct_checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_intact_basic_block_integrity_check(void* address)
|
||||||
|
{
|
||||||
|
const auto game_address = reinterpret_cast<uint64_t>(address);
|
||||||
|
constexpr auto inst_len = 3;
|
||||||
|
|
||||||
|
const auto next_inst_addr = game_address + inst_len;
|
||||||
|
const auto next_inst = *reinterpret_cast<uint32_t*>(next_inst_addr);
|
||||||
|
|
||||||
|
if ((next_inst & 0xFF00FFFF) != 0xFF004583)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(utils::string::va("Unable to patch intact basic block: %llX", game_address));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto other_frame_offset = static_cast<uint8_t>(next_inst >> 16);
|
||||||
|
static const auto stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||||
|
{
|
||||||
|
a.push(rax);
|
||||||
|
|
||||||
|
a.mov(rax, qword_ptr(rsp, 8));
|
||||||
|
a.sub(rax, 2); // Skip the push we inserted
|
||||||
|
|
||||||
|
a.push(rax);
|
||||||
|
a.pushad64();
|
||||||
|
|
||||||
|
a.mov(r8, qword_ptr(rsp, 0x88));
|
||||||
|
a.mov(rcx, rax);
|
||||||
|
a.mov(rdx, rbp);
|
||||||
|
a.call_aligned(adjust_integrity_checksum);
|
||||||
|
|
||||||
|
a.mov(qword_ptr(rsp, 0x80), rax);
|
||||||
|
|
||||||
|
a.popad64();
|
||||||
|
a.pop(rax);
|
||||||
|
|
||||||
|
a.add(rsp, 8);
|
||||||
|
|
||||||
|
a.mov(dword_ptr(rdx, rcx, 4), eax);
|
||||||
|
|
||||||
|
a.pop(rax); // return addr
|
||||||
|
a.xchg(rax, qword_ptr(rsp)); // switch with push
|
||||||
|
|
||||||
|
a.add(dword_ptr(rbp, rax), 0xFFFFFFFF);
|
||||||
|
|
||||||
|
a.mov(rax, dword_ptr(rdx, rcx, 4)); // restore rax
|
||||||
|
|
||||||
|
a.ret();
|
||||||
|
});
|
||||||
|
|
||||||
|
// push other_frame_offset
|
||||||
|
utils::hook::set<uint16_t>(game_address, static_cast<uint16_t>(0x6A | (other_frame_offset << 8)));
|
||||||
|
utils::hook::call(game_address + 2, stub);
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_split_basic_block_integrity_check(void* address)
|
||||||
|
{
|
||||||
|
const auto game_address = reinterpret_cast<uint64_t>(address);
|
||||||
|
constexpr auto inst_len = 3;
|
||||||
|
|
||||||
|
const auto next_inst_addr = game_address + inst_len;
|
||||||
|
|
||||||
|
if (*reinterpret_cast<uint8_t*>(next_inst_addr) != 0xE9)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(utils::string::va("Unable to patch split basic block: %llX", game_address));
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto jump_target = utils::hook::extract<void*>(reinterpret_cast<void*>(next_inst_addr + 1));
|
||||||
|
const auto stub = utils::hook::assemble([jump_target](utils::hook::assembler& a)
|
||||||
|
{
|
||||||
|
a.push(rax);
|
||||||
|
|
||||||
|
a.mov(rax, qword_ptr(rsp, 8));
|
||||||
|
a.push(rax);
|
||||||
|
|
||||||
|
a.pushad64();
|
||||||
|
|
||||||
|
a.mov(r8, qword_ptr(rsp, 0x88));
|
||||||
|
a.mov(rcx, rax);
|
||||||
|
a.mov(rdx, rbp);
|
||||||
|
a.call_aligned(adjust_integrity_checksum);
|
||||||
|
|
||||||
|
a.mov(qword_ptr(rsp, 0x80), rax);
|
||||||
|
|
||||||
|
a.popad64();
|
||||||
|
a.pop(rax);
|
||||||
|
|
||||||
|
a.add(rsp, 8);
|
||||||
|
|
||||||
|
a.mov(dword_ptr(rdx, rcx, 4), eax);
|
||||||
|
|
||||||
|
a.add(rsp, 8);
|
||||||
|
|
||||||
|
a.jmp(jump_target);
|
||||||
|
});
|
||||||
|
|
||||||
|
utils::hook::call(game_address, stub);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PRECOMPUTED_INTEGRITY_CHECKS
|
||||||
|
void search_and_patch_integrity_checks_precomputed()
|
||||||
|
{
|
||||||
|
if (game::environment::is_sp())
|
||||||
|
{
|
||||||
|
for (const auto i : sp::intact_integrity_check_blocks)
|
||||||
|
{
|
||||||
|
patch_intact_basic_block_integrity_check(reinterpret_cast<void*>(i));
|
||||||
|
}
|
||||||
|
for (const auto i : sp::split_integrity_check_blocks)
|
||||||
|
{
|
||||||
|
patch_split_basic_block_integrity_check(reinterpret_cast<void*>(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const auto i : mp::intact_integrity_check_blocks)
|
||||||
|
{
|
||||||
|
patch_intact_basic_block_integrity_check(reinterpret_cast<void*>(i));
|
||||||
|
}
|
||||||
|
for (const auto i : mp::split_integrity_check_blocks)
|
||||||
|
{
|
||||||
|
patch_split_basic_block_integrity_check(reinterpret_cast<void*>(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void search_and_patch_integrity_checks()
|
||||||
|
{
|
||||||
|
#ifdef PRECOMPUTED_INTEGRITY_CHECKS
|
||||||
|
assert(game::base_address == 0x140000000);
|
||||||
|
search_and_patch_integrity_checks_precomputed();
|
||||||
|
#else
|
||||||
|
const auto intact_results = "89 04 8A 83 45 ? FF"_sig;
|
||||||
|
const auto split_results = "89 04 8A E9"_sig;
|
||||||
|
|
||||||
|
for (auto* i : intact_results)
|
||||||
|
{
|
||||||
|
patch_intact_basic_block_integrity_check(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* i : split_results)
|
||||||
|
{
|
||||||
|
patch_split_basic_block_integrity_check(i);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using namespace integrity;
|
||||||
|
|
||||||
|
namespace anti_debug
|
||||||
|
{
|
||||||
|
utils::hook::detour nt_close_hook;
|
||||||
|
utils::hook::detour nt_query_information_process_hook;
|
||||||
|
|
||||||
|
NTSTATUS WINAPI nt_query_information_process_stub(const HANDLE handle, const PROCESSINFOCLASS info_class,
|
||||||
|
const PVOID info,
|
||||||
|
const ULONG info_length, const PULONG ret_length)
|
||||||
|
{
|
||||||
|
auto* orig = static_cast<decltype(NtQueryInformationProcess)*>(nt_query_information_process_hook.get_original());
|
||||||
|
auto status = orig(handle, info_class, info, info_length, ret_length);
|
||||||
|
|
||||||
|
if (NT_SUCCESS(status))
|
||||||
|
{
|
||||||
|
if (info_class == ProcessDebugObjectHandle)
|
||||||
|
{
|
||||||
|
*static_cast<HANDLE*>(info) = nullptr;
|
||||||
|
return static_cast<LONG>(0xC0000353);
|
||||||
|
}
|
||||||
|
else if (info_class == ProcessDebugPort)
|
||||||
|
{
|
||||||
|
*static_cast<HANDLE*>(info) = nullptr;
|
||||||
|
}
|
||||||
|
else if (info_class == ProcessDebugFlags)
|
||||||
|
{
|
||||||
|
*static_cast<ULONG*>(info) = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
NTSTATUS NTAPI nt_close_stub(const HANDLE handle)
|
||||||
|
{
|
||||||
|
char info[16];
|
||||||
|
if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS(4), &info, 2, nullptr) >= 0 && size_t(handle) != 0x12345)
|
||||||
|
{
|
||||||
|
auto* orig = static_cast<decltype(NtClose)*>(nt_close_hook.get_original());
|
||||||
|
return orig(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return STATUS_INVALID_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hide_being_debugged()
|
||||||
|
{
|
||||||
|
auto* const peb = PPEB(__readgsqword(0x60));
|
||||||
|
peb->BeingDebugged = false;
|
||||||
|
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70; // NtGlobalFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_hardware_breakpoints()
|
||||||
|
{
|
||||||
|
CONTEXT context;
|
||||||
|
ZeroMemory(&context, sizeof(context));
|
||||||
|
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||||
|
|
||||||
|
auto* const thread = GetCurrentThread();
|
||||||
|
GetThreadContext(thread, &context);
|
||||||
|
|
||||||
|
context.Dr0 = 0;
|
||||||
|
context.Dr1 = 0;
|
||||||
|
context.Dr2 = 0;
|
||||||
|
context.Dr3 = 0;
|
||||||
|
context.Dr6 = 0;
|
||||||
|
context.Dr7 = 0;
|
||||||
|
|
||||||
|
SetThreadContext(thread, &context);
|
||||||
|
}
|
||||||
|
|
||||||
|
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info)
|
||||||
|
{
|
||||||
|
if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE)
|
||||||
|
{
|
||||||
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL WINAPI set_thread_context_stub(const HANDLE thread, CONTEXT* context)
|
||||||
|
{
|
||||||
|
if (context->ContextFlags == CONTEXT_DEBUG_REGISTERS)
|
||||||
|
{
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SetThreadContext(thread, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum dbg_funcs_e
|
||||||
|
{
|
||||||
|
DbgBreakPoint,
|
||||||
|
DbgUserBreakPoint,
|
||||||
|
DbgUiConnectToDbg,
|
||||||
|
DbgUiContinue,
|
||||||
|
DbgUiConvertStateChangeStructure,
|
||||||
|
DbgUiDebugActiveProcess,
|
||||||
|
DbgUiGetThreadDebugObject,
|
||||||
|
DbgUiIssueRemoteBreakin,
|
||||||
|
DbgUiRemoteBreakin,
|
||||||
|
DbgUiSetThreadDebugObject,
|
||||||
|
DbgUiStopDebugging,
|
||||||
|
DbgUiWaitStateChange,
|
||||||
|
DbgPrintReturnControlC,
|
||||||
|
DbgPrompt,
|
||||||
|
DBG_FUNCS_COUNT,
|
||||||
|
};
|
||||||
|
const char* dbg_funcs_names[] =
|
||||||
|
{
|
||||||
|
"DbgBreakPoint",
|
||||||
|
"DbgUserBreakPoint",
|
||||||
|
"DbgUiConnectToDbg",
|
||||||
|
"DbgUiContinue",
|
||||||
|
"DbgUiConvertStateChangeStructure",
|
||||||
|
"DbgUiDebugActiveProcess",
|
||||||
|
"DbgUiGetThreadDebugObject",
|
||||||
|
"DbgUiIssueRemoteBreakin",
|
||||||
|
"DbgUiRemoteBreakin",
|
||||||
|
"DbgUiSetThreadDebugObject",
|
||||||
|
"DbgUiStopDebugging",
|
||||||
|
"DbgUiWaitStateChange",
|
||||||
|
"DbgPrintReturnControlC",
|
||||||
|
"DbgPrompt",
|
||||||
|
};
|
||||||
|
struct dbg_func_bytes_s
|
||||||
|
{
|
||||||
|
std::uint8_t buffer[15];
|
||||||
|
};
|
||||||
|
dbg_func_bytes_s dbg_func_bytes[DBG_FUNCS_COUNT];
|
||||||
|
void* dbg_func_procs[DBG_FUNCS_COUNT]{};
|
||||||
|
|
||||||
|
void store_debug_functions()
|
||||||
|
{
|
||||||
|
const utils::nt::library ntdll("ntdll.dll");
|
||||||
|
|
||||||
|
for (auto i = 0; i < DBG_FUNCS_COUNT; i++)
|
||||||
|
{
|
||||||
|
dbg_func_procs[i] = ntdll.get_proc<void*>(dbg_funcs_names[i]);
|
||||||
|
memcpy(dbg_func_bytes[i].buffer, dbg_func_procs[i], sizeof(dbg_func_bytes[i].buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void restore_debug_functions()
|
||||||
|
{
|
||||||
|
for (auto i = 0; i < DBG_FUNCS_COUNT; i++)
|
||||||
|
{
|
||||||
|
utils::hook::copy(dbg_func_procs[i], dbg_func_bytes[i].buffer, sizeof(dbg_func_bytes[i].buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace breakpoints
|
||||||
|
{
|
||||||
|
std::unordered_map<PVOID, void*> handle_handler;
|
||||||
|
|
||||||
|
void fake_breakpoint_trigger(void* address, _CONTEXT* fake_context)
|
||||||
|
{
|
||||||
|
_EXCEPTION_POINTERS fake_info{};
|
||||||
|
_EXCEPTION_RECORD fake_record{};
|
||||||
|
fake_info.ExceptionRecord = &fake_record;
|
||||||
|
fake_info.ContextRecord = fake_context;
|
||||||
|
|
||||||
|
fake_record.ExceptionAddress = reinterpret_cast<void*>(reinterpret_cast<std::uint64_t>(address) + 3);
|
||||||
|
fake_record.ExceptionCode = EXCEPTION_BREAKPOINT;
|
||||||
|
|
||||||
|
for (auto handler : handle_handler)
|
||||||
|
{
|
||||||
|
if (handler.second)
|
||||||
|
{
|
||||||
|
auto result = utils::hook::invoke<LONG>(handler.second, &fake_info);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
memset(fake_context, 0, sizeof(_CONTEXT));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_int2d_trap(void* address)
|
||||||
|
{
|
||||||
|
const auto game_address = reinterpret_cast<std::uint64_t>(address);
|
||||||
|
|
||||||
|
const auto jump_target = utils::hook::extract<void*>(reinterpret_cast<void*>(game_address + 3));
|
||||||
|
|
||||||
|
_CONTEXT* fake_context = new _CONTEXT{};
|
||||||
|
const auto stub = utils::hook::assemble([address, jump_target, fake_context](utils::hook::assembler& a)
|
||||||
|
{
|
||||||
|
a.push(rcx);
|
||||||
|
a.mov(rcx, fake_context);
|
||||||
|
a.call_aligned(RtlCaptureContext);
|
||||||
|
a.pop(rcx);
|
||||||
|
|
||||||
|
a.pushad64();
|
||||||
|
a.mov(rcx, address);
|
||||||
|
a.mov(rdx, fake_context);
|
||||||
|
a.call_aligned(fake_breakpoint_trigger);
|
||||||
|
a.popad64();
|
||||||
|
|
||||||
|
a.jmp(jump_target);
|
||||||
|
});
|
||||||
|
|
||||||
|
utils::hook::nop(game_address, 7);
|
||||||
|
utils::hook::jump(game_address, stub, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PRECOMPUTED_BREAKPOINTS
|
||||||
|
void patch_breakpoints_precomputed()
|
||||||
|
{
|
||||||
|
for (const auto i : mp::int2d_breakpoint_addresses)
|
||||||
|
{
|
||||||
|
patch_int2d_trap(reinterpret_cast<void*>(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void patch_breakpoints()
|
||||||
|
{
|
||||||
|
static bool once = false;
|
||||||
|
if (once)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
once = true;
|
||||||
|
|
||||||
|
// sp has no breakpoints
|
||||||
|
if (game::environment::is_sp())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PRECOMPUTED_BREAKPOINTS
|
||||||
|
assert(game::base_address == 0x140000000);
|
||||||
|
patch_breakpoints_precomputed();
|
||||||
|
#else
|
||||||
|
const auto int2d_results = utils::hook::signature("CD 2D E9 ? ? ? ?", game_module::get_game_module()).process();
|
||||||
|
for (auto* i : int2d_results)
|
||||||
|
{
|
||||||
|
patch_int2d_trap(i);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
PVOID WINAPI add_vectored_exception_handler_stub(ULONG first, PVECTORED_EXCEPTION_HANDLER handler)
|
||||||
|
{
|
||||||
|
breakpoints::patch_breakpoints();
|
||||||
|
|
||||||
|
auto handle = AddVectoredExceptionHandler(first, handler);
|
||||||
|
handle_handler[handle] = handler;
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG WINAPI remove_vectored_exception_handler_stub(PVOID handle)
|
||||||
|
{
|
||||||
|
handle_handler[handle] = nullptr;
|
||||||
|
return RemoveVectoredExceptionHandler(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using namespace anti_debug;
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void* load_import(const std::string& library, const std::string& function) override
|
||||||
|
{
|
||||||
|
if (function == "SetThreadContext")
|
||||||
|
{
|
||||||
|
return set_thread_context_stub;
|
||||||
|
}
|
||||||
|
else if (function == "AddVectoredExceptionHandler")
|
||||||
|
{
|
||||||
|
return breakpoints::add_vectored_exception_handler_stub;
|
||||||
|
}
|
||||||
|
else if (function == "RemoveVectoredExceptionHandler")
|
||||||
|
{
|
||||||
|
return breakpoints::remove_vectored_exception_handler_stub;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_load() override
|
||||||
|
{
|
||||||
|
remove_hardware_breakpoints();
|
||||||
|
hide_being_debugged();
|
||||||
|
scheduler::loop(hide_being_debugged, scheduler::pipeline::async);
|
||||||
|
store_debug_functions();
|
||||||
|
|
||||||
|
const utils::nt::library ntdll("ntdll.dll");
|
||||||
|
nt_close_hook.create(ntdll.get_proc<void*>("NtClose"), nt_close_stub);
|
||||||
|
|
||||||
|
const auto nt_query_information_process = ntdll.get_proc<void*>("NtQueryInformationProcess");
|
||||||
|
nt_query_information_process_hook.create(nt_query_information_process, nt_query_information_process_stub);
|
||||||
|
nt_query_information_process_hook.move();
|
||||||
|
|
||||||
|
AddVectoredExceptionHandler(1, exception_filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
remove_hardware_breakpoints();
|
||||||
|
search_and_patch_integrity_checks();
|
||||||
|
restore_debug_functions();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COMPONENT(arxan::component)
|
165
src/client/component/arxan/breakpoints.hpp
Normal file
165
src/client/component/arxan/breakpoints.hpp
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace mp
|
||||||
|
{
|
||||||
|
constexpr uint64_t int2d_breakpoint_addresses[] =
|
||||||
|
{
|
||||||
|
0x1400837C9,
|
||||||
|
0x140088C28,
|
||||||
|
0x140089CB9,
|
||||||
|
0x1400922E9,
|
||||||
|
0x140119A39,
|
||||||
|
0x140199679,
|
||||||
|
0x1401CBB59,
|
||||||
|
0x140205DD9,
|
||||||
|
0x140244A39,
|
||||||
|
0x1402BCFC9,
|
||||||
|
0x140346C39,
|
||||||
|
0x14037E2A9,
|
||||||
|
0x1404EB459,
|
||||||
|
0x1404EB7C9,
|
||||||
|
0x1405FCD09,
|
||||||
|
0x140612AB3,
|
||||||
|
0x140622D2E,
|
||||||
|
0x1407D82DB,
|
||||||
|
0x1407DC18C,
|
||||||
|
0x140847869,
|
||||||
|
0x140867E53,
|
||||||
|
0x15143E836,
|
||||||
|
0x15144B18C,
|
||||||
|
0x15144BE27,
|
||||||
|
0x1515E11E0,
|
||||||
|
0x1515F18C9,
|
||||||
|
0x1515F2ADE,
|
||||||
|
0x15161E477,
|
||||||
|
0x15162189F,
|
||||||
|
0x151623FC5,
|
||||||
|
0x151637C0D,
|
||||||
|
0x151640170,
|
||||||
|
0x151641309,
|
||||||
|
0x1516514DA,
|
||||||
|
0x1516528BC,
|
||||||
|
0x151653351,
|
||||||
|
0x15165627D,
|
||||||
|
0x151656436,
|
||||||
|
0x151683F6C,
|
||||||
|
0x151686F24,
|
||||||
|
0x1516881C5,
|
||||||
|
0x151695BAD,
|
||||||
|
0x15169714D,
|
||||||
|
0x1516977E9,
|
||||||
|
0x1516B6978,
|
||||||
|
0x1516B933F,
|
||||||
|
0x1516D75C7,
|
||||||
|
0x1516D8B61,
|
||||||
|
0x1516DDA42,
|
||||||
|
0x1517064EC,
|
||||||
|
0x151706519,
|
||||||
|
0x151712445,
|
||||||
|
0x151735FFA,
|
||||||
|
0x151737872,
|
||||||
|
0x151738660,
|
||||||
|
0x151748404,
|
||||||
|
0x151748DB3,
|
||||||
|
0x15174D408,
|
||||||
|
0x15174F7D2,
|
||||||
|
0x151763BDE,
|
||||||
|
0x151778988,
|
||||||
|
0x1517886DB,
|
||||||
|
0x15178DDAB,
|
||||||
|
0x151793BB6,
|
||||||
|
0x15179DC17,
|
||||||
|
0x1517A6BC5,
|
||||||
|
0x1517ACBA3,
|
||||||
|
0x1517B3502,
|
||||||
|
0x1517BAE0E,
|
||||||
|
0x1517BB039,
|
||||||
|
0x1517BB049,
|
||||||
|
0x1517BC58E,
|
||||||
|
0x1517BC7A0,
|
||||||
|
0x1517BCF51,
|
||||||
|
0x1517BFF71,
|
||||||
|
0x1517C0761,
|
||||||
|
0x1517C33F1,
|
||||||
|
0x1517C44C4,
|
||||||
|
0x1517C75AE,
|
||||||
|
0x1517CDB3B,
|
||||||
|
0x1517CE48A,
|
||||||
|
0x1517D0559,
|
||||||
|
0x1517D8E0F,
|
||||||
|
0x1517D95C0,
|
||||||
|
0x1517DA7FF,
|
||||||
|
0x1517DD407,
|
||||||
|
0x1517E11D2,
|
||||||
|
0x1517E5C08,
|
||||||
|
0x1517ED03C,
|
||||||
|
0x1517EE9CC,
|
||||||
|
0x1517EFB56,
|
||||||
|
0x1517F31AC,
|
||||||
|
0x1517F73D1,
|
||||||
|
0x1517FB1CA,
|
||||||
|
0x1518050D4,
|
||||||
|
0x15180EFA2,
|
||||||
|
0x15181A3BE,
|
||||||
|
0x15181A5C4,
|
||||||
|
0x151823361,
|
||||||
|
0x1518233AC,
|
||||||
|
0x151862C5B,
|
||||||
|
0x151863913,
|
||||||
|
0x15188E396,
|
||||||
|
0x15188EA8D,
|
||||||
|
0x15189760A,
|
||||||
|
0x1518977B0,
|
||||||
|
0x1518B14C9,
|
||||||
|
0x1518B7227,
|
||||||
|
0x1518B73AF,
|
||||||
|
0x1518B84F9,
|
||||||
|
0x1518C562A,
|
||||||
|
0x1518EA89B,
|
||||||
|
0x1518EFC20,
|
||||||
|
0x1518F10A0,
|
||||||
|
0x1518F4D00,
|
||||||
|
0x1518FD8B3,
|
||||||
|
0x1519033DA,
|
||||||
|
0x151904C1E,
|
||||||
|
0x151908F69,
|
||||||
|
0x15190D0DB,
|
||||||
|
0x151988E27,
|
||||||
|
0x15198FDD5,
|
||||||
|
0x151994C76,
|
||||||
|
0x151995C6B,
|
||||||
|
0x1519A66F1,
|
||||||
|
0x1519A7438,
|
||||||
|
0x1519A9B02,
|
||||||
|
0x1519C4269,
|
||||||
|
0x1519C4C75,
|
||||||
|
0x1519EE7BB,
|
||||||
|
0x1519EFFA8,
|
||||||
|
0x1519F0368,
|
||||||
|
0x1519F286A,
|
||||||
|
0x151A04D7E,
|
||||||
|
0x151A35E78,
|
||||||
|
0x151A38F2F,
|
||||||
|
0x151A450E4,
|
||||||
|
0x151A76754,
|
||||||
|
0x151A89BD1,
|
||||||
|
0x151A89C0A,
|
||||||
|
0x151AAB0EA,
|
||||||
|
0x151AB3E09,
|
||||||
|
0x151ABB764,
|
||||||
|
0x151ABCC16,
|
||||||
|
0x151ABF3F0,
|
||||||
|
0x151AC0659,
|
||||||
|
0x151AC5C1B,
|
||||||
|
0x151AC8705,
|
||||||
|
0x151AC88E2,
|
||||||
|
0x151ACA788,
|
||||||
|
0x151ACED96,
|
||||||
|
0x151AD7F8A,
|
||||||
|
0x151ADA318,
|
||||||
|
0x151ADC9BE,
|
||||||
|
0x151AE1BAF,
|
||||||
|
};
|
||||||
|
}
|
92
src/client/component/arxan/integrity.hpp
Normal file
92
src/client/component/arxan/integrity.hpp
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace mp
|
||||||
|
{
|
||||||
|
constexpr uint64_t intact_integrity_check_blocks[] =
|
||||||
|
{
|
||||||
|
0x140000519,
|
||||||
|
0x1407DB02E,
|
||||||
|
0x140AFF08D,
|
||||||
|
0x15143E06E,
|
||||||
|
0x151440DC4,
|
||||||
|
0x151455F3B,
|
||||||
|
0x151489363,
|
||||||
|
0x1515F10AB,
|
||||||
|
0x1515F66D0,
|
||||||
|
0x15161DB84,
|
||||||
|
0x15161EAA8,
|
||||||
|
0x151622530,
|
||||||
|
0x151687D47,
|
||||||
|
0x151690107,
|
||||||
|
0x1516979D7,
|
||||||
|
0x1516D3ED0,
|
||||||
|
0x1516E2FC2,
|
||||||
|
0x15173F3E9,
|
||||||
|
0x15174B9FA,
|
||||||
|
0x15175EAE1,
|
||||||
|
0x15176E778,
|
||||||
|
0x151787948,
|
||||||
|
0x15179956D,
|
||||||
|
0x1517B36C8,
|
||||||
|
0x1517DE653,
|
||||||
|
0x1517E2CE0,
|
||||||
|
0x1517EEAE2,
|
||||||
|
0x1517F92E1,
|
||||||
|
0x1517FB858,
|
||||||
|
0x151809719,
|
||||||
|
0x151868985,
|
||||||
|
0x1518BB832,
|
||||||
|
0x1518EE362,
|
||||||
|
0x1518FE162,
|
||||||
|
0x1519024C3,
|
||||||
|
0x151908639,
|
||||||
|
0x151985713,
|
||||||
|
0x15199565C,
|
||||||
|
0x1519C6C5B,
|
||||||
|
0x1519EB05E,
|
||||||
|
0x1519F0EE1,
|
||||||
|
0x1519F4A25,
|
||||||
|
0x151A0503F,
|
||||||
|
0x151A45752,
|
||||||
|
0x151A7E83E,
|
||||||
|
0x151A87C67,
|
||||||
|
0x151AAF804,
|
||||||
|
0x151AB3E72,
|
||||||
|
0x151AC3482,
|
||||||
|
0x151AC5B0F,
|
||||||
|
0x151ACBE3C,
|
||||||
|
};
|
||||||
|
constexpr uint64_t split_integrity_check_blocks[] =
|
||||||
|
{
|
||||||
|
0x1514B9FC8,
|
||||||
|
0x15161D235,
|
||||||
|
0x1516DE9FF,
|
||||||
|
0x151754691,
|
||||||
|
0x151772B7F,
|
||||||
|
0x1517B3CA9,
|
||||||
|
0x1517DB0D0,
|
||||||
|
0x1517E65DF,
|
||||||
|
0x15181407C,
|
||||||
|
0x151832847,
|
||||||
|
0x15183B664,
|
||||||
|
0x15199ABAA,
|
||||||
|
0x1519AA163,
|
||||||
|
0x1519C5852,
|
||||||
|
0x1519C7B0E,
|
||||||
|
0x1519C7F63,
|
||||||
|
0x1519F3291,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
namespace sp
|
||||||
|
{
|
||||||
|
constexpr uint64_t intact_integrity_check_blocks[] =
|
||||||
|
{
|
||||||
|
0x15263308B,
|
||||||
|
};
|
||||||
|
constexpr uint64_t split_integrity_check_blocks[] =
|
||||||
|
{
|
||||||
|
0x152630C3F,
|
||||||
|
};
|
||||||
|
}
|
@ -37,13 +37,13 @@ namespace branding
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
const auto text = "h1-mod: " VERSION " (" __DATE__ " " __TIME__ ")";
|
const auto text = "h1-mod (Aurora): " VERSION " (" __DATE__ " " __TIME__ ")";
|
||||||
#else
|
#else
|
||||||
const auto text = "h1-mod: " VERSION;
|
const auto text = "h1-mod (Aurora): " VERSION;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const auto placement = game::ScrPlace_GetViewPlacement();
|
const auto placement = game::ScrPlace_GetViewPlacement();
|
||||||
float text_color[4] = {0.6f, 0.6f, 0.6f, 0.6f};
|
float text_color[4] = {0.860f, 0.459f, 0.925f, 0.800f};
|
||||||
|
|
||||||
game::rectDef_s rect{};
|
game::rectDef_s rect{};
|
||||||
rect.x = 0;
|
rect.x = 0;
|
||||||
@ -68,7 +68,7 @@ namespace branding
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduler::loop(draw_branding, scheduler::pipeline::renderer);
|
scheduler::loop(draw_branding, scheduler::renderer);
|
||||||
|
|
||||||
ui_get_formatted_build_number_hook.create(
|
ui_get_formatted_build_number_hook.create(
|
||||||
SELECT_VALUE(0x406EC0_b, 0x1DF300_b), ui_get_formatted_build_number_stub);
|
SELECT_VALUE(0x406EC0_b, 0x1DF300_b), ui_get_formatted_build_number_stub);
|
||||||
|
@ -596,6 +596,7 @@ namespace demonware
|
|||||||
utils::hook::nop(0x19BB67_b, 5); // LiveStorage_SendMatchDataComplete (crashes at the end of match)
|
utils::hook::nop(0x19BB67_b, 5); // LiveStorage_SendMatchDataComplete (crashes at the end of match)
|
||||||
utils::hook::nop(0x19BC3F_b, 5); // LiveStorage_GettingStoreConfigComplete probably (crashes randomly)
|
utils::hook::nop(0x19BC3F_b, 5); // LiveStorage_GettingStoreConfigComplete probably (crashes randomly)
|
||||||
utils::hook::nop(0x19BC48_b, 5); // similar to above (crashes in killcam)
|
utils::hook::nop(0x19BC48_b, 5); // similar to above (crashes in killcam)
|
||||||
|
utils::hook::nop(0x19BBA3_b, 5); // LiveStorage_LogComplete (crashes when prestiging, LiveStorage_LogPrestige?)
|
||||||
utils::hook::set<uint8_t>(0x1A3340_b, 0xC3); // Live_CheckForFullDisconnect
|
utils::hook::set<uint8_t>(0x1A3340_b, 0xC3); // Live_CheckForFullDisconnect
|
||||||
|
|
||||||
// Remove some while loop that freezes the rendering for a few secs while connecting
|
// Remove some while loop that freezes the rendering for a few secs while connecting
|
||||||
|
@ -88,7 +88,7 @@ namespace download
|
|||||||
}, scheduler::pipeline::lui);
|
}, scheduler::pipeline::lui);
|
||||||
}
|
}
|
||||||
|
|
||||||
console::debug("Download progress: %lli/%lli\n", progress, total);
|
//console::debug("Download progress: %lli/%lli\n", progress, total);
|
||||||
if (download_aborted())
|
if (download_aborted())
|
||||||
{
|
{
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -1084,12 +1084,87 @@ namespace fastfiles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace sp
|
||||||
|
{
|
||||||
|
constexpr unsigned int get_asset_type_size(const game::XAssetType type)
|
||||||
|
{
|
||||||
|
constexpr int asset_type_sizes[] =
|
||||||
|
{
|
||||||
|
96, 88, 128, 56, 40, 216,
|
||||||
|
56, 680, 592, 32, 32, 32,
|
||||||
|
32, 32, 2112, 1936, 104,
|
||||||
|
32, 24, 152, 152, 152, 16,
|
||||||
|
64, 640, 40, 16, 136, 24,
|
||||||
|
296, 176, 2864, 48, 0, 24,
|
||||||
|
200, 88, 16, 144, 3616, 56,
|
||||||
|
64, 16, 16, 0, 0, 0, 0, 24,
|
||||||
|
40, 24, 48, 40, 24, 16, 80,
|
||||||
|
128, 2256, 136, 32, 72,
|
||||||
|
24, 64, 88, 48, 32, 96, 152,
|
||||||
|
64, 32, 32,
|
||||||
|
};
|
||||||
|
|
||||||
|
return asset_type_sizes[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr unsigned int get_pool_type_size(const game::XAssetType type)
|
||||||
|
{
|
||||||
|
constexpr int asset_pool_sizes[] =
|
||||||
|
{
|
||||||
|
128, 1024, 16, 1, 128, 5000, 5248,
|
||||||
|
2560, 10624, 256, 49152, 12288, 12288,
|
||||||
|
72864, 512, 2750, 12000, 16000, 256,
|
||||||
|
64, 64, 64, 64, 8000, 1, 1, 1, 1,
|
||||||
|
1, 2, 1, 1, 32, 0, 128,
|
||||||
|
400, 0, 11500, 128, 360, 1, 2048,
|
||||||
|
4, 6, 0, 0, 0, 0, 1024,
|
||||||
|
768, 400, 128, 128, 24, 24, 24,
|
||||||
|
32, 128, 2, 0, 64, 384, 128,
|
||||||
|
1, 128, 64, 32, 32, 16, 32, 16,
|
||||||
|
};
|
||||||
|
|
||||||
|
return asset_pool_sizes[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <game::XAssetType Type, size_t Size>
|
||||||
|
char* reallocate_asset_pool()
|
||||||
|
{
|
||||||
|
constexpr auto element_size = get_asset_type_size(Type);
|
||||||
|
static char new_pool[element_size * Size] = {0};
|
||||||
|
static_assert(element_size != 0);
|
||||||
|
assert(element_size == game::DB_GetXAssetTypeSize(Type));
|
||||||
|
|
||||||
|
std::memmove(new_pool, game::g_assetPool[Type], game::g_poolSize[Type] * element_size);
|
||||||
|
|
||||||
|
game::g_assetPool[Type] = new_pool;
|
||||||
|
game::g_poolSize[Type] = Size;
|
||||||
|
|
||||||
|
return new_pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <game::XAssetType Type, size_t Multiplier>
|
||||||
|
char* reallocate_asset_pool_multiplier()
|
||||||
|
{
|
||||||
|
constexpr auto pool_size = get_pool_type_size(Type);
|
||||||
|
return reallocate_asset_pool<Type, pool_size* Multiplier>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reallocate_asset_pools()
|
||||||
|
{
|
||||||
|
reallocate_asset_pool_multiplier<game::ASSET_TYPE_LOCALIZE, 2>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void reallocate_asset_pools()
|
void reallocate_asset_pools()
|
||||||
{
|
{
|
||||||
if (!game::environment::is_sp())
|
if (!game::environment::is_sp())
|
||||||
{
|
{
|
||||||
mp::reallocate_asset_pools();
|
mp::reallocate_asset_pools();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sp::reallocate_asset_pools();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::hook::detour db_link_x_asset_entry_hook;
|
utils::hook::detour db_link_x_asset_entry_hook;
|
||||||
|
@ -16,9 +16,12 @@ namespace fps
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
utils::hook::detour sub_5D6810_hook;
|
utils::hook::detour sub_5D6810_hook;
|
||||||
|
utils::hook::detour com_frame_hook;
|
||||||
|
utils::hook::detour r_wait_end_time_hook;
|
||||||
|
|
||||||
game::dvar_t* cg_drawfps;
|
game::dvar_t* cg_drawfps = nullptr;
|
||||||
game::dvar_t* cg_drawping;
|
game::dvar_t* cg_drawping = nullptr;
|
||||||
|
game::dvar_t* com_wait_end_frame_mode = nullptr;
|
||||||
|
|
||||||
float fps_color_good[4] = {0.6f, 1.0f, 0.0f, 1.0f};
|
float fps_color_good[4] = {0.6f, 1.0f, 0.0f, 1.0f};
|
||||||
float fps_color_ok[4] = {1.0f, 0.7f, 0.3f, 1.0f};
|
float fps_color_ok[4] = {1.0f, 0.7f, 0.3f, 1.0f};
|
||||||
@ -128,17 +131,74 @@ namespace fps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
game::dvar_t* cg_draw_fps_register_stub()
|
|
||||||
{
|
|
||||||
cg_drawfps = dvars::register_int("cg_drawFps", 0, 0, 2, game::DVAR_FLAG_SAVED, "Draw frames per second");
|
|
||||||
return cg_drawfps;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sub_5D6810_stub()
|
void sub_5D6810_stub()
|
||||||
{
|
{
|
||||||
perf_update();
|
perf_update();
|
||||||
sub_5D6810_hook.invoke<void>();
|
sub_5D6810_hook.invoke<void>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool r_wait_end_frame_stub()
|
||||||
|
{
|
||||||
|
if (com_wait_end_frame_mode->current.integer > 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return r_wait_end_time_hook.invoke<bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void com_frame_stub()
|
||||||
|
{
|
||||||
|
const auto value = com_wait_end_frame_mode->current.integer;
|
||||||
|
if (value == 0)
|
||||||
|
{
|
||||||
|
return com_frame_hook.invoke<void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto start = std::chrono::high_resolution_clock::now();
|
||||||
|
com_frame_hook.invoke<void>();
|
||||||
|
|
||||||
|
auto max_fps = 0;
|
||||||
|
static const auto com_max_fps = game::Dvar_FindVar("com_maxfps");
|
||||||
|
|
||||||
|
if (game::environment::is_mp())
|
||||||
|
{
|
||||||
|
max_fps = utils::hook::invoke<int>(0x183490_b, com_max_fps);
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
max_fps = com_max_fps->current.integer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max_fps == 0)
|
||||||
|
{
|
||||||
|
max_fps = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constexpr auto nano_secs = std::chrono::duration_cast<std::chrono::nanoseconds>(1s);
|
||||||
|
const auto frame_time = nano_secs / max_fps;
|
||||||
|
|
||||||
|
if (value == 1)
|
||||||
|
{
|
||||||
|
const auto diff = (std::chrono::high_resolution_clock::now() - start);
|
||||||
|
if (diff > frame_time)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(frame_time - diff);
|
||||||
|
std::this_thread::sleep_for(ms);
|
||||||
|
}
|
||||||
|
else if (value == 2)
|
||||||
|
{
|
||||||
|
while (std::chrono::high_resolution_clock::now() - start < frame_time)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(0ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_fps()
|
int get_fps()
|
||||||
@ -204,6 +264,11 @@ namespace fps
|
|||||||
|
|
||||||
dvars::register_bool("cg_infobar_fps", false, game::DVAR_FLAG_SAVED, "Show server latency");
|
dvars::register_bool("cg_infobar_fps", false, game::DVAR_FLAG_SAVED, "Show server latency");
|
||||||
dvars::register_bool("cg_infobar_ping", false, game::DVAR_FLAG_SAVED, "Show FPS counter");
|
dvars::register_bool("cg_infobar_ping", false, game::DVAR_FLAG_SAVED, "Show FPS counter");
|
||||||
|
|
||||||
|
// Make fps capping accurate
|
||||||
|
com_wait_end_frame_mode = dvars::register_int("com_waitEndFrameMode", 0, 0, 2, game::DVAR_FLAG_SAVED, "Wait end frame mode (0 = default, 1 = sleep(n), 2 = loop sleep(0)");
|
||||||
|
r_wait_end_time_hook.create(SELECT_VALUE(0x3A7330_b, 0x1C2420_b), r_wait_end_frame_stub);
|
||||||
|
com_frame_hook.create(SELECT_VALUE(0x385210_b, 0x15A960_b), com_frame_stub);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ namespace gsc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console::debug("Loading custom gsc '%s.gsc'", real_name.data());
|
console::info("Loading custom gsc '%s.gsc'", real_name.data());
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -172,6 +172,8 @@ namespace gsc
|
|||||||
|
|
||||||
loaded_scripts[file_name] = script_file_ptr;
|
loaded_scripts[file_name] = script_file_ptr;
|
||||||
|
|
||||||
|
console::info("Loaded custom gsc '%s.gsc'", real_name.data());
|
||||||
|
|
||||||
return script_file_ptr;
|
return script_file_ptr;
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
@ -237,13 +239,13 @@ namespace gsc
|
|||||||
|
|
||||||
if (main_handle)
|
if (main_handle)
|
||||||
{
|
{
|
||||||
console::debug("Loaded '%s::main'\n", name.data());
|
console::info("Loaded '%s::main'\n", name.data());
|
||||||
main_handles[name] = main_handle;
|
main_handles[name] = main_handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (init_handle)
|
if (init_handle)
|
||||||
{
|
{
|
||||||
console::debug("Loaded '%s::init'\n", name.data());
|
console::info("Loaded '%s::init'\n", name.data());
|
||||||
init_handles[name] = init_handle;
|
init_handles[name] = init_handle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -369,7 +371,7 @@ namespace gsc
|
|||||||
{
|
{
|
||||||
for (auto& function_handle : main_handles)
|
for (auto& function_handle : main_handles)
|
||||||
{
|
{
|
||||||
console::debug("Executing '%s::main'\n", function_handle.first.data());
|
console::info("Executing '%s::main'\n", function_handle.first.data());
|
||||||
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
|
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -378,7 +380,7 @@ namespace gsc
|
|||||||
{
|
{
|
||||||
for (auto& function_handle : init_handles)
|
for (auto& function_handle : init_handles)
|
||||||
{
|
{
|
||||||
console::debug("Executing '%s::init'\n", function_handle.first.data());
|
console::info("Executing '%s::init'\n", function_handle.first.data());
|
||||||
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
|
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,125 @@
|
|||||||
#include <std_include.hpp>
|
#include <std_include.hpp>
|
||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
|
#include "console.hpp"
|
||||||
#include "materials.hpp"
|
#include "materials.hpp"
|
||||||
|
#include "motd.hpp"
|
||||||
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
|
|
||||||
#include <utils/http.hpp>
|
#include <utils/string.hpp>
|
||||||
|
|
||||||
|
#define WEBSITE_DATA_URL "https://raw.githubusercontent.com/h1-mod/website/publish/data"
|
||||||
|
|
||||||
namespace motd
|
namespace motd
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
bool waiting = true;
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
featured_content_t featured_content;
|
||||||
|
|
||||||
|
//std::optional<utils::http::result> motd_image_data;
|
||||||
|
|
||||||
|
void get_featured_content(const std::string& content_name, const int content_index)
|
||||||
|
{
|
||||||
|
const auto name = utils::string::va(content_name.data(), content_index);
|
||||||
|
const auto url = utils::string::va(WEBSITE_DATA_URL "/%s", name);
|
||||||
|
const auto result = utils::http::get_data(url, {}, {}, {});
|
||||||
|
if (result.has_value())
|
||||||
|
{
|
||||||
|
featured_content.insert_or_assign(name, result.value());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool handle_featured_content()
|
||||||
|
{
|
||||||
|
if (index > 3)
|
||||||
|
{
|
||||||
|
return scheduler::cond_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduler::once([&]()
|
||||||
|
{
|
||||||
|
if (index > 3)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto name = (index == 0 ? "motd.json" : "featured%d.json");
|
||||||
|
get_featured_content(name, index);
|
||||||
|
|
||||||
|
++index;
|
||||||
|
}, scheduler::async);
|
||||||
|
|
||||||
|
return scheduler::cond_continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
void download_motd_image()
|
||||||
|
{
|
||||||
|
motd_image_data = utils::http::get_data(WEBSITE_DATA_URL "/motd.png", {}, {}, {});
|
||||||
|
waiting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setup_motd_image()
|
||||||
|
{
|
||||||
|
if (waiting)
|
||||||
|
{
|
||||||
|
return scheduler::cond_continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!motd_image_data.has_value())
|
||||||
|
{
|
||||||
|
printf("motd image doesn't have a value\n");
|
||||||
|
return scheduler::cond_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("motd image loading...");
|
||||||
|
|
||||||
|
const auto material = materials::create_material("motd_image");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!materials::setup_material_image(material, motd_image_data.value().buffer))
|
||||||
|
{
|
||||||
|
materials::free_material(material);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("motd image loaded");
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
materials::free_material(material);
|
||||||
|
console::error("Failed to load MOTD image: %s\n", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return scheduler::cond_end;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
featured_content_t& get_featured_content()
|
||||||
|
{
|
||||||
|
return featured_content;
|
||||||
|
}
|
||||||
|
|
||||||
class component final : public component_interface
|
class component final : public component_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void post_load() override
|
void post_load() override
|
||||||
{
|
{
|
||||||
std::thread([]
|
if (!game::environment::is_mp())
|
||||||
{
|
|
||||||
//auto data = utils::http::get_data("https://h1.gg/data/motd.png");
|
|
||||||
//if (data.has_value())
|
|
||||||
//{
|
|
||||||
// materials::add("motd_image", data.value().buffer);
|
|
||||||
//}
|
|
||||||
}).detach();
|
|
||||||
}
|
|
||||||
|
|
||||||
void post_unpack() override
|
|
||||||
{
|
|
||||||
if (game::environment::is_sp())
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//scheduler::once(download_motd_image, scheduler::async);
|
||||||
|
//scheduler::schedule(setup_motd_image, scheduler::main);
|
||||||
|
scheduler::schedule(handle_featured_content, scheduler::async);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
REGISTER_COMPONENT(motd::component)
|
REGISTER_COMPONENT(motd::component)
|
||||||
|
10
src/client/component/motd.hpp
Normal file
10
src/client/component/motd.hpp
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <utils/http.hpp>
|
||||||
|
|
||||||
|
namespace motd
|
||||||
|
{
|
||||||
|
using featured_content_t = std::unordered_map<std::string, utils::http::result>;
|
||||||
|
|
||||||
|
featured_content_t& get_featured_content();
|
||||||
|
}
|
@ -249,7 +249,7 @@ namespace party
|
|||||||
|
|
||||||
if (mapname.contains('.') || mapname.contains("::"))
|
if (mapname.contains('.') || mapname.contains("::"))
|
||||||
{
|
{
|
||||||
throw std::runtime_error(utils::string::va("Invalid server mapname value %s\n", mapname.data()));
|
throw std::runtime_error(utils::string::va("Invalid server mapname value '%s'", mapname.data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto check_file = [&](const usermap_file& file)
|
const auto check_file = [&](const usermap_file& file)
|
||||||
@ -261,7 +261,12 @@ namespace party
|
|||||||
{
|
{
|
||||||
if (!file.optional)
|
if (!file.optional)
|
||||||
{
|
{
|
||||||
throw std::runtime_error(utils::string::va("Server %s is empty", file.name.data()));
|
std::string missing_value = "Server '%s' is empty";
|
||||||
|
if (file.name == "usermap_hash"s)
|
||||||
|
{
|
||||||
|
missing_value += " (or you are missing content for map '%s')";
|
||||||
|
}
|
||||||
|
throw std::runtime_error(utils::string::va(missing_value.data(), file.name.data(), mapname.data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -301,7 +306,7 @@ namespace party
|
|||||||
|
|
||||||
if (!server_fs_game.starts_with("mods/") || server_fs_game.contains('.') || server_fs_game.contains("::"))
|
if (!server_fs_game.starts_with("mods/") || server_fs_game.contains('.') || server_fs_game.contains("::"))
|
||||||
{
|
{
|
||||||
throw std::runtime_error(utils::string::va("Invalid server fs_game value %s\n", server_fs_game.data()));
|
throw std::runtime_error(utils::string::va("Invalid server fs_game value '%s'", server_fs_game.data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto needs_restart = false;
|
auto needs_restart = false;
|
||||||
@ -316,7 +321,7 @@ namespace party
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
utils::string::va("Connection failed: Server %s is empty.", file.name.data()));
|
utils::string::va("Server '%s' is empty", file.name.data()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto file_path = server_fs_game + "/mod" + file.extension;
|
const auto file_path = server_fs_game + "/mod" + file.extension;
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
#include <std_include.hpp>
|
#include <std_include.hpp>
|
||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
#include "server_list.hpp"
|
#include "command.hpp"
|
||||||
|
#include "console.hpp"
|
||||||
|
#include "fastfiles.hpp"
|
||||||
#include "localized_strings.hpp"
|
#include "localized_strings.hpp"
|
||||||
#include "network.hpp"
|
#include "network.hpp"
|
||||||
#include "scheduler.hpp"
|
|
||||||
#include "party.hpp"
|
#include "party.hpp"
|
||||||
#include "console.hpp"
|
#include "scheduler.hpp"
|
||||||
#include "command.hpp"
|
#include "server_list.hpp"
|
||||||
|
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
#include "game/dvars.hpp"
|
#include "game/dvars.hpp"
|
||||||
@ -137,7 +138,20 @@ namespace server_list
|
|||||||
case 0:
|
case 0:
|
||||||
return servers[i].host_name.empty() ? "" : servers[i].host_name.data();
|
return servers[i].host_name.empty() ? "" : servers[i].host_name.data();
|
||||||
case 1:
|
case 1:
|
||||||
return servers[i].map_name.empty() ? "Unknown" : servers[i].map_name.data();
|
{
|
||||||
|
const auto& map_name = servers[i].map_name;
|
||||||
|
if (map_name.empty())
|
||||||
|
{
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto map_display_name = game::UI_GetMapDisplayName(map_name.data());
|
||||||
|
if (!fastfiles::exists(map_name, false))
|
||||||
|
{
|
||||||
|
map_display_name = utils::string::va("^1%s", map_display_name);
|
||||||
|
}
|
||||||
|
return map_display_name;
|
||||||
|
}
|
||||||
case 2:
|
case 2:
|
||||||
{
|
{
|
||||||
const auto client_count = servers[i].clients - servers[i].bots;
|
const auto client_count = servers[i].clients - servers[i].bots;
|
||||||
@ -147,7 +161,18 @@ namespace server_list
|
|||||||
case 3:
|
case 3:
|
||||||
return servers[i].game_type.empty() ? "" : servers[i].game_type.data();
|
return servers[i].game_type.empty() ? "" : servers[i].game_type.data();
|
||||||
case 4:
|
case 4:
|
||||||
return servers[i].ping ? utils::string::va("%i", servers[i].ping) : "999";
|
{
|
||||||
|
const auto ping = servers[i].ping ? servers[i].ping : 999;
|
||||||
|
if (ping < 75)
|
||||||
|
{
|
||||||
|
return utils::string::va("^2%d", ping);
|
||||||
|
}
|
||||||
|
else if (ping < 150)
|
||||||
|
{
|
||||||
|
return utils::string::va("^3%d", ping);
|
||||||
|
}
|
||||||
|
return utils::string::va("^1%d", ping);
|
||||||
|
}
|
||||||
case 5:
|
case 5:
|
||||||
return servers[i].is_private ? "1" : "0";
|
return servers[i].is_private ? "1" : "0";
|
||||||
case 6:
|
case 6:
|
||||||
@ -368,7 +393,7 @@ namespace server_list
|
|||||||
server_info server{};
|
server_info server{};
|
||||||
server.address = address;
|
server.address = address;
|
||||||
server.host_name = info.get("hostname");
|
server.host_name = info.get("hostname");
|
||||||
server.map_name = game::UI_GetMapDisplayName(info.get("mapname").data());
|
server.map_name = info.get("mapname");
|
||||||
server.game_type = game::UI_GetGameTypeDisplayName(info.get("gametype").data());
|
server.game_type = game::UI_GetGameTypeDisplayName(info.get("gametype").data());
|
||||||
server.mod_name = info.get("fs_game");
|
server.mod_name = info.get("fs_game");
|
||||||
server.play_mode = playmode;
|
server.play_mode = playmode;
|
||||||
|
@ -266,10 +266,8 @@ namespace weapon
|
|||||||
// precache all weapons that are loaded in zones
|
// precache all weapons that are loaded in zones
|
||||||
g_setup_level_weapon_def_hook.create(0x462630_b, g_setup_level_weapon_def_stub);
|
g_setup_level_weapon_def_hook.create(0x462630_b, g_setup_level_weapon_def_stub);
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
// use tag_weapon if tag_weapon_right or tag_knife_attach are not found on model
|
// use tag_weapon if tag_weapon_right or tag_knife_attach are not found on model
|
||||||
xmodel_get_bone_index_hook.create(0x5C82B0_b, xmodel_get_bone_index_stub);
|
xmodel_get_bone_index_hook.create(0x5C82B0_b, xmodel_get_bone_index_stub);
|
||||||
#endif
|
|
||||||
// make custom weapon index mismatch not drop in CG_SetupCustomWeapon
|
// make custom weapon index mismatch not drop in CG_SetupCustomWeapon
|
||||||
utils::hook::call(0x11B9AF_b, cw_mismatch_error_stub);
|
utils::hook::call(0x11B9AF_b, cw_mismatch_error_stub);
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
#include <std_include.hpp>
|
#include <std_include.hpp>
|
||||||
|
|
||||||
|
#include "component/motd.hpp"
|
||||||
|
#include "component/scheduler.hpp"
|
||||||
|
|
||||||
#include <utils/http.hpp>
|
#include <utils/http.hpp>
|
||||||
#include "../services.hpp"
|
#include "../services.hpp"
|
||||||
|
|
||||||
@ -14,67 +17,21 @@ namespace demonware
|
|||||||
|
|
||||||
void bdMarketingComms::getMessages(service_server* server, byte_buffer* /*buffer*/) const
|
void bdMarketingComms::getMessages(service_server* server, byte_buffer* /*buffer*/) const
|
||||||
{
|
{
|
||||||
/*auto reply = server->create_reply(this->task_id());
|
|
||||||
|
|
||||||
const int timeout = 7; // seconds
|
|
||||||
|
|
||||||
std::optional<utils::http::result> motd_content;
|
|
||||||
std::optional<utils::http::result> featured_content;
|
|
||||||
std::optional<utils::http::result> featured2_content;
|
|
||||||
|
|
||||||
auto get_motd = [&motd_content]()
|
|
||||||
{
|
|
||||||
motd_content = utils::http::get_data("https://h1.gg/data/motd.json", {}, {}, {}, timeout);
|
|
||||||
};
|
|
||||||
auto get_featured = [&featured_content]()
|
|
||||||
{
|
|
||||||
featured_content = utils::http::get_data("https://h1.gg/data/featured.json", {}, {}, {}, timeout);
|
|
||||||
};
|
|
||||||
auto get_featured2 = [&featured2_content]()
|
|
||||||
{
|
|
||||||
featured2_content = utils::http::get_data("https://h1.gg/data/featured2.json", {}, {}, {}, timeout);
|
|
||||||
};
|
|
||||||
|
|
||||||
std::thread get_motd_thread(get_motd);
|
|
||||||
std::thread get_featured_thread(get_featured);
|
|
||||||
std::thread get_featured2_thread(get_featured2);
|
|
||||||
|
|
||||||
get_motd_thread.join();
|
|
||||||
get_featured_thread.join();
|
|
||||||
get_featured2_thread.join();
|
|
||||||
|
|
||||||
if (motd_content.has_value())
|
|
||||||
{
|
|
||||||
const auto motd = new bdMarketingMessage;
|
|
||||||
motd->m_messageID = 1;
|
|
||||||
motd->m_languageCode = "en-US";
|
|
||||||
motd->m_content = motd_content.value().buffer.data();
|
|
||||||
motd->m_metadata = "{}";
|
|
||||||
reply->add(motd);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (featured_content.has_value())
|
|
||||||
{
|
|
||||||
const auto featured = new bdMarketingMessage;
|
|
||||||
featured->m_messageID = 2;
|
|
||||||
featured->m_languageCode = "en-US";
|
|
||||||
featured->m_content = featured_content.value().buffer.data();
|
|
||||||
featured->m_metadata = "{}";
|
|
||||||
reply->add(featured);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (featured2_content.has_value())
|
|
||||||
{
|
|
||||||
const auto featured2 = new bdMarketingMessage;
|
|
||||||
featured2->m_messageID = 3;
|
|
||||||
featured2->m_languageCode = "en-US";
|
|
||||||
featured2->m_content = featured2_content.value().buffer.data();
|
|
||||||
featured2->m_metadata = "{}";
|
|
||||||
reply->add(featured2);
|
|
||||||
}
|
|
||||||
|
|
||||||
reply->send();*/
|
|
||||||
auto reply = server->create_reply(this->task_id());
|
auto reply = server->create_reply(this->task_id());
|
||||||
|
|
||||||
|
int message_id = 1;
|
||||||
|
const auto featured_content = motd::get_featured_content();
|
||||||
|
for (const auto& [key, value] : featured_content)
|
||||||
|
{
|
||||||
|
const auto marketing_message = new bdMarketingMessage;
|
||||||
|
marketing_message->m_messageID = message_id;
|
||||||
|
marketing_message->m_languageCode = "en-US";
|
||||||
|
marketing_message->m_content = value.buffer;
|
||||||
|
marketing_message->m_metadata = "{}";
|
||||||
|
reply->add(marketing_message);
|
||||||
|
++message_id;
|
||||||
|
}
|
||||||
|
|
||||||
reply->send();
|
reply->send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ namespace game
|
|||||||
WEAK symbol<void(XAssetType type, void(__cdecl* func)(XAssetHeader, void*), const void* inData, bool includeOverride)>
|
WEAK symbol<void(XAssetType type, void(__cdecl* func)(XAssetHeader, void*), const void* inData, bool includeOverride)>
|
||||||
DB_EnumXAssets_Internal{0x1F0BF0, 0x394C60};
|
DB_EnumXAssets_Internal{0x1F0BF0, 0x394C60};
|
||||||
WEAK symbol<const char*(const XAsset* asset)> DB_GetXAssetName{0x1BF890, 0x366140};
|
WEAK symbol<const char*(const XAsset* asset)> DB_GetXAssetName{0x1BF890, 0x366140};
|
||||||
WEAK symbol<int(XAssetType type)> DB_GetXAssetTypeSize{0x0, 0x366180};
|
WEAK symbol<int(XAssetType type)> DB_GetXAssetTypeSize{0x1BF8D0, 0x366180};
|
||||||
WEAK symbol<XAssetHeader(XAssetType type, const char* name,
|
WEAK symbol<XAssetHeader(XAssetType type, const char* name,
|
||||||
int createDefault)> DB_FindXAssetHeader{0x1F1120, 0x3950C0};
|
int createDefault)> DB_FindXAssetHeader{0x1F1120, 0x3950C0};
|
||||||
WEAK symbol<void(void* levelLoad, const char* name,
|
WEAK symbol<void(void* levelLoad, const char* name,
|
||||||
|
@ -74,9 +74,9 @@ BEGIN
|
|||||||
VALUE "FileDescription", "H1-Mod"
|
VALUE "FileDescription", "H1-Mod"
|
||||||
VALUE "FileVersion", VERSION_FILE
|
VALUE "FileVersion", VERSION_FILE
|
||||||
VALUE "InternalName", "H1-Mod"
|
VALUE "InternalName", "H1-Mod"
|
||||||
VALUE "LegalCopyright", "Copyright © 2022 H1-Mod. All rights reserved."
|
VALUE "LegalCopyright", "Copyright © 2024 Aurora. All rights reserved."
|
||||||
VALUE "Licence", "GPLv3"
|
VALUE "Licence", "GPLv3"
|
||||||
VALUE "Info", "https://h1.gg"
|
VALUE "Info", "https://auroramod.dev"
|
||||||
VALUE "OriginalFilename", "h1-mod.exe"
|
VALUE "OriginalFilename", "h1-mod.exe"
|
||||||
VALUE "ProductName", "h1-mod"
|
VALUE "ProductName", "h1-mod"
|
||||||
VALUE "ProductVersion", VERSION_PRODUCT
|
VALUE "ProductVersion", VERSION_PRODUCT
|
||||||
|
@ -492,7 +492,7 @@
|
|||||||
<a onClick="window.external.openUrl('https://discord.gg/RzzXu5EVnh');">Discord</a>,
|
<a onClick="window.external.openUrl('https://discord.gg/RzzXu5EVnh');">Discord</a>,
|
||||||
<a onClick="window.external.openUrl('https://h1.gg');">Website</a>,
|
<a onClick="window.external.openUrl('https://h1.gg');">Website</a>,
|
||||||
<a onClick="window.external.openUrl('https://docs.h1.gg');">Docs</a>,
|
<a onClick="window.external.openUrl('https://docs.h1.gg');">Docs</a>,
|
||||||
<a onClick="window.external.openUrl('https://github.com/h1-mod/h1-mod');">GitHub</a>
|
<a onClick="window.external.openUrl('https://github.com/auroramod/h1-mod');">GitHub</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This project is based on work from <a
|
This project is based on work from <a
|
||||||
|
@ -1,8 +1,20 @@
|
|||||||
#include "hook.hpp"
|
#include "hook.hpp"
|
||||||
#include "string.hpp"
|
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <MinHook.h>
|
#include <MinHook.h>
|
||||||
|
|
||||||
|
#include "concurrency.hpp"
|
||||||
|
#include "string.hpp"
|
||||||
|
#include "nt.hpp"
|
||||||
|
|
||||||
|
#ifdef max
|
||||||
|
#undef max
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef min
|
||||||
|
#undef min
|
||||||
|
#endif
|
||||||
|
|
||||||
Mem seg_ptr(const SReg& segment, const uint64_t off)
|
Mem seg_ptr(const SReg& segment, const uint64_t off)
|
||||||
{
|
{
|
||||||
auto mem = ptr_abs(off);
|
auto mem = ptr_abs(off);
|
||||||
@ -14,22 +26,164 @@ namespace utils::hook
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
[[maybe_unused]] class _
|
size_t get_allocation_granularity()
|
||||||
|
{
|
||||||
|
SYSTEM_INFO info{};
|
||||||
|
GetSystemInfo(&info);
|
||||||
|
|
||||||
|
return info.dwAllocationGranularity;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* allocate_somewhere_near(const void* base_address, const size_t granularity, const size_t size)
|
||||||
|
{
|
||||||
|
size_t target_address = reinterpret_cast<size_t>(base_address) - (1ull << 31);
|
||||||
|
target_address &= ~(granularity - 1);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
target_address += granularity;
|
||||||
|
|
||||||
|
auto* target_ptr = reinterpret_cast<uint8_t*>(target_address);
|
||||||
|
if (is_relatively_far(base_address, target_ptr))
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto res = VirtualAlloc(target_ptr, size, MEM_RESERVE | MEM_COMMIT,
|
||||||
|
PAGE_EXECUTE_READWRITE);
|
||||||
|
if (res)
|
||||||
|
{
|
||||||
|
if (is_relatively_far(base_address, target_ptr))
|
||||||
|
{
|
||||||
|
VirtualFree(res, 0, MEM_RELEASE);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<uint8_t*>(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class memory
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
_()
|
memory() = default;
|
||||||
|
|
||||||
|
memory(const void* ptr)
|
||||||
|
: memory()
|
||||||
{
|
{
|
||||||
if (MH_Initialize() != MH_OK)
|
static const auto allocation_granularity = get_allocation_granularity();
|
||||||
|
this->length_ = allocation_granularity;
|
||||||
|
|
||||||
|
this->buffer_ = allocate_somewhere_near(ptr, allocation_granularity, this->length_);
|
||||||
|
if (!this->buffer_)
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Failed to initialize MinHook");
|
throw std::runtime_error("Failed to allocate");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~_()
|
~memory()
|
||||||
{
|
{
|
||||||
MH_Uninitialize();
|
if (this->buffer_)
|
||||||
|
{
|
||||||
|
VirtualFree(this->buffer_, 0, MEM_RELEASE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} __;
|
|
||||||
|
memory(memory&& obj) noexcept
|
||||||
|
: memory()
|
||||||
|
{
|
||||||
|
this->operator=(std::move(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
memory& operator=(memory&& obj) noexcept
|
||||||
|
{
|
||||||
|
if (this != &obj)
|
||||||
|
{
|
||||||
|
this->~memory();
|
||||||
|
this->buffer_ = obj.buffer_;
|
||||||
|
this->length_ = obj.length_;
|
||||||
|
this->offset_ = obj.offset_;
|
||||||
|
|
||||||
|
obj.buffer_ = nullptr;
|
||||||
|
obj.length_ = 0;
|
||||||
|
obj.offset_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* allocate(const size_t length)
|
||||||
|
{
|
||||||
|
if (!this->buffer_)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->offset_ + length > this->length_)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ptr = this->get_ptr();
|
||||||
|
this->offset_ += length;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* get_ptr() const
|
||||||
|
{
|
||||||
|
return this->buffer_ + this->offset_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t* buffer_{};
|
||||||
|
size_t length_{};
|
||||||
|
size_t offset_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
void* get_memory_near(const void* address, const size_t size)
|
||||||
|
{
|
||||||
|
static concurrency::container<std::vector<memory>> memory_container{};
|
||||||
|
|
||||||
|
return memory_container.access<void*>([&](std::vector<memory>& memories)
|
||||||
|
{
|
||||||
|
for (auto& memory : memories)
|
||||||
|
{
|
||||||
|
if (!is_relatively_far(address, memory.get_ptr()))
|
||||||
|
{
|
||||||
|
const auto buffer = memory.allocate(size);
|
||||||
|
if (buffer)
|
||||||
|
{
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memories.emplace_back(address);
|
||||||
|
return memories.back().allocate(size);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void* initialize_min_hook()
|
||||||
|
{
|
||||||
|
static class min_hook_init
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
min_hook_init()
|
||||||
|
{
|
||||||
|
if (MH_Initialize() != MH_OK)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to initialize MinHook");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~min_hook_init()
|
||||||
|
{
|
||||||
|
MH_Uninitialize();
|
||||||
|
}
|
||||||
|
} min_hook_init;
|
||||||
|
return &min_hook_init;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void assembler::pushad64()
|
void assembler::pushad64()
|
||||||
@ -43,12 +197,26 @@ namespace utils::hook
|
|||||||
this->push(rsi);
|
this->push(rsi);
|
||||||
this->push(rdi);
|
this->push(rdi);
|
||||||
|
|
||||||
this->sub(rsp, 0x40);
|
this->push(r8);
|
||||||
|
this->push(r9);
|
||||||
|
this->push(r10);
|
||||||
|
this->push(r11);
|
||||||
|
this->push(r12);
|
||||||
|
this->push(r13);
|
||||||
|
this->push(r14);
|
||||||
|
this->push(r15);
|
||||||
}
|
}
|
||||||
|
|
||||||
void assembler::popad64()
|
void assembler::popad64()
|
||||||
{
|
{
|
||||||
this->add(rsp, 0x40);
|
this->pop(r15);
|
||||||
|
this->pop(r14);
|
||||||
|
this->pop(r13);
|
||||||
|
this->pop(r12);
|
||||||
|
this->pop(r11);
|
||||||
|
this->pop(r10);
|
||||||
|
this->pop(r9);
|
||||||
|
this->pop(r8);
|
||||||
|
|
||||||
this->pop(rdi);
|
this->pop(rdi);
|
||||||
this->pop(rsi);
|
this->pop(rsi);
|
||||||
@ -94,19 +262,26 @@ namespace utils::hook
|
|||||||
|
|
||||||
asmjit::Error assembler::call(void* target)
|
asmjit::Error assembler::call(void* target)
|
||||||
{
|
{
|
||||||
return Assembler::call(size_t(target));
|
return Assembler::call(reinterpret_cast<size_t>(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
asmjit::Error assembler::jmp(void* target)
|
asmjit::Error assembler::jmp(void* target)
|
||||||
{
|
{
|
||||||
return Assembler::jmp(size_t(target));
|
return Assembler::jmp(reinterpret_cast<size_t>(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
detour::detour(const size_t place, void* target) : detour(reinterpret_cast<void*>(place), target)
|
detour::detour()
|
||||||
|
{
|
||||||
|
(void)initialize_min_hook();
|
||||||
|
}
|
||||||
|
|
||||||
|
detour::detour(const size_t place, void* target)
|
||||||
|
: detour(reinterpret_cast<void*>(place), target)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
detour::detour(void* place, void* target)
|
detour::detour(void* place, void* target)
|
||||||
|
: detour()
|
||||||
{
|
{
|
||||||
this->create(place, target);
|
this->create(place, target);
|
||||||
}
|
}
|
||||||
@ -116,13 +291,19 @@ namespace utils::hook
|
|||||||
this->clear();
|
this->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void detour::enable() const
|
void detour::enable()
|
||||||
{
|
{
|
||||||
MH_EnableHook(this->place_);
|
MH_EnableHook(this->place_);
|
||||||
|
|
||||||
|
if (!this->moved_data_.empty())
|
||||||
|
{
|
||||||
|
this->move();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void detour::disable() const
|
void detour::disable()
|
||||||
{
|
{
|
||||||
|
this->un_move();
|
||||||
MH_DisableHook(this->place_);
|
MH_DisableHook(this->place_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,11 +329,23 @@ namespace utils::hook
|
|||||||
{
|
{
|
||||||
if (this->place_)
|
if (this->place_)
|
||||||
{
|
{
|
||||||
|
this->un_move();
|
||||||
MH_RemoveHook(this->place_);
|
MH_RemoveHook(this->place_);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->place_ = nullptr;
|
this->place_ = nullptr;
|
||||||
this->original_ = nullptr;
|
this->original_ = nullptr;
|
||||||
|
this->moved_data_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void detour::move()
|
||||||
|
{
|
||||||
|
this->moved_data_ = move_hook(this->place_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* detour::get_place() const
|
||||||
|
{
|
||||||
|
return this->place_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* detour::get_original() const
|
void* detour::get_original() const
|
||||||
@ -160,20 +353,29 @@ namespace utils::hook
|
|||||||
return this->original_;
|
return this->original_;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool iat(const nt::library& library, const std::string& target_library, const std::string& process, void* stub)
|
void detour::un_move()
|
||||||
{
|
{
|
||||||
if (!library.is_valid()) return false;
|
if (!this->moved_data_.empty())
|
||||||
|
{
|
||||||
|
copy(this->place_, this->moved_data_.data(), this->moved_data_.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<void*, void*>> iat(const nt::library& library, const std::string& target_library,
|
||||||
|
const std::string& process, void* stub)
|
||||||
|
{
|
||||||
|
if (!library.is_valid()) return {};
|
||||||
|
|
||||||
auto* const ptr = library.get_iat_entry(target_library, process);
|
auto* const ptr = library.get_iat_entry(target_library, process);
|
||||||
if (!ptr) return false;
|
if (!ptr) return {};
|
||||||
|
|
||||||
DWORD protect;
|
DWORD protect;
|
||||||
VirtualProtect(ptr, sizeof(*ptr), PAGE_EXECUTE_READWRITE, &protect);
|
VirtualProtect(ptr, sizeof(*ptr), PAGE_EXECUTE_READWRITE, &protect);
|
||||||
|
|
||||||
*ptr = stub;
|
std::swap(*ptr, stub);
|
||||||
|
|
||||||
VirtualProtect(ptr, sizeof(*ptr), protect, &protect);
|
VirtualProtect(ptr, sizeof(*ptr), protect, &protect);
|
||||||
return true;
|
return { {ptr, stub} };
|
||||||
}
|
}
|
||||||
|
|
||||||
void nop(void* place, const size_t length)
|
void nop(void* place, const size_t length)
|
||||||
@ -208,23 +410,50 @@ namespace utils::hook
|
|||||||
copy(reinterpret_cast<void*>(place), data, length);
|
copy(reinterpret_cast<void*>(place), data, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void copy_string(void* place, const char* str)
|
||||||
|
{
|
||||||
|
copy(reinterpret_cast<void*>(place), str, strlen(str) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void copy_string(const size_t place, const char* str)
|
||||||
|
{
|
||||||
|
copy_string(reinterpret_cast<void*>(place), str);
|
||||||
|
}
|
||||||
|
|
||||||
bool is_relatively_far(const void* pointer, const void* data, const int offset)
|
bool is_relatively_far(const void* pointer, const void* data, const int offset)
|
||||||
{
|
{
|
||||||
const int64_t diff = size_t(data) - (size_t(pointer) + offset);
|
return is_relatively_far(reinterpret_cast<size_t>(pointer), reinterpret_cast<size_t>(data), offset);
|
||||||
const auto small_diff = int32_t(diff);
|
}
|
||||||
return diff != int64_t(small_diff);
|
|
||||||
|
bool is_relatively_far(const size_t pointer, const size_t data, const int offset)
|
||||||
|
{
|
||||||
|
const auto diff = static_cast<int64_t>(data - (pointer + offset));
|
||||||
|
const auto small_diff = static_cast<int32_t>(diff);
|
||||||
|
return diff != static_cast<int64_t>(small_diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
void call(void* pointer, void* data)
|
void call(void* pointer, void* data)
|
||||||
{
|
{
|
||||||
if (is_relatively_far(pointer, data))
|
if (is_relatively_far(pointer, data))
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Too far away to create 32bit relative branch");
|
auto* trampoline = get_memory_near(pointer, 14);
|
||||||
|
if (!trampoline)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Too far away to create 32bit relative branch");
|
||||||
|
}
|
||||||
|
|
||||||
|
call(pointer, trampoline);
|
||||||
|
jump(trampoline, data, true, true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* patch_pointer = PBYTE(pointer);
|
uint8_t copy_data[5];
|
||||||
set<uint8_t>(patch_pointer, 0xE8);
|
copy_data[0] = 0xE8;
|
||||||
set<int32_t>(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5)));
|
*reinterpret_cast<int32_t*>(©_data[1]) = static_cast<int32_t>(reinterpret_cast<size_t>(data) - (
|
||||||
|
reinterpret_cast<size_t>(pointer) + 5));
|
||||||
|
|
||||||
|
auto* patch_pointer = static_cast<PBYTE>(pointer);
|
||||||
|
copy(patch_pointer, copy_data, sizeof(copy_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
void call(const size_t pointer, void* data)
|
void call(const size_t pointer, void* data)
|
||||||
@ -237,39 +466,67 @@ namespace utils::hook
|
|||||||
return call(pointer, reinterpret_cast<void*>(data));
|
return call(pointer, reinterpret_cast<void*>(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
void jump(void* pointer, void* data, const bool use_far)
|
void jump(void* pointer, void* data, const bool use_far, const bool use_safe)
|
||||||
{
|
{
|
||||||
static const unsigned char jump_data[] = {
|
static const unsigned char jump_data[] = {
|
||||||
0x48, 0xb8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0xff, 0xe0
|
0x48, 0xb8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0xff, 0xe0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const unsigned char jump_data_safe[] = {
|
||||||
|
0xFF, 0x25, 0x00, 0x00, 0x00, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
if (!use_far && is_relatively_far(pointer, data))
|
if (!use_far && is_relatively_far(pointer, data))
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Too far away to create 32bit relative branch");
|
auto* trampoline = get_memory_near(pointer, 14);
|
||||||
|
if (!trampoline)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Too far away to create 32bit relative branch");
|
||||||
|
}
|
||||||
|
jump(pointer, trampoline, false, false);
|
||||||
|
jump(trampoline, data, true, true);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* patch_pointer = PBYTE(pointer);
|
auto* patch_pointer = PBYTE(pointer);
|
||||||
|
|
||||||
if (use_far)
|
if (use_far)
|
||||||
{
|
{
|
||||||
copy(patch_pointer, jump_data, sizeof(jump_data));
|
if (use_safe)
|
||||||
copy(patch_pointer + 2, &data, sizeof(data));
|
{
|
||||||
|
uint8_t copy_data[sizeof(jump_data_safe) + sizeof(data)];
|
||||||
|
memcpy(copy_data, jump_data_safe, sizeof(jump_data_safe));
|
||||||
|
memcpy(copy_data + sizeof(jump_data_safe), &data, sizeof(data));
|
||||||
|
|
||||||
|
copy(patch_pointer, copy_data, sizeof(copy_data));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint8_t copy_data[sizeof(jump_data)];
|
||||||
|
memcpy(copy_data, jump_data, sizeof(jump_data));
|
||||||
|
memcpy(copy_data + 2, &data, sizeof(data));
|
||||||
|
|
||||||
|
copy(patch_pointer, copy_data, sizeof(copy_data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
set<uint8_t>(patch_pointer, 0xE9);
|
uint8_t copy_data[5];
|
||||||
set<int32_t>(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5)));
|
copy_data[0] = 0xE9;
|
||||||
|
*reinterpret_cast<int32_t*>(©_data[1]) = int32_t(size_t(data) - (size_t(pointer) + 5));
|
||||||
|
|
||||||
|
copy(patch_pointer, copy_data, sizeof(copy_data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void jump(const size_t pointer, void* data, const bool use_far)
|
void jump(const size_t pointer, void* data, const bool use_far, const bool use_safe)
|
||||||
{
|
{
|
||||||
return jump(reinterpret_cast<void*>(pointer), data, use_far);
|
return jump(reinterpret_cast<void*>(pointer), data, use_far, use_safe);
|
||||||
}
|
}
|
||||||
|
|
||||||
void jump(const size_t pointer, const size_t data, const bool use_far)
|
void jump(const size_t pointer, const size_t data, const bool use_far, const bool use_safe)
|
||||||
{
|
{
|
||||||
return jump(pointer, reinterpret_cast<void*>(data), use_far);
|
return jump(pointer, reinterpret_cast<void*>(data), use_far, use_safe);
|
||||||
}
|
}
|
||||||
|
|
||||||
void* assemble(const std::function<void(assembler&)>& asm_function)
|
void* assemble(const std::function<void(assembler&)>& asm_function)
|
||||||
@ -284,24 +541,70 @@ namespace utils::hook
|
|||||||
asm_function(a);
|
asm_function(a);
|
||||||
|
|
||||||
void* result = nullptr;
|
void* result = nullptr;
|
||||||
runtime.add(&result, &code);
|
auto err_result = runtime.add(&result, &code);
|
||||||
|
|
||||||
|
if (err_result != asmjit::ErrorCode::kErrorOk)
|
||||||
|
{
|
||||||
|
printf("ASMJIT ERROR: %s\n", asmjit::DebugUtils::errorAsString(err_result));
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void inject(void* pointer, const void* data)
|
void inject(size_t pointer, size_t data)
|
||||||
{
|
{
|
||||||
if (is_relatively_far(pointer, data, 4))
|
if (is_relatively_far(pointer, data, 4))
|
||||||
{
|
{
|
||||||
throw std::runtime_error("Too far away to create 32bit relative branch");
|
throw std::runtime_error("Too far away to create 32bit relative branch");
|
||||||
}
|
}
|
||||||
|
|
||||||
set<int32_t>(pointer, int32_t(size_t(data) - (size_t(pointer) + 4)));
|
set<int32_t>(
|
||||||
|
pointer, static_cast<int32_t>(data - (pointer + 4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void inject(void* pointer, const void* data)
|
||||||
|
{
|
||||||
|
return inject(reinterpret_cast<size_t>(pointer), reinterpret_cast<size_t>(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
void inject(const size_t pointer, const void* data)
|
void inject(const size_t pointer, const void* data)
|
||||||
{
|
{
|
||||||
return inject(reinterpret_cast<void*>(pointer), data);
|
return inject(pointer, reinterpret_cast<size_t>(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> move_hook(void* pointer)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> original_data{};
|
||||||
|
|
||||||
|
auto* data_ptr = static_cast<uint8_t*>(pointer);
|
||||||
|
if (data_ptr[0] == 0xE9)
|
||||||
|
{
|
||||||
|
original_data.resize(6);
|
||||||
|
memmove(original_data.data(), pointer, original_data.size());
|
||||||
|
|
||||||
|
auto* target = follow_branch(data_ptr);
|
||||||
|
nop(data_ptr, 1);
|
||||||
|
jump(data_ptr + 1, target);
|
||||||
|
}
|
||||||
|
else if (data_ptr[0] == 0xFF && data_ptr[1] == 0x25)
|
||||||
|
{
|
||||||
|
original_data.resize(15);
|
||||||
|
memmove(original_data.data(), pointer, original_data.size());
|
||||||
|
|
||||||
|
copy(data_ptr + 1, data_ptr, 14);
|
||||||
|
nop(data_ptr, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error("No branch instruction found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return original_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> move_hook(const size_t pointer)
|
||||||
|
{
|
||||||
|
return move_hook(reinterpret_cast<void*>(pointer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void* follow_branch(void* address)
|
void* follow_branch(void* address)
|
||||||
@ -314,31 +617,4 @@ namespace utils::hook
|
|||||||
|
|
||||||
return extract<void*>(data + 1);
|
return extract<void*>(data + 1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
uint8_t* allocate_somewhere_near(const void* base_address, const size_t size)
|
|
||||||
{
|
|
||||||
size_t offset = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
offset += size;
|
|
||||||
auto* target_address = static_cast<const uint8_t*>(base_address) - offset;
|
|
||||||
if (utils::hook::is_relatively_far(base_address, target_address))
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto res = VirtualAlloc(const_cast<uint8_t*>(target_address), size, MEM_RESERVE | MEM_COMMIT,
|
|
||||||
PAGE_EXECUTE_READWRITE);
|
|
||||||
if (res)
|
|
||||||
{
|
|
||||||
if (utils::hook::is_relatively_far(base_address, target_address))
|
|
||||||
{
|
|
||||||
VirtualFree(res, 0, MEM_RELEASE);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<uint8_t*>(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,20 +13,20 @@ namespace utils::hook
|
|||||||
{
|
{
|
||||||
namespace detail
|
namespace detail
|
||||||
{
|
{
|
||||||
template<size_t entries>
|
template <size_t Entries>
|
||||||
std::vector<size_t(*)()> get_iota_functions()
|
std::vector<size_t(*)()> get_iota_functions()
|
||||||
{
|
{
|
||||||
if constexpr (entries == 0)
|
if constexpr (Entries == 0)
|
||||||
{
|
{
|
||||||
std::vector<size_t(*)()> functions;
|
std::vector<size_t(*)()> functions;
|
||||||
return functions;
|
return functions;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto functions = get_iota_functions<entries - 1>();
|
auto functions = get_iota_functions<Entries - 1>();
|
||||||
functions.emplace_back([]()
|
functions.emplace_back([]()
|
||||||
{
|
{
|
||||||
return entries - 1;
|
return Entries - 1;
|
||||||
});
|
});
|
||||||
return functions;
|
return functions;
|
||||||
}
|
}
|
||||||
@ -39,8 +39,8 @@ namespace utils::hook
|
|||||||
// Example:
|
// Example:
|
||||||
// ID3D11Device* device = ...
|
// ID3D11Device* device = ...
|
||||||
// auto entry = get_vtable_entry(device, &ID3D11Device::CreateTexture2D);
|
// auto entry = get_vtable_entry(device, &ID3D11Device::CreateTexture2D);
|
||||||
template <size_t entries = 100, typename Class, typename T, typename... Args>
|
template <size_t Entries = 100, typename Class, typename T, typename... Args>
|
||||||
void** get_vtable_entry(Class* obj, T (Class::* entry)(Args ...))
|
void** get_vtable_entry(Class* obj, T(Class::* entry)(Args ...))
|
||||||
{
|
{
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
@ -50,11 +50,11 @@ namespace utils::hook
|
|||||||
|
|
||||||
func = entry;
|
func = entry;
|
||||||
|
|
||||||
auto iota_functions = detail::get_iota_functions<entries>();
|
auto iota_functions = detail::get_iota_functions<Entries>();
|
||||||
auto* object = iota_functions.data();
|
auto* object = iota_functions.data();
|
||||||
|
|
||||||
using FakeFunc = size_t(__thiscall*)(void* self);
|
using fake_func = size_t(__thiscall*)(void* self);
|
||||||
auto index = static_cast<FakeFunc>(pointer)(&object);
|
auto index = static_cast<fake_func>(pointer)(&object);
|
||||||
|
|
||||||
void** obj_v_table = *reinterpret_cast<void***>(obj);
|
void** obj_v_table = *reinterpret_cast<void***>(obj);
|
||||||
return &obj_v_table[index];
|
return &obj_v_table[index];
|
||||||
@ -88,7 +88,7 @@ namespace utils::hook
|
|||||||
class detour
|
class detour
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
detour() = default;
|
detour();
|
||||||
detour(void* place, void* target);
|
detour(void* place, void* target);
|
||||||
detour(size_t place, void* target);
|
detour(size_t place, void* target);
|
||||||
~detour();
|
~detour();
|
||||||
@ -102,13 +102,15 @@ namespace utils::hook
|
|||||||
{
|
{
|
||||||
if (this != &other)
|
if (this != &other)
|
||||||
{
|
{
|
||||||
this->~detour();
|
this->clear();
|
||||||
|
|
||||||
this->place_ = other.place_;
|
this->place_ = other.place_;
|
||||||
this->original_ = other.original_;
|
this->original_ = other.original_;
|
||||||
|
this->moved_data_ = other.moved_data_;
|
||||||
|
|
||||||
other.place_ = nullptr;
|
other.place_ = nullptr;
|
||||||
other.original_ = nullptr;
|
other.original_ = nullptr;
|
||||||
|
other.moved_data_ = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
@ -117,13 +119,17 @@ namespace utils::hook
|
|||||||
detour(const detour&) = delete;
|
detour(const detour&) = delete;
|
||||||
detour& operator=(const detour&) = delete;
|
detour& operator=(const detour&) = delete;
|
||||||
|
|
||||||
void enable() const;
|
void enable();
|
||||||
void disable() const;
|
void disable();
|
||||||
|
|
||||||
void create(void* place, void* target);
|
void create(void* place, void* target);
|
||||||
void create(size_t place, void* target);
|
void create(size_t place, void* target);
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
void move();
|
||||||
|
|
||||||
|
void* get_place() const;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T* get() const
|
T* get() const
|
||||||
{
|
{
|
||||||
@ -139,11 +145,15 @@ namespace utils::hook
|
|||||||
[[nodiscard]] void* get_original() const;
|
[[nodiscard]] void* get_original() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::vector<uint8_t> moved_data_{};
|
||||||
void* place_{};
|
void* place_{};
|
||||||
void* original_{};
|
void* original_{};
|
||||||
|
|
||||||
|
void un_move();
|
||||||
};
|
};
|
||||||
|
|
||||||
bool iat(const nt::library& library, const std::string& target_library, const std::string& process, void* stub);
|
std::optional<std::pair<void*, void*>> iat(const nt::library& library, const std::string& target_library,
|
||||||
|
const std::string& process, void* stub);
|
||||||
|
|
||||||
void nop(void* place, size_t length);
|
void nop(void* place, size_t length);
|
||||||
void nop(size_t place, size_t length);
|
void nop(size_t place, size_t length);
|
||||||
@ -151,20 +161,28 @@ namespace utils::hook
|
|||||||
void copy(void* place, const void* data, size_t length);
|
void copy(void* place, const void* data, size_t length);
|
||||||
void copy(size_t place, const void* data, size_t length);
|
void copy(size_t place, const void* data, size_t length);
|
||||||
|
|
||||||
|
void copy_string(void* place, const char* str);
|
||||||
|
void copy_string(size_t place, const char* str);
|
||||||
|
|
||||||
bool is_relatively_far(const void* pointer, const void* data, int offset = 5);
|
bool is_relatively_far(const void* pointer, const void* data, int offset = 5);
|
||||||
|
bool is_relatively_far(size_t pointer, size_t data, int offset = 5);
|
||||||
|
|
||||||
void call(void* pointer, void* data);
|
void call(void* pointer, void* data);
|
||||||
void call(size_t pointer, void* data);
|
void call(size_t pointer, void* data);
|
||||||
void call(size_t pointer, size_t data);
|
void call(size_t pointer, size_t data);
|
||||||
|
|
||||||
void jump(void* pointer, void* data, bool use_far = false);
|
void jump(void* pointer, void* data, bool use_far = false, bool use_safe = false);
|
||||||
void jump(size_t pointer, void* data, bool use_far = false);
|
void jump(size_t pointer, void* data, bool use_far = false, bool use_safe = false);
|
||||||
void jump(size_t pointer, size_t data, bool use_far = false);
|
void jump(size_t pointer, size_t data, bool use_far = false, bool use_safe = false);
|
||||||
|
|
||||||
void* assemble(const std::function<void(assembler&)>& asm_function);
|
void* assemble(const std::function<void(assembler&)>& asm_function);
|
||||||
|
|
||||||
void inject(void* pointer, const void* data);
|
void inject(void* pointer, const void* data);
|
||||||
void inject(size_t pointer, const void* data);
|
void inject(size_t pointer, const void* data);
|
||||||
|
void inject(size_t pointer, size_t data);
|
||||||
|
|
||||||
|
std::vector<uint8_t> move_hook(void* pointer);
|
||||||
|
std::vector<uint8_t> move_hook(size_t pointer);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T extract(void* address)
|
T extract(void* address)
|
||||||
@ -177,19 +195,13 @@ namespace utils::hook
|
|||||||
void* follow_branch(void* address);
|
void* follow_branch(void* address);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static void set(void* place, T value)
|
static void set(void* place, T value = false)
|
||||||
{
|
{
|
||||||
DWORD old_protect;
|
copy(place, &value, sizeof(value));
|
||||||
VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &old_protect);
|
|
||||||
|
|
||||||
*static_cast<T*>(place) = value;
|
|
||||||
|
|
||||||
VirtualProtect(place, sizeof(T), old_protect, &old_protect);
|
|
||||||
FlushInstructionCache(GetCurrentProcess(), place, sizeof(T));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
static void set(const size_t place, T value)
|
static void set(const size_t place, T value = false)
|
||||||
{
|
{
|
||||||
return set<T>(reinterpret_cast<void*>(place), value);
|
return set<T>(reinterpret_cast<void*>(place), value);
|
||||||
}
|
}
|
||||||
@ -206,8 +218,6 @@ namespace utils::hook
|
|||||||
return static_cast<T(*)(Args ...)>(func)(args...);
|
return static_cast<T(*)(Args ...)>(func)(args...);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t* allocate_somewhere_near(const void* base_address, const size_t size);
|
|
||||||
|
|
||||||
template <size_t Base>
|
template <size_t Base>
|
||||||
void* allocate_far_jump()
|
void* allocate_far_jump()
|
||||||
{
|
{
|
||||||
@ -255,11 +265,4 @@ namespace utils::hook
|
|||||||
const auto pos = create_far_jump<Base>(dest);
|
const auto pos = create_far_jump<Base>(dest);
|
||||||
jump(address, pos, false);
|
jump(address, pos, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
template <size_t Base, typename T>
|
|
||||||
void far_call(const size_t address, const T dest)
|
|
||||||
{
|
|
||||||
const auto pos = create_far_jump<Base>(dest);
|
|
||||||
call(address, pos);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,6 +4,14 @@
|
|||||||
|
|
||||||
#include <intrin.h>
|
#include <intrin.h>
|
||||||
|
|
||||||
|
#ifdef max
|
||||||
|
#undef max
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef min
|
||||||
|
#undef min
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace utils::hook
|
namespace utils::hook
|
||||||
{
|
{
|
||||||
void signature::load_pattern(const std::string& pattern)
|
void signature::load_pattern(const std::string& pattern)
|
||||||
@ -29,7 +37,7 @@ namespace utils::hook
|
|||||||
throw std::runtime_error("Invalid pattern");
|
throw std::runtime_error("Invalid pattern");
|
||||||
}
|
}
|
||||||
|
|
||||||
char str[] = {val, 0};
|
char str[] = { val, 0 };
|
||||||
const auto current_nibble = static_cast<uint8_t>(strtol(str, nullptr, 16));
|
const auto current_nibble = static_cast<uint8_t>(strtol(str, nullptr, 16));
|
||||||
|
|
||||||
if (!has_nibble)
|
if (!has_nibble)
|
||||||
@ -68,15 +76,15 @@ namespace utils::hook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> signature::process_range(uint8_t* start, const size_t length) const
|
signature::signature_result signature::process_range(uint8_t* start, const size_t length) const
|
||||||
{
|
{
|
||||||
if (this->has_sse_support()) return this->process_range_vectorized(start, length);
|
if (this->has_sse_support()) return this->process_range_vectorized(start, length);
|
||||||
return this->process_range_linear(start, length);
|
return this->process_range_linear(start, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> signature::process_range_linear(uint8_t* start, const size_t length) const
|
signature::signature_result signature::process_range_linear(uint8_t* start, const size_t length) const
|
||||||
{
|
{
|
||||||
std::vector<size_t> result;
|
std::vector<uint8_t*> result;
|
||||||
|
|
||||||
for (size_t i = 0; i < length; ++i)
|
for (size_t i = 0; i < length; ++i)
|
||||||
{
|
{
|
||||||
@ -93,17 +101,17 @@ namespace utils::hook
|
|||||||
|
|
||||||
if (j == this->mask_.size())
|
if (j == this->mask_.size())
|
||||||
{
|
{
|
||||||
result.push_back(size_t(address));
|
result.push_back(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> signature::process_range_vectorized(uint8_t* start, const size_t length) const
|
signature::signature_result signature::process_range_vectorized(uint8_t* start, const size_t length) const
|
||||||
{
|
{
|
||||||
std::vector<size_t> result;
|
std::vector<uint8_t*> result;
|
||||||
__declspec(align(16)) char desired_mask[16] = {0};
|
__declspec(align(16)) char desired_mask[16] = { 0 };
|
||||||
|
|
||||||
for (size_t i = 0; i < this->mask_.size(); i++)
|
for (size_t i = 0; i < this->mask_.size(); i++)
|
||||||
{
|
{
|
||||||
@ -118,14 +126,14 @@ namespace utils::hook
|
|||||||
const auto address = start + i;
|
const auto address = start + i;
|
||||||
const auto value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(address));
|
const auto value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(address));
|
||||||
const auto comparison = _mm_cmpestrm(value, 16, comparand, static_cast<int>(this->mask_.size()),
|
const auto comparison = _mm_cmpestrm(value, 16, comparand, static_cast<int>(this->mask_.size()),
|
||||||
_SIDD_CMP_EQUAL_EACH);
|
_SIDD_CMP_EQUAL_EACH);
|
||||||
|
|
||||||
const auto matches = _mm_and_si128(mask, comparison);
|
const auto matches = _mm_and_si128(mask, comparison);
|
||||||
const auto equivalence = _mm_xor_si128(mask, matches);
|
const auto equivalence = _mm_xor_si128(mask, matches);
|
||||||
|
|
||||||
if (_mm_test_all_zeros(equivalence, equivalence))
|
if (_mm_test_all_zeros(equivalence, equivalence))
|
||||||
{
|
{
|
||||||
result.push_back(size_t(address));
|
result.push_back(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +152,7 @@ namespace utils::hook
|
|||||||
signature::signature_result signature::process_serial() const
|
signature::signature_result signature::process_serial() const
|
||||||
{
|
{
|
||||||
const auto sub = this->has_sse_support() ? 16 : this->mask_.size();
|
const auto sub = this->has_sse_support() ? 16 : this->mask_.size();
|
||||||
return {this->process_range(this->start_, this->length_ - sub)};
|
return { this->process_range(this->start_, this->length_ - sub) };
|
||||||
}
|
}
|
||||||
|
|
||||||
signature::signature_result signature::process_parallel() const
|
signature::signature_result signature::process_parallel() const
|
||||||
@ -156,7 +164,7 @@ namespace utils::hook
|
|||||||
const auto grid = range / cores;
|
const auto grid = range / cores;
|
||||||
|
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
std::vector<size_t> result;
|
std::vector<uint8_t*> result;
|
||||||
std::vector<std::thread> threads;
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
for (auto i = 0u; i < cores; ++i)
|
for (auto i = 0u; i < cores; ++i)
|
||||||
@ -165,7 +173,7 @@ namespace utils::hook
|
|||||||
const auto length = (i + 1 == cores) ? (this->start_ + this->length_ - sub) - start : grid;
|
const auto length = (i + 1 == cores) ? (this->start_ + this->length_ - sub) - start : grid;
|
||||||
threads.emplace_back([&, start, length]()
|
threads.emplace_back([&, start, length]()
|
||||||
{
|
{
|
||||||
auto local_result = this->process_range(start, length);
|
const auto local_result = this->process_range(start, length);
|
||||||
if (local_result.empty()) return;
|
if (local_result.empty()) return;
|
||||||
|
|
||||||
std::lock_guard _(mutex);
|
std::lock_guard _(mutex);
|
||||||
@ -185,7 +193,7 @@ namespace utils::hook
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::sort(result.begin(), result.end());
|
std::sort(result.begin(), result.end());
|
||||||
return {std::move(result)};
|
return { std::move(result) };
|
||||||
}
|
}
|
||||||
|
|
||||||
bool signature::has_sse_support() const
|
bool signature::has_sse_support() const
|
||||||
|
@ -7,33 +7,9 @@ namespace utils::hook
|
|||||||
class signature final
|
class signature final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
class signature_result
|
using signature_result = std::vector<uint8_t*>;
|
||||||
{
|
|
||||||
public:
|
|
||||||
signature_result(std::vector<size_t>&& matches) : matches_(std::move(matches))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] uint8_t* get(const size_t index) const
|
explicit signature(const std::string& pattern, const nt::library& library = {})
|
||||||
{
|
|
||||||
if (index >= this->count())
|
|
||||||
{
|
|
||||||
throw std::runtime_error("Invalid index");
|
|
||||||
}
|
|
||||||
|
|
||||||
return reinterpret_cast<uint8_t*>(this->matches_[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] size_t count() const
|
|
||||||
{
|
|
||||||
return this->matches_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<size_t> matches_;
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit signature(const std::string& pattern, const nt::library library = {})
|
|
||||||
: signature(pattern, library.get_ptr(), library.get_optional_header()->SizeOfImage)
|
: signature(pattern, library.get_ptr(), library.get_optional_header()->SizeOfImage)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -62,9 +38,9 @@ namespace utils::hook
|
|||||||
|
|
||||||
signature_result process_parallel() const;
|
signature_result process_parallel() const;
|
||||||
signature_result process_serial() const;
|
signature_result process_serial() const;
|
||||||
std::vector<size_t> process_range(uint8_t* start, size_t length) const;
|
signature_result process_range(uint8_t* start, size_t length) const;
|
||||||
std::vector<size_t> process_range_linear(uint8_t* start, size_t length) const;
|
signature_result process_range_linear(uint8_t* start, size_t length) const;
|
||||||
std::vector<size_t> process_range_vectorized(uint8_t* start, size_t length) const;
|
signature_result process_range_vectorized(uint8_t* start, size_t length) const;
|
||||||
|
|
||||||
bool has_sse_support() const;
|
bool has_sse_support() const;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user