diff --git a/.github/workflows/main_ci.yml b/.github/workflows/main_ci.yml index e5968d18..172821bb 100644 --- a/.github/workflows/main_ci.yml +++ b/.github/workflows/main_ci.yml @@ -1,4 +1,4 @@ -name: Main CI +name: build on: push: diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml index dc089001..fdd2b381 100644 --- a/.github/workflows/maintenance.yml +++ b/.github/workflows/maintenance.yml @@ -1,6 +1,6 @@ -name: Scripted maintenance +name: maintenance -on: [push, pull_request_target] +on: [push] jobs: maintenance: @@ -8,8 +8,6 @@ jobs: steps: - uses: actions/checkout@v2 - with: - ref: ${{ github.head_ref }} - uses: actions/setup-python@v2 with: diff --git a/CommonLibF4/cmake/sourcelist.cmake b/CommonLibF4/cmake/sourcelist.cmake index 0ba59e38..4b4cfe8c 100644 --- a/CommonLibF4/cmake/sourcelist.cmake +++ b/CommonLibF4/cmake/sourcelist.cmake @@ -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 @@ -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 diff --git a/CommonLibF4/include/F4SE/API.h b/CommonLibF4/include/F4SE/API.h index 6214e2a4..b4019feb 100644 --- a/CommonLibF4/include/F4SE/API.h +++ b/CommonLibF4/include/F4SE/API.h @@ -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; @@ -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; } diff --git a/CommonLibF4/include/F4SE/Impl/PCH.h b/CommonLibF4/include/F4SE/Impl/PCH.h index 21bb5a7e..284fe858 100644 --- a/CommonLibF4/include/F4SE/Impl/PCH.h +++ b/CommonLibF4/include/F4SE/Impl/PCH.h @@ -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" diff --git a/CommonLibF4/include/F4SE/Trampoline.h b/CommonLibF4/include/F4SE/Trampoline.h index 8811f49c..11a32937 100644 --- a/CommonLibF4/include/F4SE/Trampoline.h +++ b/CommonLibF4/include/F4SE/Trampoline.h @@ -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: @@ -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() + text.size(); - } - - 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, 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(a_trampoline); if (trampoline) { @@ -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::min(); - constexpr auto max = std::numeric_limits::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(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 = static_cast(0); - mem->addr = static_cast(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(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(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 [[nodiscard]] std::uintptr_t write_branch(std::uintptr_t a_src, std::uintptr_t a_dst, std::uint8_t a_data) @@ -347,6 +185,14 @@ namespace F4SE _size = 0; } + [[nodiscard]] static bool in_range(std::ptrdiff_t a_disp) + { + constexpr auto min = std::numeric_limits::min(); + constexpr auto max = std::numeric_limits::max(); + + return min <= a_disp && a_disp <= max; + } + std::map _5branches; std::map _6branches; std::string _name{ "Default Trampoline"sv }; @@ -355,4 +201,6 @@ namespace F4SE std::size_t _capacity{ 0 }; std::size_t _size{ 0 }; }; + + [[nodiscard]] Trampoline& GetTrampoline() noexcept; } diff --git a/CommonLibF4/include/REL/ID.h b/CommonLibF4/include/REL/ID.h new file mode 100644 index 00000000..5b4c5b7d --- /dev/null +++ b/CommonLibF4/include/REL/ID.h @@ -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(-1) }; + }; +} diff --git a/CommonLibF4/include/REL/IDDB.h b/CommonLibF4/include/REL/IDDB.h new file mode 100644 index 00000000..ae10ab1a --- /dev/null +++ b/CommonLibF4/include/REL/IDDB.h @@ -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(it->offset); + } + + protected: + friend class Offset2ID; + + [[nodiscard]] std::span get_id2offset() const noexcept { return _id2offset; } + + private: + IDDB(); + ~IDDB() = default; + + mmio::mapped_file_source _mmap; + std::span _id2offset; + }; +} diff --git a/CommonLibF4/include/REL/Module.h b/CommonLibF4/include/REL/Module.h new file mode 100644 index 00000000..a53c180f --- /dev/null +++ b/CommonLibF4/include/REL/Module.h @@ -0,0 +1,62 @@ +#pragma once + +#include "REL/Segment.h" +#include "REL/Version.h" + +namespace REL +{ + class Module + { + public: + Module(const Module&) = delete; + Module(Module&&) = delete; + + Module& operator=(const Module&) = delete; + Module& operator=(Module&&) = delete; + + [[nodiscard]] static Module& get() + { + static Module singleton; + return singleton; + } + + [[nodiscard]] constexpr std::uintptr_t base() const noexcept { return _base; } + [[nodiscard]] stl::zwstring filename() const noexcept { return _filename; } + [[nodiscard]] constexpr Segment segment(Segment::Name a_segment) const noexcept { return _segments[a_segment]; } + [[nodiscard]] constexpr Version version() const noexcept { return _version; } + + [[nodiscard]] void* pointer() const noexcept { return reinterpret_cast(base()); } + + template + [[nodiscard]] T* pointer() const noexcept + { + return static_cast(pointer()); + } + + private: + Module(); + ~Module() noexcept = default; + + void load_segments(); + void load_version(); + + static constexpr auto ENVIRONMENT = L"F4SE_RUNTIME"sv; + + static constexpr std::array SEGMENTS{ + ".text"sv, + ".interpr"sv, + ".idata"sv, + ".rdata"sv, + ".data"sv, + ".pdata"sv, + ".tls"sv + }; + + static inline std::uintptr_t _natvis{ 0 }; + + std::wstring _filename; + std::array _segments; + Version _version; + std::uintptr_t _base{ 0 }; + }; +} diff --git a/CommonLibF4/include/REL/Offset.h b/CommonLibF4/include/REL/Offset.h new file mode 100644 index 00000000..7311ad58 --- /dev/null +++ b/CommonLibF4/include/REL/Offset.h @@ -0,0 +1,30 @@ +#pragma once + +#include "REL/Module.h" + +namespace REL +{ + class Offset + { + public: + constexpr Offset() noexcept = default; + + explicit constexpr Offset(std::size_t a_offset) noexcept : + _offset(a_offset) + {} + + constexpr Offset& operator=(std::size_t a_offset) noexcept + { + _offset = a_offset; + return *this; + } + + [[nodiscard]] std::uintptr_t address() const { return base() + offset(); } + [[nodiscard]] constexpr std::size_t offset() const noexcept { return _offset; } + + private: + [[nodiscard]] static std::uintptr_t base() { return Module::get().base(); } + + std::size_t _offset{ 0 }; + }; +} diff --git a/CommonLibF4/include/REL/Offset2ID.h b/CommonLibF4/include/REL/Offset2ID.h new file mode 100644 index 00000000..49c8da71 --- /dev/null +++ b/CommonLibF4/include/REL/Offset2ID.h @@ -0,0 +1,70 @@ +#pragma once + +#include "REL/IDDB.h" + +namespace REL +{ + class Offset2ID + { + public: + using value_type = IDDB::mapping_t; + using container_type = std::vector; + using size_type = typename container_type::size_type; + using const_iterator = typename container_type::const_iterator; + using const_reverse_iterator = typename container_type::const_reverse_iterator; + + template + explicit Offset2ID(ExecutionPolicy&& a_policy) // NOLINT(bugprone-forwarding-reference-overload) + requires(std::is_execution_policy_v>) + { + const auto id2offset = IDDB::get().get_id2offset(); + _offset2id.reserve(id2offset.size()); + _offset2id.insert(_offset2id.begin(), id2offset.begin(), id2offset.end()); + std::sort(a_policy, _offset2id.begin(), _offset2id.end(), [](auto&& a_lhs, auto&& a_rhs) { + return a_lhs.offset < a_rhs.offset; + }); + } + + Offset2ID() : + Offset2ID(std::execution::sequenced_policy{}) + {} + + [[nodiscard]] std::uint64_t operator()(std::size_t a_offset) const + { + if (_offset2id.empty()) { + stl::report_and_fail("data is empty"sv); + } + + const IDDB::mapping_t elem{ 0, a_offset }; + const auto it = std::lower_bound( + _offset2id.begin(), + _offset2id.end(), + elem, + [](auto&& a_lhs, auto&& a_rhs) { + return a_lhs.offset < a_rhs.offset; + }); + if (it == _offset2id.end()) { + stl::report_and_fail("offset not found"sv); + } + + return it->id; + } + + [[nodiscard]] const_iterator begin() const noexcept { return _offset2id.begin(); } + [[nodiscard]] const_iterator cbegin() const noexcept { return _offset2id.cbegin(); } + + [[nodiscard]] const_iterator end() const noexcept { return _offset2id.end(); } + [[nodiscard]] const_iterator cend() const noexcept { return _offset2id.cend(); } + + [[nodiscard]] const_reverse_iterator rbegin() const noexcept { return _offset2id.rbegin(); } + [[nodiscard]] const_reverse_iterator crbegin() const noexcept { return _offset2id.crbegin(); } + + [[nodiscard]] const_reverse_iterator rend() const noexcept { return _offset2id.rend(); } + [[nodiscard]] const_reverse_iterator crend() const noexcept { return _offset2id.crend(); } + + [[nodiscard]] size_type size() const noexcept { return _offset2id.size(); } + + private: + container_type _offset2id; + }; +} diff --git a/CommonLibF4/include/REL/REL.h b/CommonLibF4/include/REL/REL.h new file mode 100644 index 00000000..bf24bee2 --- /dev/null +++ b/CommonLibF4/include/REL/REL.h @@ -0,0 +1,10 @@ +#pragma once + +#include "REL/ID.h" +#include "REL/IDDB.h" +#include "REL/Module.h" +#include "REL/Offset.h" +#include "REL/Offset2ID.h" +#include "REL/Relocation.h" +#include "REL/Segment.h" +#include "REL/Version.h" diff --git a/CommonLibF4/include/REL/Relocation.h b/CommonLibF4/include/REL/Relocation.h index 1f466fb1..dfac87cc 100644 --- a/CommonLibF4/include/REL/Relocation.h +++ b/CommonLibF4/include/REL/Relocation.h @@ -1,6 +1,10 @@ #pragma once -#include "REX/W32/VERSION.h" +#include "F4SE/Trampoline.h" + +#include "REL/ID.h" +#include "REL/Module.h" +#include "REL/Offset.h" #define REL_MAKE_MEMBER_FUNCTION_POD_TYPE_HELPER_IMPL(a_nopropQual, a_propQual, ...) \ template < \ @@ -60,13 +64,6 @@ namespace REL { - class ID; - class IDManager; - class Module; - class Offset; - class Segment; - class Version; - template class Relocation; @@ -188,19 +185,7 @@ namespace REL } } - inline void safe_write(std::uintptr_t a_dst, const void* a_src, std::size_t a_count) - { - std::uint32_t old{ 0 }; - bool success = REX::W32::VirtualProtect( - reinterpret_cast(a_dst), a_count, REX::W32::PAGE_EXECUTE_READWRITE, std::addressof(old)); - if (success) { - std::memcpy(reinterpret_cast(a_dst), a_src, a_count); - success = REX::W32::VirtualProtect( - reinterpret_cast(a_dst), a_count, old, std::addressof(old)); - } - - assert(success); - } + void safe_write(std::uintptr_t a_dst, const void* a_src, std::size_t a_count); template void safe_write(std::uintptr_t a_dst, const T& a_data) @@ -214,482 +199,9 @@ namespace REL safe_write(a_dst, a_data.data(), a_data.size_bytes()); } - inline void safe_fill(std::uintptr_t a_dst, std::uint8_t a_value, std::size_t a_count) - { - std::uint32_t old{ 0 }; - bool success = REX::W32::VirtualProtect( - reinterpret_cast(a_dst), a_count, REX::W32::PAGE_EXECUTE_READWRITE, std::addressof(old)); - if (success) { - std::fill_n(reinterpret_cast(a_dst), a_count, a_value); - success = REX::W32::VirtualProtect( - reinterpret_cast(a_dst), a_count, old, std::addressof(old)); - } - - assert(success); - } - - class Version - { - public: - using value_type = std::uint16_t; - using reference = value_type&; - using const_reference = const value_type&; - - constexpr Version() noexcept = default; - - explicit constexpr Version(std::array a_version) noexcept : - _impl(a_version) - {} - - constexpr Version(value_type a_v1, value_type a_v2 = 0, value_type a_v3 = 0, value_type a_v4 = 0) noexcept : - _impl{ a_v1, a_v2, a_v3, a_v4 } - {} - - [[nodiscard]] constexpr reference operator[](std::size_t a_idx) noexcept { return _impl[a_idx]; } - [[nodiscard]] constexpr const_reference operator[](std::size_t a_idx) const noexcept { return _impl[a_idx]; } - - [[nodiscard]] constexpr decltype(auto) begin() const noexcept { return _impl.begin(); } - [[nodiscard]] constexpr decltype(auto) cbegin() const noexcept { return _impl.cbegin(); } - [[nodiscard]] constexpr decltype(auto) end() const noexcept { return _impl.end(); } - [[nodiscard]] constexpr decltype(auto) cend() const noexcept { return _impl.cend(); } - - [[nodiscard]] std::strong_ordering constexpr compare(const Version& a_rhs) const noexcept - { - for (std::size_t i = 0; i < _impl.size(); ++i) { - if ((*this)[i] != a_rhs[i]) { - return (*this)[i] < a_rhs[i] ? std::strong_ordering::less : std::strong_ordering::greater; - } - } - return std::strong_ordering::equal; - } - - [[nodiscard]] constexpr std::uint32_t pack() const noexcept - { - return static_cast( - (_impl[0] & 0x0FF) << 24u | - (_impl[1] & 0x0FF) << 16u | - (_impl[2] & 0xFFF) << 4u | - (_impl[3] & 0x00F) << 0u); - } - - [[nodiscard]] constexpr value_type major() const noexcept { return _impl[0]; } - - [[nodiscard]] constexpr value_type minor() const noexcept { return _impl[1]; } - - [[nodiscard]] constexpr value_type patch() const noexcept { return _impl[2]; } - - [[nodiscard]] constexpr value_type build() const noexcept { return _impl[3]; } - - [[nodiscard]] constexpr std::string string(const std::string_view a_separator = "."sv) const - { - std::string result; - for (auto&& ver : _impl) { - result += std::to_string(ver); - result.append(a_separator.data(), a_separator.size()); - } - result.erase(result.size() - a_separator.size(), a_separator.size()); - return result; - } - - [[nodiscard]] constexpr std::wstring wstring(const std::wstring_view a_separator = L"."sv) const - { - std::wstring result; - for (auto&& ver : _impl) { - result += std::to_wstring(ver); - result.append(a_separator.data(), a_separator.size()); - } - result.erase(result.size() - a_separator.size(), a_separator.size()); - return result; - } - - [[nodiscard]] static constexpr Version unpack(const std::uint32_t a_packedVersion) noexcept - { - return Version{ - static_cast((a_packedVersion >> 24) & 0x0FF), - static_cast((a_packedVersion >> 16) & 0x0FF), - static_cast((a_packedVersion >> 4) & 0xFFF), - static_cast(a_packedVersion & 0x0F) - }; - } - - private: - std::array _impl{ 0, 0, 0, 0 }; - }; - - [[nodiscard]] constexpr bool operator==(const Version& a_lhs, const Version& a_rhs) noexcept { return a_lhs.compare(a_rhs) == 0; } - [[nodiscard]] constexpr std::strong_ordering operator<=>(const Version& a_lhs, const Version& a_rhs) noexcept { return a_lhs.compare(a_rhs); } - - [[nodiscard]] inline std::optional get_file_version(stl::zwstring a_filename) - { - std::uint32_t dummy; - std::vector buf(REX::W32::GetFileVersionInfoSizeW(a_filename.data(), std::addressof(dummy))); - if (buf.empty()) { - return std::nullopt; - } - - if (!REX::W32::GetFileVersionInfoW(a_filename.data(), 0, static_cast(buf.size()), buf.data())) { - return std::nullopt; - } - - void* verBuf{ nullptr }; - std::uint32_t verLen{ 0 }; - if (!REX::W32::VerQueryValueW(buf.data(), L"\\StringFileInfo\\040904B0\\ProductVersion", std::addressof(verBuf), std::addressof(verLen))) { - return std::nullopt; - } - - Version version; - std::wistringstream ss( - std::wstring(static_cast(verBuf), verLen)); - std::wstring token; - for (std::size_t i = 0; i < 4 && std::getline(ss, token, L'.'); ++i) { - version[i] = static_cast(std::stoi(token)); - } - - return version; - } - - class Segment - { - public: - enum Name : std::size_t - { - text, - interpr, - idata, - rdata, - data, - pdata, - tls, - total - }; - - constexpr Segment() noexcept = default; - - constexpr Segment(std::uintptr_t a_proxyBase, std::uintptr_t a_address, std::uintptr_t a_size) noexcept : - _proxyBase(a_proxyBase), - _address(a_address), - _size(a_size) - {} - - [[nodiscard]] constexpr std::uintptr_t address() const noexcept { return _address; } - [[nodiscard]] constexpr std::size_t offset() const noexcept { return address() - _proxyBase; } - [[nodiscard]] constexpr std::size_t size() const noexcept { return _size; } - - [[nodiscard]] void* pointer() const noexcept { return reinterpret_cast(address()); } - - template - [[nodiscard]] T* pointer() const noexcept - { - return static_cast(pointer()); - } - - private: - std::uintptr_t _proxyBase{ 0 }; - std::uintptr_t _address{ 0 }; - std::size_t _size{ 0 }; - }; - - class Module - { - public: - Module(const Module&) = delete; - Module(Module&&) = delete; - - Module& operator=(const Module&) = delete; - Module& operator=(Module&&) = delete; - - [[nodiscard]] static Module& get() - { - static Module singleton; - return singleton; - } - - [[nodiscard]] constexpr std::uintptr_t base() const noexcept { return _base; } - [[nodiscard]] stl::zwstring filename() const noexcept { return _filename; } - [[nodiscard]] constexpr Segment segment(Segment::Name a_segment) const noexcept { return _segments[a_segment]; } - [[nodiscard]] constexpr Version version() const noexcept { return _version; } - - [[nodiscard]] void* pointer() const noexcept { return reinterpret_cast(base()); } - - template - [[nodiscard]] T* pointer() const noexcept - { - return static_cast(pointer()); - } - - private: - Module() - { - const auto getFilename = [&]() { - return REX::W32::GetEnvironmentVariableW( - ENVIRONMENT.data(), - _filename.data(), - static_cast(_filename.size())); - }; - - _filename.resize(getFilename()); - if (const auto result = getFilename(); - result != _filename.size() - 1 || - result == 0) { - _filename = L"Fallout4.exe"sv; - } - - load(); - } - - ~Module() noexcept = default; - - void load() - { - auto handle = REX::W32::GetModuleHandleW(_filename.c_str()); - if (handle == nullptr) { - stl::report_and_fail("failed to obtain module handle"sv); - } - _base = reinterpret_cast(handle); - _natvis = _base; - - load_version(); - load_segments(); - } - - void load_segments(); - - void load_version() - { - const auto version = get_file_version(_filename); - if (version) { - _version = *version; - } else { - stl::report_and_fail("failed to obtain file version"sv); - } - } - - static constexpr auto ENVIRONMENT = L"F4SE_RUNTIME"sv; - - static constexpr std::array SEGMENTS{ - ".text"sv, - ".interpr"sv, - ".idata"sv, - ".rdata"sv, - ".data"sv, - ".pdata"sv, - ".tls"sv - }; - - static inline std::uintptr_t _natvis{ 0 }; - - std::wstring _filename; - std::array _segments; - Version _version; - std::uintptr_t _base{ 0 }; - }; + void safe_fill(std::uintptr_t a_dst, std::uint8_t a_value, std::size_t a_count); - class IDDatabase - { - private: - struct mapping_t - { - std::uint64_t id; - std::uint64_t offset; - }; - - public: - IDDatabase(const IDDatabase&) = delete; - IDDatabase(IDDatabase&&) = delete; - - IDDatabase& operator=(const IDDatabase&) = delete; - IDDatabase& operator=(IDDatabase&&) = delete; - - class Offset2ID - { - public: - using value_type = mapping_t; - using container_type = std::vector; - using size_type = typename container_type::size_type; - using const_iterator = typename container_type::const_iterator; - using const_reverse_iterator = typename container_type::const_reverse_iterator; - - template - explicit Offset2ID(ExecutionPolicy&& a_policy) // NOLINT(bugprone-forwarding-reference-overload) - requires(std::is_execution_policy_v>) - { - const auto id2offset = IDDatabase::get().get_id2offset(); - _offset2id.reserve(id2offset.size()); - _offset2id.insert(_offset2id.begin(), id2offset.begin(), id2offset.end()); - std::sort( - a_policy, - _offset2id.begin(), - _offset2id.end(), - [](auto&& a_lhs, auto&& a_rhs) { - return a_lhs.offset < a_rhs.offset; - }); - } - - Offset2ID() : - Offset2ID(std::execution::sequenced_policy{}) - {} - - [[nodiscard]] std::uint64_t operator()(std::size_t a_offset) const - { - if (_offset2id.empty()) { - stl::report_and_fail("data is empty"sv); - } - - const mapping_t elem{ 0, a_offset }; - const auto it = std::lower_bound( - _offset2id.begin(), - _offset2id.end(), - elem, - [](auto&& a_lhs, auto&& a_rhs) { - return a_lhs.offset < a_rhs.offset; - }); - if (it == _offset2id.end()) { - stl::report_and_fail("offset not found"sv); - } - - return it->id; - } - - [[nodiscard]] const_iterator begin() const noexcept { return _offset2id.begin(); } - [[nodiscard]] const_iterator cbegin() const noexcept { return _offset2id.cbegin(); } - - [[nodiscard]] const_iterator end() const noexcept { return _offset2id.end(); } - [[nodiscard]] const_iterator cend() const noexcept { return _offset2id.cend(); } - - [[nodiscard]] const_reverse_iterator rbegin() const noexcept { return _offset2id.rbegin(); } - [[nodiscard]] const_reverse_iterator crbegin() const noexcept { return _offset2id.crbegin(); } - - [[nodiscard]] const_reverse_iterator rend() const noexcept { return _offset2id.rend(); } - [[nodiscard]] const_reverse_iterator crend() const noexcept { return _offset2id.crend(); } - - [[nodiscard]] size_type size() const noexcept { return _offset2id.size(); } - - private: - container_type _offset2id; - }; - - [[nodiscard]] static IDDatabase& get() - { - static IDDatabase 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(it->offset); - } - - protected: - friend class Offset2ID; - - [[nodiscard]] std::span get_id2offset() const noexcept { return _id2offset; } - - private: - IDDatabase() { load(); } - ~IDDatabase() = default; - - void load() - { - const auto version = Module::get().version(); - const auto path = std::format( - "Data/F4SE/Plugins/version-{}.bin", - version.string("-"sv)); - if (!_mmap.open(path)) { - stl::report_and_fail(std::format("failed to open: {}", path)); - } - if (version == REL::Version(1, 10, 980)) { - const auto sha = detail::sha512({ _mmap }); - if (!sha) { - stl::report_and_fail(std::format("failed to hash: {}", path)); - } - // Address bins are expected to be pre-sorted. This bin was released without being sorted, and will cause lookups to randomly fail. - if (*sha == "2AD60B95388F1B6E77A6F86F17BEB51D043CF95A341E91ECB2E911A393E45FE8156D585D2562F7B14434483D6E6652E2373B91589013507CABAE596C26A343F1"sv) { - stl::report_and_fail(std::format( - "The address bin you are using ({}) is corrupted. " - "Please go to the Nexus page for Address Library and redownload the file corresponding to version {}.{}.{}.{}", - path, - version[0], - version[1], - version[2], - version[3])); - } - } - _id2offset = std::span{ - reinterpret_cast(_mmap.data() + sizeof(std::uint64_t)), - *reinterpret_cast(_mmap.data()) - }; - } - - mmio::mapped_file_source _mmap; - std::span _id2offset; - }; - - class Offset - { - public: - constexpr Offset() noexcept = default; - - explicit constexpr Offset(std::size_t a_offset) noexcept : - _offset(a_offset) - {} - - constexpr Offset& operator=(std::size_t a_offset) noexcept - { - _offset = a_offset; - return *this; - } - - [[nodiscard]] std::uintptr_t address() const { return base() + offset(); } - [[nodiscard]] constexpr std::size_t offset() const noexcept { return _offset; } - - private: - [[nodiscard]] static std::uintptr_t base() { return Module::get().base(); } - - std::size_t _offset{ 0 }; - }; - - 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 IDDatabase::get().id2offset(_id); } - - private: - [[nodiscard]] static std::uintptr_t base() { return Module::get().base(); } - - std::uint64_t _id{ static_cast(-1) }; - }; - - template + template class Relocation { public: @@ -736,22 +248,22 @@ namespace REL } template - [[nodiscard]] decltype(auto) operator*() const noexcept // + [[nodiscard]] decltype(auto) operator*() const noexcept requires(std::is_pointer_v) { return *get(); } template - [[nodiscard]] auto operator->() const noexcept // + [[nodiscard]] auto operator->() const noexcept requires(std::is_pointer_v) { return get(); } template - std::invoke_result_t operator()(Args&&... a_args) const // - noexcept(std::is_nothrow_invocable_v) // + std::invoke_result_t operator()(Args&&... a_args) const + noexcept(std::is_nothrow_invocable_v) requires(std::invocable) { return REL::invoke(get(), std::forward(a_args)...); @@ -760,15 +272,64 @@ namespace REL [[nodiscard]] constexpr std::uintptr_t address() const noexcept { return _impl; } [[nodiscard]] std::size_t offset() const { return _impl - base(); } - [[nodiscard]] value_type get() const // + [[nodiscard]] value_type get() const noexcept(std::is_nothrow_copy_constructible_v) { assert(_impl != 0); return stl::unrestricted_cast(_impl); } + + template + void write(const U& a_data) + requires(std::same_as) + { + safe_write(address(), std::addressof(a_data), sizeof(T)); + } + + 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 F4SE::GetTrampoline().write_branch(address(), a_dst); + } + + template + std::uintptr_t write_branch(const F a_dst) + requires(std::same_as) + { + return F4SE::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 F4SE::GetTrampoline().write_call(address(), a_dst); + } + + template + std::uintptr_t write_call(const F a_dst) + requires(std::same_as) + { + return F4SE::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); + } + template - std::uintptr_t write_vfunc(std::size_t a_idx, std::uintptr_t a_newFunc) // + std::uintptr_t write_vfunc(std::size_t a_idx, std::uintptr_t a_newFunc) requires(std::same_as) { const auto addr = address() + (sizeof(void*) * a_idx); @@ -778,7 +339,7 @@ namespace REL } template - std::uintptr_t write_vfunc(std::size_t a_idx, F a_newFunc) // + std::uintptr_t write_vfunc(std::size_t a_idx, F a_newFunc) requires(std::same_as) { return write_vfunc(a_idx, stl::unrestricted_cast(a_newFunc)); @@ -793,28 +354,6 @@ namespace REL }; } -template -struct std::formatter : formatter -{ - template - constexpr auto format(const REL::Version& a_version, FormatContext& a_ctx) const - { - return formatter::format(a_version.string(), a_ctx); - } -}; - -#ifdef FMT_VERSION -template -struct fmt::formatter : formatter -{ - template - auto format(const REL::Version& a_version, FormatContext& a_ctx) - { - return formatter::format(a_version.string(), a_ctx); - } -}; -#endif - #undef REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE #undef REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER #undef REL_MAKE_MEMBER_FUNCTION_NON_POD_TYPE_HELPER_IMPL diff --git a/CommonLibF4/include/REL/Segment.h b/CommonLibF4/include/REL/Segment.h new file mode 100644 index 00000000..ff67b5d5 --- /dev/null +++ b/CommonLibF4/include/REL/Segment.h @@ -0,0 +1,45 @@ +#pragma once + +namespace REL +{ + class Segment + { + public: + enum Name : std::size_t + { + text, + interpr, + idata, + rdata, + data, + pdata, + tls, + total + }; + + constexpr Segment() noexcept = default; + + constexpr Segment(std::uintptr_t a_proxyBase, std::uintptr_t a_address, std::uintptr_t a_size) noexcept : + _proxyBase(a_proxyBase), + _address(a_address), + _size(a_size) + {} + + [[nodiscard]] constexpr std::uintptr_t address() const noexcept { return _address; } + [[nodiscard]] constexpr std::size_t offset() const noexcept { return address() - _proxyBase; } + [[nodiscard]] constexpr std::size_t size() const noexcept { return _size; } + + [[nodiscard]] void* pointer() const noexcept { return reinterpret_cast(address()); } + + template + [[nodiscard]] T* pointer() const noexcept + { + return static_cast(pointer()); + } + + private: + std::uintptr_t _proxyBase{ 0 }; + std::uintptr_t _address{ 0 }; + std::size_t _size{ 0 }; + }; +} diff --git a/CommonLibF4/include/REL/Version.h b/CommonLibF4/include/REL/Version.h new file mode 100644 index 00000000..1556c213 --- /dev/null +++ b/CommonLibF4/include/REL/Version.h @@ -0,0 +1,124 @@ +#pragma once + +namespace REL +{ + class Version + { + public: + using value_type = std::uint16_t; + using reference = value_type&; + using const_reference = const value_type&; + + constexpr Version() noexcept = default; + + explicit constexpr Version(std::array a_version) noexcept : + _impl(a_version) + {} + + constexpr Version(value_type a_v1, value_type a_v2 = 0, value_type a_v3 = 0, value_type a_v4 = 0) noexcept : + _impl{ a_v1, a_v2, a_v3, a_v4 } + {} + + [[nodiscard]] constexpr reference operator[](std::size_t a_idx) noexcept { return _impl[a_idx]; } + [[nodiscard]] constexpr const_reference operator[](std::size_t a_idx) const noexcept { return _impl[a_idx]; } + + [[nodiscard]] constexpr decltype(auto) begin() const noexcept { return _impl.begin(); } + [[nodiscard]] constexpr decltype(auto) cbegin() const noexcept { return _impl.cbegin(); } + [[nodiscard]] constexpr decltype(auto) end() const noexcept { return _impl.end(); } + [[nodiscard]] constexpr decltype(auto) cend() const noexcept { return _impl.cend(); } + + [[nodiscard]] std::strong_ordering constexpr compare(const Version& a_rhs) const noexcept + { + for (std::size_t i = 0; i < _impl.size(); ++i) { + if ((*this)[i] != a_rhs[i]) { + return (*this)[i] < a_rhs[i] ? std::strong_ordering::less : std::strong_ordering::greater; + } + } + return std::strong_ordering::equal; + } + + [[nodiscard]] constexpr std::uint32_t pack() const noexcept + { + return static_cast( + (_impl[0] & 0x0FF) << 24u | + (_impl[1] & 0x0FF) << 16u | + (_impl[2] & 0xFFF) << 4u | + (_impl[3] & 0x00F) << 0u); + } + + [[nodiscard]] constexpr value_type major() const noexcept { return _impl[0]; } + [[nodiscard]] constexpr value_type minor() const noexcept { return _impl[1]; } + [[nodiscard]] constexpr value_type patch() const noexcept { return _impl[2]; } + [[nodiscard]] constexpr value_type build() const noexcept { return _impl[3]; } + + [[nodiscard]] constexpr std::string string(const std::string_view a_separator = "."sv) const + { + std::string result; + for (auto&& ver : _impl) { + result += std::to_string(ver); + result.append(a_separator.data(), a_separator.size()); + } + result.erase(result.size() - a_separator.size(), a_separator.size()); + return result; + } + + [[nodiscard]] constexpr std::wstring wstring(const std::wstring_view a_separator = L"."sv) const + { + std::wstring result; + for (auto&& ver : _impl) { + result += std::to_wstring(ver); + result.append(a_separator.data(), a_separator.size()); + } + result.erase(result.size() - a_separator.size(), a_separator.size()); + return result; + } + + [[nodiscard]] static constexpr Version unpack(const std::uint32_t a_packedVersion) noexcept + { + return Version{ + static_cast((a_packedVersion >> 24) & 0x0FF), + static_cast((a_packedVersion >> 16) & 0x0FF), + static_cast((a_packedVersion >> 4) & 0xFFF), + static_cast(a_packedVersion & 0x0F) + }; + } + + [[nodiscard]] friend constexpr bool operator==(const Version& a_lhs, const Version& a_rhs) noexcept + { + return a_lhs.compare(a_rhs) == 0; + } + + [[nodiscard]] friend constexpr std::strong_ordering operator<=>(const Version& a_lhs, const Version& a_rhs) noexcept + { + return a_lhs.compare(a_rhs); + } + + private: + std::array _impl{ 0, 0, 0, 0 }; + }; + + [[nodiscard]] std::optional GetFileVersion(std::string_view a_filename); + [[nodiscard]] std::optional GetFileVersion(std::wstring_view a_filename); +} + +template +struct std::formatter : formatter +{ + template + constexpr auto format(const REL::Version& a_version, FormatContext& a_ctx) const + { + return formatter::format(a_version.string(), a_ctx); + } +}; + +#ifdef FMT_VERSION +template +struct fmt::formatter : formatter +{ + template + auto format(const REL::Version& a_version, FormatContext& a_ctx) + { + return formatter::format(a_version.string(), a_ctx); + } +}; +#endif diff --git a/CommonLibF4/src/F4SE/API.cpp b/CommonLibF4/src/F4SE/API.cpp index c53a758f..f575096f 100644 --- a/CommonLibF4/src/F4SE/API.cpp +++ b/CommonLibF4/src/F4SE/API.cpp @@ -64,7 +64,7 @@ namespace F4SE } (void)REL::Module::get(); - (void)REL::IDDatabase::get(); + (void)REL::IDDB::get(); auto& storage = detail::APIStorage::get(); const auto& intfc = *a_intfc; @@ -179,12 +179,6 @@ namespace F4SE return detail::APIStorage::get().trampolineInterface; } - Trampoline& GetTrampoline() noexcept - { - static Trampoline trampoline; - return trampoline; - } - void AllocTrampoline(std::size_t a_size) noexcept { auto& trampoline = GetTrampoline(); diff --git a/CommonLibF4/src/F4SE/Trampoline.cpp b/CommonLibF4/src/F4SE/Trampoline.cpp index 35f979a1..b77c73aa 100644 --- a/CommonLibF4/src/F4SE/Trampoline.cpp +++ b/CommonLibF4/src/F4SE/Trampoline.cpp @@ -17,6 +17,54 @@ 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; + } + } + + void Trampoline::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() + text.size(); + } + + 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); + }); + } + #ifdef F4SE_SUPPORT_XBYAK void* Trampoline::allocate(const Xbyak::CodeGenerator& a_code) { @@ -69,9 +117,123 @@ namespace F4SE return nullptr; } + void* Trampoline::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 Trampoline::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(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 = static_cast(0); + mem->addr = static_cast(a_dst); + } + + void Trampoline::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(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}%)", _name, _size, _capacity, pct); } + + Trampoline& GetTrampoline() noexcept + { + static Trampoline trampoline; + return trampoline; + } } diff --git a/CommonLibF4/src/REL/IDDB.cpp b/CommonLibF4/src/REL/IDDB.cpp new file mode 100644 index 00000000..a8bab763 --- /dev/null +++ b/CommonLibF4/src/REL/IDDB.cpp @@ -0,0 +1,108 @@ +#include "REL/IDDB.h" +#include "REL/Version.h" + +#include "REX/W32/BCRYPT.h" + +#include "F4SE/Logger.h" + +namespace REL +{ + namespace log = F4SE::log; + + std::optional SHA512(std::span a_data) + { + REX::W32::BCRYPT_ALG_HANDLE algorithm; + if (!REX::W32::NT_SUCCESS(REX::W32::BCryptOpenAlgorithmProvider(&algorithm, REX::W32::BCRYPT_SHA512_ALGORITHM))) { + log::error("failed to open algorithm provider"); + return std::nullopt; + } + + const stl::scope_exit delAlgorithm([&]() { + [[maybe_unused]] const auto success = REX::W32::NT_SUCCESS(REX::W32::BCryptCloseAlgorithmProvider(algorithm)); + assert(success); + }); + + REX::W32::BCRYPT_HASH_HANDLE hash; + if (!REX::W32::NT_SUCCESS(REX::W32::BCryptCreateHash(algorithm, &hash))) { + log::error("failed to create hash"); + return std::nullopt; + } + + const stl::scope_exit delHash([&]() { + [[maybe_unused]] const auto success = REX::W32::NT_SUCCESS(REX::W32::BCryptDestroyHash(hash)); + assert(success); + }); + + if (!REX::W32::NT_SUCCESS(REX::W32::BCryptHashData( + hash, + reinterpret_cast(const_cast(a_data.data())), // does not modify contents of buffer + static_cast(a_data.size())))) { + log::error("failed to hash data"); + return std::nullopt; + } + + std::uint32_t hashLen = 0; + std::uint32_t discard = 0; + if (!REX::W32::NT_SUCCESS(REX::W32::BCryptGetProperty( + hash, + REX::W32::BCRYPT_HASH_LENGTH, + reinterpret_cast(&hashLen), + sizeof(hashLen), + &discard))) { + log::error("failed to get property"); + return std::nullopt; + } + + std::vector buffer(static_cast(hashLen)); + if (!REX::W32::NT_SUCCESS(REX::W32::BCryptFinishHash( + hash, + buffer.data(), + static_cast(buffer.size())))) { + log::error("failed to finish hash"); + return std::nullopt; + } + + std::string result; + result.reserve(buffer.size() * 2); + for (const auto byte : buffer) { + result += std::format("{:02X}", byte); + } + + return { std::move(result) }; + } +} + +namespace REL +{ + IDDB::IDDB() + { + const auto version = Module::get().version(); + const auto path = std::format("Data/F4SE/Plugins/version-{}.bin", version.string("-"sv)); + if (!_mmap.open(path)) { + stl::report_and_fail(std::format("failed to open: {}", path)); + } + + if (version == Version{ 1, 10, 980 }) { + const auto sha = SHA512({ _mmap }); + if (!sha) { + stl::report_and_fail(std::format("failed to hash: {}", path)); + } + // Address bins are expected to be pre-sorted. This bin was released without being sorted, and will cause lookups to randomly fail. + if (*sha == "2AD60B95388F1B6E77A6F86F17BEB51D043CF95A341E91ECB2E911A393E45FE8156D585D2562F7B14434483D6E6652E2373B91589013507CABAE596C26A343F1"sv) { + stl::report_and_fail(std::format( + "The address bin you are using ({}) is corrupted. " + "Please go to the Nexus page for Address Library and redownload the file corresponding to version {}.{}.{}.{}", + path, + version[0], + version[1], + version[2], + version[3])); + } + } + + _id2offset = std::span{ + reinterpret_cast(_mmap.data() + sizeof(std::uint64_t)), + *reinterpret_cast(_mmap.data()) + }; + } +} diff --git a/CommonLibF4/src/REL/Module.cpp b/CommonLibF4/src/REL/Module.cpp new file mode 100644 index 00000000..53b338ee --- /dev/null +++ b/CommonLibF4/src/REL/Module.cpp @@ -0,0 +1,64 @@ +#include "REL/Module.h" + +#include "REX/W32/KERNEL32.h" + +namespace REL +{ + Module::Module() + { + const auto getFilename = [&]() { + return REX::W32::GetEnvironmentVariableW( + ENVIRONMENT.data(), + _filename.data(), + static_cast(_filename.size())); + }; + + _filename.resize(getFilename()); + if (const auto result = getFilename(); + result != _filename.size() - 1 || + result == 0) { + _filename = L"Fallout4.exe"sv; + } + + auto handle = REX::W32::GetModuleHandleW(_filename.c_str()); + if (handle == nullptr) { + stl::report_and_fail("failed to obtain module handle"sv); + } + + _base = reinterpret_cast(handle); + _natvis = _base; + + load_version(); + load_segments(); + } + + void Module::load_version() + { + const auto version = GetFileVersion(_filename); + if (version) { + _version = *version; + } else { + stl::report_and_fail("failed to obtain file version"sv); + } + } + + void Module::load_segments() + { + const auto dosHeader = reinterpret_cast(_base); + const auto ntHeader = stl::adjust_pointer(dosHeader, dosHeader->lfanew); + const auto sections = REX::W32::IMAGE_FIRST_SECTION(ntHeader); + const auto size = std::min(ntHeader->fileHeader.sectionCount, _segments.size()); + for (std::size_t i = 0; i < size; ++i) { + const auto& section = sections[i]; + const auto it = std::find_if(SEGMENTS.begin(), SEGMENTS.end(), [&](auto&& a_elem) { + constexpr auto size = std::extent_v; + const auto len = std::min(a_elem.size(), size); + return std::memcmp(a_elem.data(), section.name, len) == 0; + }); + if (it != SEGMENTS.end()) { + const auto idx = static_cast(std::distance(SEGMENTS.begin(), it)); + _segments[idx] = Segment{ _base, _base + section.virtualAddress, section.virtualSize }; + } + } + } +} diff --git a/CommonLibF4/src/REL/Relocation.cpp b/CommonLibF4/src/REL/Relocation.cpp index d51bc5fc..5befb838 100644 --- a/CommonLibF4/src/REL/Relocation.cpp +++ b/CommonLibF4/src/REL/Relocation.cpp @@ -1,95 +1,34 @@ #include "REL/Relocation.h" -#include "REX/W32/BCRYPT.h" - -#include "F4SE/Logger.h" +#include "REX/W32/KERNEL32.h" namespace REL { - namespace log = F4SE::log; - - namespace detail + void safe_write(std::uintptr_t a_dst, const void* a_src, std::size_t a_count) { - std::optional sha512(std::span a_data) - { - REX::W32::BCRYPT_ALG_HANDLE algorithm; - if (!REX::W32::NT_SUCCESS(REX::W32::BCryptOpenAlgorithmProvider(&algorithm, REX::W32::BCRYPT_SHA512_ALGORITHM))) { - log::error("failed to open algorithm provider"); - return std::nullopt; - } - - const stl::scope_exit delAlgorithm([&]() { - [[maybe_unused]] const auto success = REX::W32::NT_SUCCESS(REX::W32::BCryptCloseAlgorithmProvider(algorithm)); - assert(success); - }); - - REX::W32::BCRYPT_HASH_HANDLE hash; - if (!REX::W32::NT_SUCCESS(REX::W32::BCryptCreateHash(algorithm, &hash))) { - log::error("failed to create hash"); - return std::nullopt; - } - - const stl::scope_exit delHash([&]() { - [[maybe_unused]] const auto success = REX::W32::NT_SUCCESS(REX::W32::BCryptDestroyHash(hash)); - assert(success); - }); - - if (!REX::W32::NT_SUCCESS(REX::W32::BCryptHashData( - hash, - reinterpret_cast(const_cast(a_data.data())), // does not modify contents of buffer - static_cast(a_data.size())))) { - log::error("failed to hash data"); - return std::nullopt; - } - - std::uint32_t hashLen = 0; - std::uint32_t discard = 0; - if (!REX::W32::NT_SUCCESS(REX::W32::BCryptGetProperty( - hash, - REX::W32::BCRYPT_HASH_LENGTH, - reinterpret_cast(&hashLen), - sizeof(hashLen), - &discard))) { - log::error("failed to get property"); - return std::nullopt; - } - - std::vector buffer(static_cast(hashLen)); - if (!REX::W32::NT_SUCCESS(REX::W32::BCryptFinishHash( - hash, - buffer.data(), - static_cast(buffer.size())))) { - log::error("failed to finish hash"); - return std::nullopt; - } - - std::string result; - result.reserve(buffer.size() * 2); - for (const auto byte : buffer) { - result += std::format("{:02X}", byte); - } - - return { std::move(result) }; + std::uint32_t old{ 0 }; + bool success = REX::W32::VirtualProtect( + reinterpret_cast(a_dst), a_count, REX::W32::PAGE_EXECUTE_READWRITE, std::addressof(old)); + if (success) { + std::memcpy(reinterpret_cast(a_dst), a_src, a_count); + success = REX::W32::VirtualProtect( + reinterpret_cast(a_dst), a_count, old, std::addressof(old)); } + + assert(success); } - void Module::load_segments() + void safe_fill(std::uintptr_t a_dst, std::uint8_t a_value, std::size_t a_count) { - const auto dosHeader = reinterpret_cast(_base); - const auto ntHeader = stl::adjust_pointer(dosHeader, dosHeader->lfanew); - const auto sections = REX::W32::IMAGE_FIRST_SECTION(ntHeader); - const auto size = std::min(ntHeader->fileHeader.sectionCount, _segments.size()); - for (std::size_t i = 0; i < size; ++i) { - const auto& section = sections[i]; - const auto it = std::find_if(SEGMENTS.begin(), SEGMENTS.end(), [&](auto&& a_elem) { - constexpr auto size = std::extent_v; - const auto len = std::min(a_elem.size(), size); - return std::memcmp(a_elem.data(), section.name, len) == 0; - }); - if (it != SEGMENTS.end()) { - const auto idx = static_cast(std::distance(SEGMENTS.begin(), it)); - _segments[idx] = Segment{ _base, _base + section.virtualAddress, section.virtualSize }; - } + std::uint32_t old{ 0 }; + bool success = REX::W32::VirtualProtect( + reinterpret_cast(a_dst), a_count, REX::W32::PAGE_EXECUTE_READWRITE, std::addressof(old)); + if (success) { + std::fill_n(reinterpret_cast(a_dst), a_count, a_value); + success = REX::W32::VirtualProtect( + reinterpret_cast(a_dst), a_count, old, std::addressof(old)); } + + assert(success); } } diff --git a/CommonLibF4/src/REL/Version.cpp b/CommonLibF4/src/REL/Version.cpp new file mode 100644 index 00000000..8eed6d09 --- /dev/null +++ b/CommonLibF4/src/REL/Version.cpp @@ -0,0 +1,62 @@ +#include "REL/Version.h" + +#include "REX/W32/VERSION.h" + +namespace REL +{ + std::optional GetFileVersion(std::string_view a_filename) + { + std::uint32_t dummy; + std::vector buf(REX::W32::GetFileVersionInfoSizeA(a_filename.data(), std::addressof(dummy))); + if (buf.empty()) { + return std::nullopt; + } + + if (!REX::W32::GetFileVersionInfoA(a_filename.data(), 0, static_cast(buf.size()), buf.data())) { + return std::nullopt; + } + + void* verBuf{ nullptr }; + std::uint32_t verLen{ 0 }; + if (!REX::W32::VerQueryValueA(buf.data(), "\\StringFileInfo\\040904B0\\ProductVersion", std::addressof(verBuf), std::addressof(verLen))) { + return std::nullopt; + } + + Version version; + std::wistringstream ss(std::wstring(static_cast(verBuf), verLen)); + std::wstring token; + for (std::size_t i = 0; i < 4 && std::getline(ss, token, L'.'); ++i) { + version[i] = static_cast(std::stoi(token)); + } + + return version; + } + + std::optional GetFileVersion(std::wstring_view a_filename) + { + std::uint32_t dummy; + std::vector buf(REX::W32::GetFileVersionInfoSizeW(a_filename.data(), std::addressof(dummy))); + if (buf.empty()) { + return std::nullopt; + } + + if (!REX::W32::GetFileVersionInfoW(a_filename.data(), 0, static_cast(buf.size()), buf.data())) { + return std::nullopt; + } + + void* verBuf{ nullptr }; + std::uint32_t verLen{ 0 }; + if (!REX::W32::VerQueryValueW(buf.data(), L"\\StringFileInfo\\040904B0\\ProductVersion", std::addressof(verBuf), std::addressof(verLen))) { + return std::nullopt; + } + + Version version; + std::wistringstream ss(std::wstring(static_cast(verBuf), verLen)); + std::wstring token; + for (std::size_t i = 0; i < 4 && std::getline(ss, token, L'.'); ++i) { + version[i] = static_cast(std::stoi(token)); + } + + return version; + } +} diff --git a/RTTIDump/src/PCH.h b/RTTIDump/src/PCH.h index 17263619..431fe90e 100644 --- a/RTTIDump/src/PCH.h +++ b/RTTIDump/src/PCH.h @@ -2,6 +2,7 @@ #include "F4SE/F4SE.h" #include "RE/Fallout.h" +#include "REL/REL.h" #include "REX/W32/DBGHELP.h" #include diff --git a/RTTIDump/src/main.cpp b/RTTIDump/src/main.cpp index 9673c9c3..839ad4d2 100644 --- a/RTTIDump/src/main.cpp +++ b/RTTIDump/src/main.cpp @@ -1,6 +1,6 @@ [[nodiscard]] auto& get_iddb() { - static REL::IDDatabase::Offset2ID iddb; + static REL::Offset2ID iddb; return iddb; }