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: improve REL and Relocation #14

Merged
merged 5 commits into from
May 17, 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
2 changes: 1 addition & 1 deletion .github/workflows/main_ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Main CI
name: build

on:
push:
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/maintenance.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
name: Scripted maintenance
name: maintenance

on: [push, pull_request_target]
on: [push]

jobs:
maintenance:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.head_ref }}

- uses: actions/setup-python@v2
with:
Expand Down
11 changes: 11 additions & 0 deletions CommonLibF4/cmake/sourcelist.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,15 @@ set(SOURCES
include/RE/VTABLE_IDs.h
include/RE/msvc/memory.h
include/RE/msvc/typeinfo.h
include/REL/ID.h
include/REL/IDDB.h
include/REL/Module.h
include/REL/Offset.h
include/REL/Offset2ID.h
include/REL/REL.h
include/REL/Relocation.h
include/REL/Segment.h
include/REL/Version.h
include/REX/W32.h
include/REX/W32/ADVAPI32.h
include/REX/W32/BASE.h
Expand Down Expand Up @@ -413,7 +421,10 @@ set(SOURCES
src/RE/NetImmerse/NiPoint3.cpp
src/RE/NetImmerse/NiRect.cpp
src/RE/Scaleform/GFx/GFx_Player.cpp
src/REL/IDDB.cpp
src/REL/Module.cpp
src/REL/Relocation.cpp
src/REL/Version.cpp
src/REX/W32/ADVAPI32.cpp
src/REX/W32/BCRYPT.cpp
src/REX/W32/D3D11.cpp
Expand Down
3 changes: 0 additions & 3 deletions CommonLibF4/include/F4SE/API.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ namespace F4SE
class ObjectInterface;
class TrampolineInterface;

class Trampoline;

void Init(const LoadInterface* a_intfc, bool a_log = true) noexcept;

[[nodiscard]] std::string_view GetPluginName() noexcept;
Expand All @@ -38,6 +36,5 @@ namespace F4SE
[[nodiscard]] const ObjectInterface* GetObjectInterface() noexcept;
[[nodiscard]] const TrampolineInterface* GetTrampolineInterface() noexcept;

[[nodiscard]] Trampoline& GetTrampoline() noexcept;
void AllocTrampoline(std::size_t a_size) noexcept;
}
2 changes: 1 addition & 1 deletion CommonLibF4/include/F4SE/Impl/PCH.h
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ namespace REL
namespace stl = F4SE::stl;
}

#include "REL/Relocation.h"
#include "REL/REL.h"

#include "RE/NiRTTI_IDs.h"
#include "RE/RTTI_IDs.h"
Expand Down
182 changes: 15 additions & 167 deletions CommonLibF4/include/F4SE/Trampoline.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,6 @@ namespace Xbyak

