Skip to content

Commit

Permalink
Merge pull request #14 from qudix/dev
Browse files Browse the repository at this point in the history
feat: improve `REL` and `Relocation`
  • Loading branch information
shad0wshayd3 authored May 17, 2024
2 parents 5ea83ff + b6a0f20 commit 303e2dd
Show file tree
Hide file tree
Showing 23 changed files with 947 additions and 791 deletions.
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

0 comments on commit 303e2dd

Please sign in to comment.