Compare commits

...

18 Commits

Author SHA1 Message Date
m
ec16ccd80c fix #490 lol
https://github.com/auroramod/h1-mod/issues/490
2024-02-22 11:05:08 -06:00
m
d42d4fb6cd new branding 🍇 2024-02-22 11:00:55 -06:00
m
ca2a3c3eb6
better readme [skip ci] 2024-02-13 15:18:16 -06:00
fed
2e392bb7a3 Realloc localize on sp 2024-01-30 02:12:42 +01:00
m
a51fa416cf re-add motd & featured content 2024-01-17 09:21:42 -06:00
m
f8eb92fff4 server browser colorization + fixes
closes #694
2024-01-17 06:59:17 -06:00
m
45d1617d22 fix stat/prestige reset crashing game
fixes #274 #180
2024-01-17 06:24:27 -06:00
m
5cdc2dc100 re-add info prints 2024-01-17 03:22:06 -06:00
m
df12711248
Merge pull request #730 from h1-mod/dependabot/submodules/deps/curl-3378d2b
Bump deps/curl from `102de7a` to `3378d2b`
2024-01-16 13:04:31 -06:00
m
a5e6c257c0
Merge pull request #728 from h1-mod/dependabot/submodules/deps/asmjit-118ae6c
Bump deps/asmjit from `7c10a14` to `118ae6c`
2024-01-16 13:03:08 -06:00
m
931d5ac295
Merge pull request #727 from h1-mod/dependabot/submodules/deps/sol2-e8e122e
Bump deps/sol2 from `9c882a2` to `e8e122e`
2024-01-16 13:02:45 -06:00
dependabot[bot]
8472b1958a
Bump deps/curl from 102de7a to 3378d2b
Bumps [deps/curl](https://github.com/curl/curl) from `102de7a` to `3378d2b`.
- [Release notes](https://github.com/curl/curl/releases)
- [Commits](102de7aa8d...3378d2bd09)

---
updated-dependencies:
- dependency-name: deps/curl
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-16 17:54:59 +00:00
quaK
027cabb071 Enable this since its fixed 2024-01-16 02:02:09 +02:00
dependabot[bot]
1bc60390a5
Bump deps/asmjit from 7c10a14 to 118ae6c
Bumps [deps/asmjit](https://github.com/asmjit/asmjit) from `7c10a14` to `118ae6c`.
- [Commits](7c10a14d34...118ae6ced1)

---
updated-dependencies:
- dependency-name: deps/asmjit
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-15 17:48:19 +00:00
dependabot[bot]
ba55395e99
Bump deps/sol2 from 9c882a2 to e8e122e
Bumps [deps/sol2](https://github.com/ThePhD/sol2) from `9c882a2` to `e8e122e`.
- [Release notes](https://github.com/ThePhD/sol2/releases)
- [Commits](9c882a28fd...e8e122e9ce)

---
updated-dependencies:
- dependency-name: deps/sol2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-15 17:48:17 +00:00
fed
1c1a543a9c Add com_waitEndFrameMode 2024-01-13 20:18:18 +01:00
quaK
820aa6788e update arxan component
includes anti anti debug
2024-01-11 19:43:49 +02:00
m
24a8c68abf fix team balance gsc 2024-01-09 17:20:58 -06:00
30 changed files with 1621 additions and 426 deletions

View File

@ -1,7 +1,7 @@
![license](https://img.shields.io/github/license/h1-mod/h1-mod.svg)
[![open bugs](https://img.shields.io/github/issues/h1-mod/h1-mod/bug?label=bugs)](https://github.com/h1-mod/h1-mod/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
[![Build](https://github.com/h1-mod/h1-mod/workflows/Build/badge.svg)](https://github.com/h1-mod/h1-mod/actions)
[![Build status](https://ci.appveyor.com/api/projects/status/0sh80kdnsvm53rno?svg=true)](https://ci.appveyor.com/project/h1-mod/h1-mod)
![license](https://img.shields.io/github/license/auroramod/h1-mod.svg)
[![open bugs](https://img.shields.io/github/issues/auroramod/h1-mod/bug?label=bugs)](https://github.com/auroramod/h1-mod/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
[![Build](https://github.com/auroramod/h1-mod/workflows/Build/badge.svg)](https://github.com/auroramod/h1-mod/actions)
[![Build status](https://ci.appveyor.com/api/projects/status/0sh80kdnsvm53rno?svg=true)](https://ci.appveyor.com/project/auroramod/h1-mod)
[![Discord](https://img.shields.io/discord/945420505157083208?color=%237289DA&label=members&logo=discord&logoColor=%23FFFFFF)](https://discord.gg/RzzXu5EVnh)
# H1-Mod
@ -27,9 +27,9 @@ NOTE: You must legally own Call of Duty®: Modern Warfare Remastered to run this
## 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)
- [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

View File

@ -18,7 +18,7 @@ balance_teams_stub()
{
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");
}
@ -41,7 +41,7 @@ get_valid_team_array(team)
if (isdefined(players[i].pers["team"]) && players[i].pers["team"] == team)
team_array[team_array.size] = players[i];
}
return team_arary;
return team_array;
}
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)
for (i = 0; i < team.size; i++)
{
if (isdefined(team[j].dont_auto_balance))
if (isdefined(team[i].dont_auto_balance))
continue;
if (!isdefined(lowest_score_player))
lowest_score_player = team[j];
else if (team[j].pers["score"] < lowest_score_player.pers["score"])
lowest_score_player = team[j];
lowest_score_player = team[i];
else if (team[i].pers["score"] < lowest_score_player.pers["score"])
lowest_score_player = team[i];
}
lowest_score_player set_team(new_team);

View File

@ -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",
"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)
return str:sub(1, #start) == start
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 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 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
else
player_score = player_stats.extrascore0

View File

@ -18,7 +18,7 @@ local columns = {{
text = "@MENU_TYPE1",
dataindex = 3
}, {
offset = 780,
offset = 790,
text = "@EXE_SV_INFO_MOD",
dataindex = 6
}, {

2
deps/asmjit vendored

@ -1 +1 @@
Subproject commit 7c10a14d347879f889c6d11a9398f1d453acc690
Subproject commit 118ae6ced160f68dd142a0d758aa7efb4356196a

2
deps/curl vendored

@ -1 +1 @@
Subproject commit 102de7aa8d5bfc6ed5fe85e89c7b943d0c186f03
Subproject commit 3378d2bd0931433999ef4c0ca95040c394146484

2
deps/sol2 vendored

@ -1 +1 @@
Subproject commit 9c882a28fdb6f4ad79a53a4191b43ce48a661175
Subproject commit e8e122e9ce46f4f1c0b04003d8b703fe1b89755a

View File

@ -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)

View 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)

View 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,
};
}

View 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,
};
}

View File

@ -37,13 +37,13 @@ namespace branding
}
#ifdef DEBUG
const auto text = "h1-mod: " VERSION " (" __DATE__ " " __TIME__ ")";
const auto text = "h1-mod (Aurora): " VERSION " (" __DATE__ " " __TIME__ ")";
#else
const auto text = "h1-mod: " VERSION;
const auto text = "h1-mod (Aurora): " VERSION;
#endif
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{};
rect.x = 0;
@ -68,7 +68,7 @@ namespace branding
return;
}
scheduler::loop(draw_branding, scheduler::pipeline::renderer);
scheduler::loop(draw_branding, scheduler::renderer);
ui_get_formatted_build_number_hook.create(
SELECT_VALUE(0x406EC0_b, 0x1DF300_b), ui_get_formatted_build_number_stub);

View File

@ -596,6 +596,7 @@ namespace demonware
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(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
// Remove some while loop that freezes the rendering for a few secs while connecting

View File

@ -88,7 +88,7 @@ namespace download
}, scheduler::pipeline::lui);
}
console::debug("Download progress: %lli/%lli\n", progress, total);
//console::debug("Download progress: %lli/%lli\n", progress, total);
if (download_aborted())
{
return -1;

View File

@ -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()
{
if (!game::environment::is_sp())
{
mp::reallocate_asset_pools();
}
else
{
sp::reallocate_asset_pools();
}
}
utils::hook::detour db_link_x_asset_entry_hook;

View File

@ -16,9 +16,12 @@ namespace fps
namespace
{
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_drawping;
game::dvar_t* cg_drawfps = nullptr;
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_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()
{
perf_update();
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()
@ -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_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);
}
};
}

View File

@ -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
{
@ -172,6 +172,8 @@ namespace gsc
loaded_scripts[file_name] = script_file_ptr;
console::info("Loaded custom gsc '%s.gsc'", real_name.data());
return script_file_ptr;
}
catch (const std::exception& e)
@ -237,13 +239,13 @@ namespace gsc
if (main_handle)
{
console::debug("Loaded '%s::main'\n", name.data());
console::info("Loaded '%s::main'\n", name.data());
main_handles[name] = main_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;
}
}
@ -369,7 +371,7 @@ namespace gsc
{
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));
}
}
@ -378,7 +380,7 @@ namespace gsc
{
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));
}
}

View File

@ -1,37 +1,125 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "console.hpp"
#include "materials.hpp"
#include "motd.hpp"
#include "scheduler.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
{
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
{
public:
void post_load() override
{
std::thread([]
{
//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())
if (!game::environment::is_mp())
{
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)

View 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();
}

View File

@ -249,7 +249,7 @@ namespace party
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)
@ -261,7 +261,12 @@ namespace party
{
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;
@ -301,7 +306,7 @@ namespace party
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;
@ -316,7 +321,7 @@ namespace party
}
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;

View File

@ -1,13 +1,14 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "server_list.hpp"
#include "command.hpp"
#include "console.hpp"
#include "fastfiles.hpp"
#include "localized_strings.hpp"
#include "network.hpp"
#include "scheduler.hpp"
#include "party.hpp"
#include "console.hpp"
#include "command.hpp"
#include "scheduler.hpp"
#include "server_list.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
@ -137,7 +138,20 @@ namespace server_list
case 0:
return servers[i].host_name.empty() ? "" : servers[i].host_name.data();
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:
{
const auto client_count = servers[i].clients - servers[i].bots;
@ -147,7 +161,18 @@ namespace server_list
case 3:
return servers[i].game_type.empty() ? "" : servers[i].game_type.data();
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:
return servers[i].is_private ? "1" : "0";
case 6:
@ -368,7 +393,7 @@ namespace server_list
server_info server{};
server.address = address;
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.mod_name = info.get("fs_game");
server.play_mode = playmode;

View File

@ -266,10 +266,8 @@ namespace weapon
// precache all weapons that are loaded in zones
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
xmodel_get_bone_index_hook.create(0x5C82B0_b, xmodel_get_bone_index_stub);
#endif
// make custom weapon index mismatch not drop in CG_SetupCustomWeapon
utils::hook::call(0x11B9AF_b, cw_mismatch_error_stub);

View File

@ -1,5 +1,8 @@
#include <std_include.hpp>
#include "component/motd.hpp"
#include "component/scheduler.hpp"
#include <utils/http.hpp>
#include "../services.hpp"
@ -14,67 +17,21 @@ namespace demonware
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());
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();
}

View File

@ -199,7 +199,7 @@ namespace game
WEAK symbol<void(XAssetType type, void(__cdecl* func)(XAssetHeader, void*), const void* inData, bool includeOverride)>
DB_EnumXAssets_Internal{0x1F0BF0, 0x394C60};
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,
int createDefault)> DB_FindXAssetHeader{0x1F1120, 0x3950C0};
WEAK symbol<void(void* levelLoad, const char* name,

View File

@ -74,9 +74,9 @@ BEGIN
VALUE "FileDescription", "H1-Mod"
VALUE "FileVersion", VERSION_FILE
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 "Info", "https://h1.gg"
VALUE "Info", "https://auroramod.dev"
VALUE "OriginalFilename", "h1-mod.exe"
VALUE "ProductName", "h1-mod"
VALUE "ProductVersion", VERSION_PRODUCT

View File

@ -492,7 +492,7 @@
<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://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>
This project is based on work from <a

View File

@ -1,8 +1,20 @@
#include "hook.hpp"
#include "string.hpp"
#include <map>
#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)
{
auto mem = ptr_abs(off);
@ -14,22 +26,164 @@ namespace utils::hook
{
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:
_()
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()
@ -43,12 +197,26 @@ namespace utils::hook
this->push(rsi);
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()
{
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(rsi);
@ -94,19 +262,26 @@ namespace utils::hook
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)
{
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()
{
this->create(place, target);
}
@ -116,13 +291,19 @@ namespace utils::hook
this->clear();
}
void detour::enable() const
void detour::enable()
{
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_);
}
@ -148,11 +329,23 @@ namespace utils::hook
{
if (this->place_)
{
this->un_move();
MH_RemoveHook(this->place_);
}
this->place_ = 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
@ -160,20 +353,29 @@ namespace utils::hook
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);
if (!ptr) return false;
if (!ptr) return {};
DWORD protect;
VirtualProtect(ptr, sizeof(*ptr), PAGE_EXECUTE_READWRITE, &protect);
*ptr = stub;
std::swap(*ptr, stub);
VirtualProtect(ptr, sizeof(*ptr), protect, &protect);
return true;
return { {ptr, stub} };
}
void nop(void* place, const size_t length)
@ -208,23 +410,50 @@ namespace utils::hook
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)
{
const int64_t diff = size_t(data) - (size_t(pointer) + offset);
const auto small_diff = int32_t(diff);
return diff != int64_t(small_diff);
return is_relatively_far(reinterpret_cast<size_t>(pointer), reinterpret_cast<size_t>(data), offset);
}
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)
{
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);
set<uint8_t>(patch_pointer, 0xE8);
set<int32_t>(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5)));
uint8_t copy_data[5];
copy_data[0] = 0xE8;
*reinterpret_cast<int32_t*>(&copy_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)
@ -237,39 +466,67 @@ namespace utils::hook
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[] = {
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))
{
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);
if (use_far)
{
copy(patch_pointer, jump_data, sizeof(jump_data));
copy(patch_pointer + 2, &data, sizeof(data));
if (use_safe)
{
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
{
set<uint8_t>(patch_pointer, 0xE9);
set<int32_t>(patch_pointer + 1, int32_t(size_t(data) - (size_t(pointer) + 5)));
uint8_t copy_data[5];
copy_data[0] = 0xE9;
*reinterpret_cast<int32_t*>(&copy_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)
@ -284,24 +541,70 @@ namespace utils::hook
asm_function(a);
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;
}
void inject(void* pointer, const void* data)
void inject(size_t pointer, size_t data)
{
if (is_relatively_far(pointer, data, 4))
{
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)
{
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)
@ -314,31 +617,4 @@ namespace utils::hook
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);
}
}
}
}
}

