From adf4fcc7b51bafcd855e68b3c87dfaa703945818 Mon Sep 17 00:00:00 2001 From: Qudix <17361645+Qudix@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:29:02 -0500 Subject: [PATCH 1/3] refactor: trampoline --- include/SFSE/API.h | 3 - include/SFSE/Trampoline.h | 167 ++------------------------------------ src/SFSE/API.cpp | 7 +- src/SFSE/Trampoline.cpp | 163 +++++++++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 170 deletions(-) diff --git a/include/SFSE/API.h b/include/SFSE/API.h index 68b57526..1674e766 100644 --- a/include/SFSE/API.h +++ b/include/SFSE/API.h @@ -2,7 +2,6 @@ #include "SFSE/Impl/Stubs.h" #include "SFSE/Interfaces.h" -#include "SFSE/Trampoline.h" #define SFSEAPI __cdecl @@ -28,8 +27,6 @@ namespace SFSE const MenuInterface* GetMenuInterface() noexcept; const TaskInterface* GetTaskInterface() noexcept; - Trampoline& GetTrampoline(); - void AllocTrampoline(std::size_t a_size, bool a_trySFSEReserve = true); void SetPapyrusCallback(const std::function a_callback, bool a_trySFSEReserve = true); diff --git a/include/SFSE/Trampoline.h b/include/SFSE/Trampoline.h index d7b761c5..12a65ec7 100644 --- a/include/SFSE/Trampoline.h +++ b/include/SFSE/Trampoline.h @@ -9,29 +9,6 @@ namespace Xbyak namespace SFSE { - namespace detail - { - [[nodiscard]] constexpr std::size_t roundup(const std::size_t a_number, const std::size_t a_multiple) noexcept - { - if (a_multiple == 0) { - return 0; - } - - const auto remainder = a_number % a_multiple; - return remainder == 0 ? a_number : a_number + a_multiple - remainder; - } - - [[nodiscard]] constexpr std::size_t rounddown(const std::size_t a_number, const std::size_t a_multiple) noexcept - { - if (a_multiple == 0) { - return 0; - } - - const auto remainder = a_number % a_multiple; - return remainder == 0 ? a_number : a_number - remainder; - } - } - class Trampoline { public: @@ -58,46 +35,9 @@ namespace SFSE return *this; } - void create(const std::size_t a_size) { return create(a_size, nullptr); } - - void create(const std::size_t a_size, void* a_module) - { - if (a_size == 0) { - stl::report_and_fail("cannot create a trampoline with a zero size"sv); - } - - if (!a_module) { - const auto text = REL::Module::get().segment(REL::Segment::textx); - a_module = text.pointer() + text.size(); - } - - const auto mem = do_create(a_size, reinterpret_cast(a_module)); - if (!mem) { - stl::report_and_fail("failed to create trampoline"sv); - } - - set_trampoline(mem, a_size, [](void* a_mem, std::size_t) { REX::W32::VirtualFree(a_mem, 0, REX::W32::MEM_RELEASE); }); - } - - void set_trampoline(void* a_trampoline, const std::size_t a_size) { set_trampoline(a_trampoline, a_size, {}); } - - void set_trampoline(void* a_trampoline, const std::size_t a_size, deleter_type a_deleter) - { - const auto trampoline = static_cast(a_trampoline); - if (trampoline) { - constexpr auto INT3 = 0xCC; - std::memset(trampoline, INT3, a_size); - } - - release(); + void create(const std::size_t a_size, void* a_module = nullptr); - _deleter = std::move(a_deleter); - _data = trampoline; - _capacity = a_size; - _size = 0; - - log_stats(); - } + void set_trampoline(void* a_trampoline, const std::size_t a_size, deleter_type a_deleter = {}); [[nodiscard]] void* allocate(const std::size_t a_size) { @@ -117,11 +57,8 @@ namespace SFSE } [[nodiscard]] constexpr std::size_t empty() const noexcept { return _capacity == 0; } - [[nodiscard]] constexpr std::size_t capacity() const noexcept { return _capacity; } - [[nodiscard]] constexpr std::size_t allocated_size() const noexcept { return _size; } - [[nodiscard]] constexpr std::size_t free_size() const noexcept { return _capacity - _size; } template @@ -189,102 +126,8 @@ namespace SFSE return mem; } - void write_5branch(const std::uintptr_t a_src, std::uintptr_t a_dst, const std::uint8_t a_opcode) - { -#pragma pack(push, 1) - - struct SrcAssembly - { - // jmp/call [rip + imm32] - std::uint8_t opcode; // 0 - 0xE9/0xE8 - std::int32_t disp; // 1 - }; - - static_assert(offsetof(SrcAssembly, opcode) == 0x0); - static_assert(offsetof(SrcAssembly, disp) == 0x1); - static_assert(sizeof(SrcAssembly) == 0x5); - - // FF /4 - // JMP r/m64 - struct TrampolineAssembly - { - // jmp [rip] - std::uint8_t jmp; // 0 - 0xFF - std::uint8_t modrm; // 1 - 0x25 - std::int32_t disp; // 2 - 0x00000000 - std::uint64_t addr; // 6 - [rip] - }; - - static_assert(offsetof(TrampolineAssembly, jmp) == 0x0); - static_assert(offsetof(TrampolineAssembly, modrm) == 0x1); - static_assert(offsetof(TrampolineAssembly, disp) == 0x2); - static_assert(offsetof(TrampolineAssembly, addr) == 0x6); - static_assert(sizeof(TrampolineAssembly) == 0xE); -#pragma pack(pop) - - TrampolineAssembly* mem; - if (const auto it = _5branches.find(a_dst); it != _5branches.end()) { - mem = reinterpret_cast(it->second); - } else { - mem = allocate(); - _5branches.emplace(a_dst, reinterpret_cast(mem)); - } - - const auto disp = reinterpret_cast(mem) - reinterpret_cast(a_src + sizeof(SrcAssembly)); - if (!in_range(disp)) { // the trampoline should already be in range, so this should never happen - stl::report_and_fail("displacement is out of range"sv); - } - - SrcAssembly assembly; - assembly.opcode = a_opcode; - assembly.disp = static_cast(disp); - REL::safe_write(a_src, &assembly, sizeof(assembly)); - - mem->jmp = static_cast(0xFF); - mem->modrm = static_cast(0x25); - mem->disp = 0; - mem->addr = a_dst; - } - - void write_6branch(const std::uintptr_t a_src, std::uintptr_t a_dst, const std::uint8_t a_modrm) - { -#pragma pack(push, 1) - - struct Assembly - { - // jmp/call [rip + imm32] - std::uint8_t opcode; // 0 - 0xFF - std::uint8_t modrm; // 1 - 0x25/0x15 - std::int32_t disp; // 2 - }; - - static_assert(offsetof(Assembly, opcode) == 0x0); - static_assert(offsetof(Assembly, modrm) == 0x1); - static_assert(offsetof(Assembly, disp) == 0x2); - static_assert(sizeof(Assembly) == 0x6); -#pragma pack(pop) - - std::uintptr_t* mem; - if (const auto it = _6branches.find(a_dst); it != _6branches.end()) { - mem = reinterpret_cast(it->second); - } else { - mem = allocate(); - _6branches.emplace(a_dst, reinterpret_cast(mem)); - } - - const auto disp = reinterpret_cast(mem) - reinterpret_cast(a_src + sizeof(Assembly)); - if (!in_range(disp)) { // the trampoline should already be in range, so this should never happen - stl::report_and_fail("displacement is out of range"sv); - } - - Assembly assembly; - assembly.opcode = static_cast(0xFF); - assembly.modrm = a_modrm; - assembly.disp = static_cast(disp); - REL::safe_write(a_src, &assembly, sizeof(assembly)); - - *mem = a_dst; - } + void write_5branch(const std::uintptr_t a_src, std::uintptr_t a_dst, const std::uint8_t a_opcode); + void write_6branch(const std::uintptr_t a_src, std::uintptr_t a_dst, const std::uint8_t a_modrm); template [[nodiscard]] constexpr std::uintptr_t write_branch(const std::uintptr_t a_src, const std::uintptr_t a_dst, const std::uint8_t a_data) @@ -353,4 +196,6 @@ namespace SFSE std::size_t _capacity{}; std::size_t _size{}; }; + + Trampoline& GetTrampoline(); } diff --git a/src/SFSE/API.cpp b/src/SFSE/API.cpp index bc84bf63..38c8c60c 100644 --- a/src/SFSE/API.cpp +++ b/src/SFSE/API.cpp @@ -1,6 +1,7 @@ #include "SFSE/API.h" #include "SFSE/Logger.h" +#include "SFSE/Trampoline.h" namespace SFSE { @@ -149,12 +150,6 @@ namespace SFSE return detail::APIStorage::get().taskInterface; } - Trampoline& GetTrampoline() - { - static Trampoline trampoline; - return trampoline; - } - void AllocTrampoline(const std::size_t a_size, const bool a_trySFSEReserve) { auto& trampoline = GetTrampoline(); diff --git a/src/SFSE/Trampoline.cpp b/src/SFSE/Trampoline.cpp index eb457124..2019c952 100644 --- a/src/SFSE/Trampoline.cpp +++ b/src/SFSE/Trampoline.cpp @@ -17,6 +17,66 @@ namespace SFSE { + namespace detail + { + [[nodiscard]] constexpr std::size_t roundup(const std::size_t a_number, const std::size_t a_multiple) noexcept + { + if (a_multiple == 0) { + return 0; + } + + const auto remainder = a_number % a_multiple; + return remainder == 0 ? a_number : a_number + a_multiple - remainder; + } + + [[nodiscard]] constexpr std::size_t rounddown(const std::size_t a_number, const std::size_t a_multiple) noexcept + { + if (a_multiple == 0) { + return 0; + } + + const auto remainder = a_number % a_multiple; + return remainder == 0 ? a_number : a_number - remainder; + } + } + + void Trampoline::create(const std::size_t a_size, void* a_module) + { + if (a_size == 0) { + stl::report_and_fail("cannot create a trampoline with a zero size"sv); + } + + if (!a_module) { + const auto text = REL::Module::get().segment(REL::Segment::textx); + a_module = text.pointer() + text.size(); + } + + const auto mem = do_create(a_size, reinterpret_cast(a_module)); + if (!mem) { + stl::report_and_fail("failed to create trampoline"sv); + } + + set_trampoline(mem, a_size, [](void* a_mem, std::size_t) { REX::W32::VirtualFree(a_mem, 0, REX::W32::MEM_RELEASE); }); + } + + void Trampoline::set_trampoline(void* a_trampoline, const std::size_t a_size, deleter_type a_deleter) + { + const auto trampoline = static_cast(a_trampoline); + if (trampoline) { + constexpr auto INT3 = 0xCC; + std::memset(trampoline, INT3, a_size); + } + + release(); + + _deleter = std::move(a_deleter); + _data = trampoline; + _capacity = a_size; + _size = 0; + + log_stats(); + } + #ifdef SFSE_SUPPORT_XBYAK void* Trampoline::allocate(Xbyak::CodeGenerator& a_code) { @@ -68,9 +128,112 @@ namespace SFSE return nullptr; } + void Trampoline::write_5branch(const std::uintptr_t a_src, std::uintptr_t a_dst, const std::uint8_t a_opcode) + { +#pragma pack(push, 1) + + struct SrcAssembly + { + // jmp/call [rip + imm32] + std::uint8_t opcode; // 0 - 0xE9/0xE8 + std::int32_t disp; // 1 + }; + + static_assert(offsetof(SrcAssembly, opcode) == 0x0); + static_assert(offsetof(SrcAssembly, disp) == 0x1); + static_assert(sizeof(SrcAssembly) == 0x5); + + // FF /4 + // JMP r/m64 + struct TrampolineAssembly + { + // jmp [rip] + std::uint8_t jmp; // 0 - 0xFF + std::uint8_t modrm; // 1 - 0x25 + std::int32_t disp; // 2 - 0x00000000 + std::uint64_t addr; // 6 - [rip] + }; + + static_assert(offsetof(TrampolineAssembly, jmp) == 0x0); + static_assert(offsetof(TrampolineAssembly, modrm) == 0x1); + static_assert(offsetof(TrampolineAssembly, disp) == 0x2); + static_assert(offsetof(TrampolineAssembly, addr) == 0x6); + static_assert(sizeof(TrampolineAssembly) == 0xE); +#pragma pack(pop) + + TrampolineAssembly* mem; + if (const auto it = _5branches.find(a_dst); it != _5branches.end()) { + mem = reinterpret_cast(it->second); + } else { + mem = allocate(); + _5branches.emplace(a_dst, reinterpret_cast(mem)); + } + + const auto disp = reinterpret_cast(mem) - reinterpret_cast(a_src + sizeof(SrcAssembly)); + if (!in_range(disp)) { // the trampoline should already be in range, so this should never happen + stl::report_and_fail("displacement is out of range"sv); + } + + SrcAssembly assembly; + assembly.opcode = a_opcode; + assembly.disp = static_cast(disp); + REL::safe_write(a_src, &assembly, sizeof(assembly)); + + mem->jmp = static_cast(0xFF); + mem->modrm = static_cast(0x25); + mem->disp = 0; + mem->addr = a_dst; + } + + void Trampoline::write_6branch(const std::uintptr_t a_src, std::uintptr_t a_dst, const std::uint8_t a_modrm) + { +#pragma pack(push, 1) + + struct Assembly + { + // jmp/call [rip + imm32] + std::uint8_t opcode; // 0 - 0xFF + std::uint8_t modrm; // 1 - 0x25/0x15 + std::int32_t disp; // 2 + }; + + static_assert(offsetof(Assembly, opcode) == 0x0); + static_assert(offsetof(Assembly, modrm) == 0x1); + static_assert(offsetof(Assembly, disp) == 0x2); + static_assert(sizeof(Assembly) == 0x6); +#pragma pack(pop) + + std::uintptr_t* mem; + if (const auto it = _6branches.find(a_dst); it != _6branches.end()) { + mem = reinterpret_cast(it->second); + } else { + mem = allocate(); + _6branches.emplace(a_dst, reinterpret_cast(mem)); + } + + const auto disp = reinterpret_cast(mem) - reinterpret_cast(a_src + sizeof(Assembly)); + if (!in_range(disp)) { // the trampoline should already be in range, so this should never happen + stl::report_and_fail("displacement is out of range"sv); + } + + Assembly assembly; + assembly.opcode = static_cast(0xFF); + assembly.modrm = a_modrm; + assembly.disp = static_cast(disp); + REL::safe_write(a_src, &assembly, sizeof(assembly)); + + *mem = a_dst; + } + void Trampoline::log_stats() const { auto pct = (static_cast(_size) / static_cast(_capacity)) * 100.0; log::debug("{} => {}B / {}B ({:05.2f}%)"sv, _name, _size, _capacity, pct); } + + Trampoline& GetTrampoline() + { + static Trampoline trampoline; + return trampoline; + } } From 676dd45f4b7ce2ea7ca6941ad6b55cb0d07d985d Mon Sep 17 00:00:00 2001 From: Qudix <17361645+Qudix@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:31:38 -0500 Subject: [PATCH 2/3] feat: `Relocation` --- include/REL/Relocation.h | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/include/REL/Relocation.h b/include/REL/Relocation.h index bcbc8725..9e710f51 100644 --- a/include/REL/Relocation.h +++ b/include/REL/Relocation.h @@ -4,6 +4,8 @@ #include "REL/Module.h" #include "REL/Offset.h" +#include "SFSE/Trampoline.h" + #define REL_MAKE_MEMBER_FUNCTION_POD_TYPE_HELPER_IMPL(a_nopropQual, a_propQual, ...) \ template \ struct member_function_pod_type \ @@ -250,6 +252,67 @@ namespace REL return invoke(get(), std::forward(a_args)...); } + + void write(const void* a_src, std::size_t a_count) + requires(std::same_as) + { + safe_write(address(), a_src, a_count); + } + + template + void write(const U& a_data) + requires(std::same_as) + { + safe_write(address(), std::addressof(a_data), sizeof(U)); + } + + void write(const std::initializer_list a_data) + requires(std::same_as) + { + safe_write(address(), a_data.begin(), a_data.size()); + } + + template + void write(const std::span a_data) + requires(std::same_as) + { + safe_write(address(), a_data.data(), a_data.size_bytes()); + } + + template + std::uintptr_t write_branch(const std::uintptr_t a_dst) + requires(std::same_as) + { + return SFSE::GetTrampoline().write_branch(address(), a_dst); + } + + template + std::uintptr_t write_branch(const F a_dst) + requires(std::same_as) + { + return SFSE::GetTrampoline().write_branch(address(), stl::unrestricted_cast(a_dst)); + } + + template + std::uintptr_t write_call(const std::uintptr_t a_dst) + requires(std::same_as) + { + return SFSE::GetTrampoline().write_call(address(), a_dst); + } + + template + std::uintptr_t write_call(const F a_dst) + requires(std::same_as) + { + return SFSE::GetTrampoline().write_call(address(), stl::unrestricted_cast(a_dst)); + } + + void write_fill(const std::uint8_t a_value, const std::size_t a_count) + requires(std::same_as) + { + safe_fill(address(), a_value, a_count); + } + constexpr std::uintptr_t write_vfunc(const std::size_t a_idx, const std::uintptr_t a_newFunc) requires(std::same_as) { From 92b9491858d11fee610077ae8f76aeb123d2bd10 Mon Sep 17 00:00:00 2001 From: Qudix <17361645+Qudix@users.noreply.github.com> Date: Fri, 27 Sep 2024 05:05:01 -0500 Subject: [PATCH 3/3] refactor: cosmetic --- include/REL/Relocation.h | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/include/REL/Relocation.h b/include/REL/Relocation.h index 9e710f51..75353221 100644 --- a/include/REL/Relocation.h +++ b/include/REL/Relocation.h @@ -222,16 +222,6 @@ namespace REL return *this; } - [[nodiscard]] constexpr value_type get() const // - noexcept(std::is_nothrow_copy_constructible_v) - { - return stl::unrestricted_cast(_address); - } - - [[nodiscard]] constexpr decltype(auto) address() const noexcept { return _address; } - - [[nodiscard]] constexpr std::size_t offset() const noexcept { return _address - base(); } - [[nodiscard]] constexpr decltype(auto) operator*() const noexcept requires(std::is_pointer_v) { @@ -245,13 +235,21 @@ namespace REL } template - constexpr std::invoke_result_t operator()(Args&&... a_args) const // + constexpr std::invoke_result_t operator()(Args&&... a_args) const noexcept(std::is_nothrow_invocable_v) requires(std::invocable) { return invoke(get(), std::forward(a_args)...); } + [[nodiscard]] constexpr decltype(auto) address() const noexcept { return _address; } + [[nodiscard]] constexpr std::size_t offset() const noexcept { return _address - base(); } + + [[nodiscard]] constexpr value_type get() const + noexcept(std::is_nothrow_copy_constructible_v) + { + return stl::unrestricted_cast(_address); + } void write(const void* a_src, std::size_t a_count) requires(std::same_as) @@ -330,7 +328,10 @@ namespace REL } private: - [[nodiscard]] static constexpr std::uintptr_t base() { return Module::get().base(); } + [[nodiscard]] static constexpr std::uintptr_t base() + { + return Module::get().base(); + } std::uintptr_t _address{}; };