namespace F4SE
{
namespace detail
{
[[nodiscard]] constexpr std::size_t roundup(std::size_t a_number, 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(std::size_t a_number, 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 @@ -62,32 +35,9 @@ namespace F4SE
return *this;
}

void create(std::size_t a_size) { return create(a_size, nullptr); }
void create(std::size_t a_size, void* a_module = nullptr);

void create(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::text);
a_module = text.pointer<std::byte>() + text.size();
}

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, std::size_t a_size) { set_trampoline(a_trampoline, a_size, {}); }

void set_trampoline(void* a_trampoline, std::size_t a_size, deleter_type a_deleter)
void set_trampoline(void* a_trampoline, std::size_t a_size, deleter_type a_deleter = {})
{
auto trampoline = static_cast<std::byte*>(a_trampoline);
if (trampoline) {
Expand Down Expand Up @@ -179,122 +129,10 @@ namespace F4SE

private:
[[nodiscard]] static void* do_create(std::size_t a_size, std::uintptr_t a_address);
[[nodiscard]] void* do_allocate(std::size_t a_size);

[[nodiscard]] static bool in_range(std::ptrdiff_t a_disp)
{
constexpr auto min = std::numeric_limits<std::int32_t>::min();
constexpr auto max = std::numeric_limits<std::int32_t>::max();

return min <= a_disp && a_disp <= max;
}

[[nodiscard]] void* do_allocate(std::size_t a_size)
{
if (a_size > free_size()) {
stl::report_and_fail("Failed to handle allocation request"sv);
}

auto mem = _data + _size;
_size += a_size;

return mem;
}

void write_5branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_opcode)
{
#pragma pack(push, 1)
struct SrcAssembly
{
// jmp/call [rip + imm32]
std::uint8_t opcode{ 0 }; // 0 - 0xE9/0xE8
std::int32_t disp{ 0 }; // 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 }; // 0 - 0xFF
std::uint8_t modrm{ 0 }; // 1 - 0x25
std::int32_t disp{ 0 }; // 2 - 0x00000000
std::uint64_t addr{ 0 }; // 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 = nullptr;
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 = static_cast<std::int32_t>(0);
mem->addr = static_cast<std::uint64_t>(a_dst);
}

void write_6branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_modrm)
{
#pragma pack(push, 1)
struct Assembly
{
// jmp/call [rip + imm32]
std::uint8_t opcode{ 0 }; // 0 - 0xFF
std::uint8_t modrm{ 0 }; // 1 - 0x25/0x15
std::int32_t disp{ 0 }; // 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 = nullptr;
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(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_opcode);
void write_6branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_modrm);

template <std::size_t N>
[[nodiscard]] std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_data)
Expand Down Expand Up @@ -347,6 +185,14 @@ namespace F4SE
_size = 0;
}

[[nodiscard]] static bool in_range(std::ptrdiff_t a_disp)
{
constexpr auto min = std::numeric_limits<std::int32_t>::min();
constexpr auto max = std::numeric_limits<std::int32_t>::max();

return min <= a_disp && a_disp <= max;
}

std::map<std::uintptr_t, std::byte*> _5branches;
std::map<std::uintptr_t, std::byte*> _6branches;
std::string _name{ "Default Trampoline"sv };
Expand All @@ -355,4 +201,6 @@ namespace F4SE
std::size_t _capacity{ 0 };
std::size_t _size{ 0 };
};

[[nodiscard]] Trampoline& GetTrampoline() noexcept;
}
32 changes: 32 additions & 0 deletions CommonLibF4/include/REL/ID.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include "REL/IDDB.h"
#include "REL/Module.h"

namespace REL
{
class ID
{
public:
constexpr ID() noexcept = default;

explicit constexpr ID(std::uint64_t a_id) noexcept :
_id(a_id)
{}

constexpr ID& operator=(std::uint64_t a_id) noexcept
{
_id = a_id;
return *this;
}

[[nodiscard]] std::uintptr_t address() const { return base() + offset(); }
[[nodiscard]] constexpr std::uint64_t id() const noexcept { return _id; }
[[nodiscard]] std::size_t offset() const { return IDDB::get().id2offset(_id); }

private:
[[nodiscard]] static std::uintptr_t base() { return Module::get().base(); }

std::uint64_t _id{ static_cast<std::uint64_t>(-1) };
};
}
60 changes: 60 additions & 0 deletions CommonLibF4/include/REL/IDDB.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#pragma once

namespace REL
{
class IDDB
{
private:
struct mapping_t
{
std::uint64_t id;
std::uint64_t offset;
};

public:
IDDB(const IDDB&) = delete;
IDDB(IDDB&&) = delete;

IDDB& operator=(const IDDB&) = delete;
IDDB& operator=(IDDB&&) = delete;

[[nodiscard]] static IDDB& get()
{
static IDDB singleton;
return singleton;
}

[[nodiscard]] std::size_t id2offset(std::uint64_t a_id) const
{
if (_id2offset.empty()) {
stl::report_and_fail("data is empty"sv);
}

const mapping_t elem{ a_id, 0 };
const auto it = std::lower_bound(
_id2offset.begin(),
_id2offset.end(),
elem,
[](auto&& a_lhs, auto&& a_rhs) {
return a_lhs.id < a_rhs.id;
});
if (it == _id2offset.end()) {
stl::report_and_fail("id not found"sv);
}

return static_cast<std::size_t>(it->offset);
}

protected:
friend class Offset2ID;

[[nodiscard]] std::span<const mapping_t> get_id2offset() const noexcept { return _id2offset; }

private:
IDDB();
~IDDB() = default;

mmio::mapped_file_source _mmap;
std::span<const mapping_t> _id2offset;
};
}
Loading
Loading