View File

@ -13,20 +13,20 @@ namespace utils::hook
{
namespace detail
{
template<size_t entries>
template <size_t Entries>
std::vector<size_t(*)()> get_iota_functions()
{
if constexpr (entries == 0)
if constexpr (Entries == 0)
{
std::vector<size_t(*)()> functions;
return functions;
}
else
{
auto functions = get_iota_functions<entries - 1>();
auto functions = get_iota_functions<Entries - 1>();
functions.emplace_back([]()
{
return entries - 1;
return Entries - 1;
});
return functions;
}
@ -39,8 +39,8 @@ namespace utils::hook
// Example:
// ID3D11Device* device = ...
// auto entry = get_vtable_entry(device, &ID3D11Device::CreateTexture2D);
template <size_t entries = 100, typename Class, typename T, typename... Args>
void** get_vtable_entry(Class* obj, T (Class::* entry)(Args ...))
template <size_t Entries = 100, typename Class, typename T, typename... Args>
void** get_vtable_entry(Class* obj, T(Class::* entry)(Args ...))
{
union
{
@ -50,11 +50,11 @@ namespace utils::hook
func = entry;
auto iota_functions = detail::get_iota_functions<entries>();
auto iota_functions = detail::get_iota_functions<Entries>();
auto* object = iota_functions.data();
using FakeFunc = size_t(__thiscall*)(void* self);
auto index = static_cast<FakeFunc>(pointer)(&object);
using fake_func = size_t(__thiscall*)(void* self);
auto index = static_cast<fake_func>(pointer)(&object);
void** obj_v_table = *reinterpret_cast<void***>(obj);
return &obj_v_table[index];
@ -88,7 +88,7 @@ namespace utils::hook
class detour
{
public:
detour() = default;
detour();
detour(void* place, void* target);
detour(size_t place, void* target);
~detour();
@ -102,13 +102,15 @@ namespace utils::hook
{
if (this != &other)
{
this->~detour();
this->clear();
this->place_ = other.place_;
this->original_ = other.original_;
this->moved_data_ = other.moved_data_;
other.place_ = nullptr;
other.original_ = nullptr;
other.moved_data_ = {};
}
return *this;
@ -117,13 +119,17 @@ namespace utils::hook
detour(const detour&) = delete;
detour& operator=(const detour&) = delete;
void enable() const;
void disable() const;
void enable();
void disable();
void create(void* place, void* target);
void create(size_t place, void* target);
void clear();
void move();
void* get_place() const;
template <typename T>
T* get() const
{
@ -139,11 +145,15 @@ namespace utils::hook
[[nodiscard]] void* get_original() const;
private:
std::vector<uint8_t> moved_data_{};
void* place_{};
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(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(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(size_t pointer, size_t data, int offset = 5);
void call(void* pointer, void* data);
void call(size_t pointer, void* data);
void call(size_t pointer, size_t data);
void jump(void* pointer, void* data, bool use_far = false);
void jump(size_t pointer, void* data, bool use_far = false);
void jump(size_t pointer, size_t 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, bool use_safe = 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 inject(void* 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>
T extract(void* address)
@ -177,19 +195,13 @@ namespace utils::hook
void* follow_branch(void* address);
template <typename T>
static void set(void* place, T value)
static void set(void* place, T value = false)
{
DWORD old_protect;
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));
copy(place, &value, sizeof(value));
}
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);
}
@ -206,8 +218,6 @@ namespace utils::hook
return static_cast<T(*)(Args ...)>(func)(args...);
}
uint8_t* allocate_somewhere_near(const void* base_address, const size_t size);
template <size_t Base>
void* allocate_far_jump()
{
@ -255,11 +265,4 @@ namespace utils::hook
const auto pos = create_far_jump<Base>(dest);
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);
}
}
}

View File

@ -4,6 +4,14 @@
#include <intrin.h>
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
namespace utils::hook
{
void signature::load_pattern(const std::string& pattern)
@ -29,7 +37,7 @@ namespace utils::hook
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));
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);
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)
{
@ -93,17 +101,17 @@ namespace utils::hook
if (j == this->mask_.size())
{
result.push_back(size_t(address));
result.push_back(address);
}
}
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;
__declspec(align(16)) char desired_mask[16] = {0};
std::vector<uint8_t*> result;
__declspec(align(16)) char desired_mask[16] = { 0 };
for (size_t i = 0; i < this->mask_.size(); i++)
{
@ -118,14 +126,14 @@ namespace utils::hook
const auto address = start + i;
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()),
_SIDD_CMP_EQUAL_EACH);
_SIDD_CMP_EQUAL_EACH);
const auto matches = _mm_and_si128(mask, comparison);
const auto equivalence = _mm_xor_si128(mask, matches);
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
{
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
@ -156,7 +164,7 @@ namespace utils::hook
const auto grid = range / cores;
std::mutex mutex;
std::vector<size_t> result;
std::vector<uint8_t*> result;
std::vector<std::thread> threads;
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;
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;
std::lock_guard _(mutex);
@ -185,7 +193,7 @@ namespace utils::hook
}
std::sort(result.begin(), result.end());
return {std::move(result)};
return { std::move(result) };
}
bool signature::has_sse_support() const

View File

@ -7,33 +7,9 @@ namespace utils::hook
class signature final
{
public:
class signature_result
{
public:
signature_result(std::vector<size_t>&& matches) : matches_(std::move(matches))
{
}
using signature_result = std::vector<uint8_t*>;
[[nodiscard]] uint8_t* get(const size_t index) const
{
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 = {})
explicit signature(const std::string& pattern, const nt::library& library = {})
: 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_serial() const;
std::vector<size_t> process_range(uint8_t* start, size_t length) const;
std::vector<size_t> 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(uint8_t* start, size_t length) const;
signature_result process_range_linear(uint8_t* start, size_t length) const;
signature_result process_range_vectorized(uint8_t* start, size_t length) const;
bool has_sse_support() const;
};