Skip to content

Commit

Permalink
feat: Relocation helpers (#275)
Browse files Browse the repository at this point in the history
  • Loading branch information
qudix authored Sep 27, 2024
1 parent 981fd39 commit 7fad73a
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 182 deletions.
88 changes: 76 additions & 12 deletions include/REL/Relocation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class R, class Cls, class... Args> \
struct member_function_pod_type<R (Cls::*)(Args...) __VA_ARGS__ a_nopropQual a_propQual> \
Expand Down Expand Up @@ -220,16 +222,6 @@ namespace REL
return *this;
}

[[nodiscard]] constexpr value_type get() const //
noexcept(std::is_nothrow_copy_constructible_v<value_type>)
{
return stl::unrestricted_cast<value_type>(_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<value_type>)
{
Expand All @@ -243,13 +235,82 @@ namespace REL
}

template <class... Args>
constexpr std::invoke_result_t<const value_type&, Args...> operator()(Args&&... a_args) const //
constexpr std::invoke_result_t<const value_type&, Args...> operator()(Args&&... a_args) const
noexcept(std::is_nothrow_invocable_v<const value_type&, Args...>)
requires(std::invocable<const value_type&, Args...>)
{
return invoke(get(), std::forward<Args>(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<value_type>)
{
return stl::unrestricted_cast<value_type>(_address);
}

void write(const void* a_src, std::size_t a_count)
requires(std::same_as<value_type, std::uintptr_t>)
{
safe_write(address(), a_src, a_count);
}

template <std::integral U>
void write(const U& a_data)
requires(std::same_as<value_type, std::uintptr_t>)
{
safe_write(address(), std::addressof(a_data), sizeof(U));
}

void write(const std::initializer_list<std::uint8_t> a_data)
requires(std::same_as<value_type, std::uintptr_t>)
{
safe_write(address(), a_data.begin(), a_data.size());
}

template <class U>
void write(const std::span<U> a_data)
requires(std::same_as<value_type, std::uintptr_t>)
{
safe_write(address(), a_data.data(), a_data.size_bytes());
}

template <std::size_t N>
std::uintptr_t write_branch(const std::uintptr_t a_dst)
requires(std::same_as<value_type, std::uintptr_t>)
{
return SFSE::GetTrampoline().write_branch<N>(address(), a_dst);
}

template <std::size_t N, class F>
std::uintptr_t write_branch(const F a_dst)
requires(std::same_as<value_type, std::uintptr_t>)
{
return SFSE::GetTrampoline().write_branch<N>(address(), stl::unrestricted_cast<std::uintptr_t>(a_dst));
}

template <std::size_t N>
std::uintptr_t write_call(const std::uintptr_t a_dst)
requires(std::same_as<value_type, std::uintptr_t>)
{
return SFSE::GetTrampoline().write_call<N>(address(), a_dst);
}

template <std::size_t N, class F>
std::uintptr_t write_call(const F a_dst)
requires(std::same_as<value_type, std::uintptr_t>)
{
return SFSE::GetTrampoline().write_call<N>(address(), stl::unrestricted_cast<std::uintptr_t>(a_dst));
}

void write_fill(const std::uint8_t a_value, const std::size_t a_count)
requires(std::same_as<value_type, std::uintptr_t>)
{
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<value_type, std::uintptr_t>)
{
Expand All @@ -267,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{};
};
Expand Down
3 changes: 0 additions & 3 deletions include/SFSE/API.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include "SFSE/Impl/Stubs.h"
#include "SFSE/Interfaces.h"
#include "SFSE/Trampoline.h"

#define SFSEAPI __cdecl

Expand All @@ -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<void(RE::BSScript::IVirtualMachine**)> a_callback, bool a_trySFSEReserve = true);
Expand Down
167 changes: 6 additions & 161 deletions include/SFSE/Trampoline.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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<std::byte>() + text.size();
}

const auto mem = do_create(a_size, reinterpret_cast<std::uintptr_t>(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<std::byte*>(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)
{
Expand All @@ -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 <std::size_t N>
Expand Down Expand Up @@ -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<TrampolineAssembly*>(it->second);
} else {
mem = allocate<TrampolineAssembly>();
_5branches.emplace(a_dst, reinterpret_cast<std::byte*>(mem));
}

const auto disp = reinterpret_cast<const std::byte*>(mem) - reinterpret_cast<const std::byte*>(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<std::int32_t>(disp);
REL::safe_write(a_src, &assembly, sizeof(assembly));

mem->jmp = static_cast<std::uint8_t>(0xFF);
mem->modrm = static_cast<std::uint8_t>(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<std::uintptr_t*>(it->second);
} else {
mem = allocate<std::uintptr_t>();
_6branches.emplace(a_dst, reinterpret_cast<std::byte*>(mem));
}

const auto disp = reinterpret_cast<const std::byte*>(mem) - reinterpret_cast<const std::byte*>(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<std::uint8_t>(0xFF);
assembly.modrm = a_modrm;
assembly.disp = static_cast<std::int32_t>(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 <std::size_t N>
[[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)
Expand Down Expand Up @@ -353,4 +196,6 @@ namespace SFSE
std::size_t _capacity{};
std::size_t _size{};
};

Trampoline& GetTrampoline();
}
7 changes: 1 addition & 6 deletions src/SFSE/API.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "SFSE/API.h"

#include "SFSE/Logger.h"
#include "SFSE/Trampoline.h"

namespace SFSE
{
Expand Down Expand Up @@ -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();
Expand Down
Loading

0 comments on commit 7fad73a

Please sign in to comment.