Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Relocation helpers #275

Merged
merged 3 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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