mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-18 15:35:20 +02:00
547 lines
24 KiB
C++
547 lines
24 KiB
C++
#include "native_render_resource_pool.h"
|
|
|
|
#include "Engine/Core/Hooks/hooks.h"
|
|
#include "Engine/Game/Build/supported_build.h"
|
|
#include "Engine/Game/Build/addresses.h"
|
|
#include "Engine/Game/Build/20260929/render_resource_pool.h"
|
|
|
|
#include <Windows.h>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <mutex>
|
|
#include <span>
|
|
|
|
namespace dingosdk {
|
|
namespace {
|
|
namespace contract = addr::render_resource_pool;
|
|
|
|
constexpr std::size_t record_bytes =
|
|
native_render_resource_capacity * native_render_resource_record_size;
|
|
constexpr std::size_t metadata_bytes = native_render_resource_capacity * sizeof(std::uint32_t);
|
|
constexpr std::size_t resource_flag_bytes = native_render_resource_capacity;
|
|
constexpr std::size_t metadata_storage_bytes = metadata_bytes + resource_flag_bytes;
|
|
constexpr std::size_t index_table_capacity = native_render_resource_capacity + 1;
|
|
constexpr std::size_t index_bucket_count = native_render_resource_capacity + 1;
|
|
constexpr std::size_t index_bucket_slots = index_bucket_count + 1;
|
|
constexpr std::size_t index_node_size = 0x10;
|
|
constexpr std::size_t index_bucket_bytes = index_bucket_slots * sizeof(std::uintptr_t);
|
|
constexpr std::size_t index_node_bytes = index_table_capacity * index_node_size;
|
|
constexpr std::size_t resource_state_stride = 0x738;
|
|
constexpr std::size_t resource_state_bytes =
|
|
native_render_resource_capacity * resource_state_stride;
|
|
constexpr std::size_t relation_masks_per_resource = 6;
|
|
constexpr std::size_t relation_mask_bytes = native_render_resource_capacity *
|
|
relation_masks_per_resource * sizeof(std::uint64_t);
|
|
constexpr std::ptrdiff_t vector_begin_offset = contract::vector_begin_offset;
|
|
constexpr std::ptrdiff_t vector_end_offset = contract::vector_end_offset;
|
|
constexpr std::ptrdiff_t vector_capacity_offset = contract::vector_capacity_offset;
|
|
constexpr std::ptrdiff_t metadata_begin_offset = contract::metadata_begin_offset;
|
|
constexpr std::ptrdiff_t metadata_end_offset = contract::metadata_end_offset;
|
|
constexpr std::ptrdiff_t resource_state_vector_offset = contract::resource_state_vector_offset;
|
|
constexpr std::ptrdiff_t relation_mask_vector_offset = contract::relation_mask_vector_offset;
|
|
constexpr std::ptrdiff_t index_buckets_offset = 0x00;
|
|
constexpr std::ptrdiff_t index_bucket_count_offset = 0x08;
|
|
constexpr std::ptrdiff_t index_free_list_offset = 0x20;
|
|
constexpr std::ptrdiff_t index_node_cursor_offset = 0x28;
|
|
constexpr std::ptrdiff_t index_node_end_offset = 0x30;
|
|
constexpr std::ptrdiff_t index_node_stride_offset = 0x38;
|
|
static_assert(record_bytes == 0x00f00000);
|
|
static_assert(metadata_bytes == 0x00040000);
|
|
static_assert(resource_flag_bytes == 0x00010000);
|
|
static_assert(metadata_storage_bytes == 0x00050000);
|
|
static_assert(index_table_capacity == 0x10001);
|
|
static_assert(index_bucket_slots == 0x10002);
|
|
static_assert(index_node_bytes == 0x100010);
|
|
static_assert(resource_state_bytes == 0x07380000);
|
|
static_assert(relation_mask_bytes == 0x00300000);
|
|
|
|
using NativeConstructor = void* (__fastcall *)(void*, void*);
|
|
using IndexTableConstructor = void* (__fastcall *)(void*, void*, void*);
|
|
|
|
struct Buffers {
|
|
std::byte* records{};
|
|
std::uint32_t* metadata{};
|
|
std::uint8_t* resource_flags{};
|
|
std::byte* resource_states{};
|
|
std::uint64_t* relation_masks{};
|
|
|
|
explicit operator bool() const noexcept {
|
|
return records && metadata && resource_flags && resource_states && relation_masks;
|
|
}
|
|
};
|
|
|
|
struct IndexTableBuffers {
|
|
std::uintptr_t* buckets{};
|
|
std::byte* nodes{};
|
|
|
|
explicit operator bool() const noexcept { return buckets && nodes; }
|
|
};
|
|
|
|
std::atomic<NativeConstructor> original_constructor{};
|
|
std::atomic<IndexTableConstructor> original_index_table_constructor{};
|
|
std::mutex install_mutex;
|
|
std::mutex allocation_mutex;
|
|
Buffers reserved_buffers{};
|
|
std::array<IndexTableBuffers, 2> reserved_index_tables{};
|
|
std::size_t next_reserved_index_table{};
|
|
std::byte* resource_flag_stubs{};
|
|
std::uintptr_t installed_base{};
|
|
|
|
bool read_memory(const void* source, void* destination, std::size_t size) noexcept {
|
|
SIZE_T count{};
|
|
return ReadProcessMemory(GetCurrentProcess(), source, destination, size, &count) && count == size;
|
|
}
|
|
|
|
bool matches(std::uintptr_t address, std::span<const std::uint8_t> expected) noexcept {
|
|
if (expected.empty()) return true;
|
|
std::array<std::uint8_t, 32> actual{};
|
|
if (expected.size() > actual.size()) return false;
|
|
return read_memory(reinterpret_cast<const void*>(address), actual.data(), expected.size()) &&
|
|
std::memcmp(actual.data(), expected.data(), expected.size()) == 0;
|
|
}
|
|
|
|
bool validate_contract(std::uintptr_t base) noexcept {
|
|
if (!base || base > std::numeric_limits<std::uintptr_t>::max() - supported_build::game_image_size)
|
|
return false;
|
|
IMAGE_DOS_HEADER dos{};
|
|
IMAGE_NT_HEADERS64 nt{};
|
|
if (!read_memory(reinterpret_cast<const void*>(base), &dos, sizeof(dos)) ||
|
|
dos.e_magic != IMAGE_DOS_SIGNATURE || dos.e_lfanew < static_cast<LONG>(sizeof(dos)) ||
|
|
dos.e_lfanew > 0x1000 ||
|
|
!read_memory(reinterpret_cast<const void*>(base + dos.e_lfanew), &nt, sizeof(nt)) ||
|
|
nt.Signature != IMAGE_NT_SIGNATURE || nt.FileHeader.Machine != IMAGE_FILE_MACHINE_AMD64 ||
|
|
nt.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC ||
|
|
nt.OptionalHeader.SizeOfImage != supported_build::game_image_size ||
|
|
!matches(base + contract::constructor_rva, contract::constructor_entry) ||
|
|
!matches(base + contract::index_table_constructor_rva,
|
|
contract::index_table_constructor_entry) ||
|
|
!matches(base + contract::resource_flags_priority_rva,
|
|
contract::resource_flags_read_priority) ||
|
|
!matches(base + contract::resource_flags_visibility_rva,
|
|
contract::resource_flags_read_visibility)) return false;
|
|
for (const auto& patch : contract::patches)
|
|
if (patch.expected.size() != patch.replacement.size() ||
|
|
!matches(base + patch.rva, patch.expected)) return false;
|
|
return true;
|
|
}
|
|
|
|
Buffers allocate_buffers() noexcept {
|
|
Buffers result{};
|
|
result.records = static_cast<std::byte*>(VirtualAlloc(
|
|
nullptr, record_bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
|
if (!result.records) return {};
|
|
result.metadata = static_cast<std::uint32_t*>(VirtualAlloc(
|
|
nullptr, metadata_storage_bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
|
if (!result.metadata) {
|
|
VirtualFree(result.records, 0, MEM_RELEASE);
|
|
return {};
|
|
}
|
|
result.resource_flags = reinterpret_cast<std::uint8_t*>(result.metadata) + metadata_bytes;
|
|
result.resource_states = static_cast<std::byte*>(VirtualAlloc(
|
|
nullptr, resource_state_bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
|
if (!result.resource_states) {
|
|
VirtualFree(result.metadata, 0, MEM_RELEASE);
|
|
VirtualFree(result.records, 0, MEM_RELEASE);
|
|
return {};
|
|
}
|
|
result.relation_masks = static_cast<std::uint64_t*>(VirtualAlloc(
|
|
nullptr, relation_mask_bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
|
if (!result.relation_masks) {
|
|
VirtualFree(result.resource_states, 0, MEM_RELEASE);
|
|
VirtualFree(result.metadata, 0, MEM_RELEASE);
|
|
VirtualFree(result.records, 0, MEM_RELEASE);
|
|
return {};
|
|
}
|
|
return result;
|
|
}
|
|
|
|
IndexTableBuffers allocate_index_table_buffers() noexcept {
|
|
IndexTableBuffers result{};
|
|
result.buckets = static_cast<std::uintptr_t*>(VirtualAlloc(
|
|
nullptr, index_bucket_bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
|
if (!result.buckets) return {};
|
|
result.nodes = static_cast<std::byte*>(VirtualAlloc(
|
|
nullptr, index_node_bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
|
|
if (!result.nodes) {
|
|
VirtualFree(result.buckets, 0, MEM_RELEASE);
|
|
return {};
|
|
}
|
|
// Native lookup tables keep an all-ones marker immediately after the
|
|
// addressable bucket range.
|
|
result.buckets[index_bucket_count] = std::numeric_limits<std::uintptr_t>::max();
|
|
return result;
|
|
}
|
|
|
|
void free_buffers(Buffers& buffers) noexcept {
|
|
if (buffers.relation_masks) VirtualFree(buffers.relation_masks, 0, MEM_RELEASE);
|
|
if (buffers.resource_states) VirtualFree(buffers.resource_states, 0, MEM_RELEASE);
|
|
if (buffers.metadata) VirtualFree(buffers.metadata, 0, MEM_RELEASE);
|
|
if (buffers.records) VirtualFree(buffers.records, 0, MEM_RELEASE);
|
|
buffers = {};
|
|
}
|
|
|
|
void free_index_table_buffers(IndexTableBuffers& buffers) noexcept {
|
|
if (buffers.nodes) VirtualFree(buffers.nodes, 0, MEM_RELEASE);
|
|
if (buffers.buckets) VirtualFree(buffers.buckets, 0, MEM_RELEASE);
|
|
buffers = {};
|
|
}
|
|
|
|
void free_reserved_allocations() noexcept {
|
|
free_buffers(reserved_buffers);
|
|
for (auto& table : reserved_index_tables) free_index_table_buffers(table);
|
|
next_reserved_index_table = 0;
|
|
}
|
|
|
|
Buffers take_buffers() noexcept {
|
|
std::lock_guard lock(allocation_mutex);
|
|
if (reserved_buffers) {
|
|
const auto result = reserved_buffers;
|
|
reserved_buffers = {};
|
|
return result;
|
|
}
|
|
// The renderer is normally a singleton. Supporting a subsequent native
|
|
// instance costs another isolated allocation and avoids aliasing records.
|
|
return allocate_buffers();
|
|
}
|
|
|
|
IndexTableBuffers take_index_table_buffers() noexcept {
|
|
std::lock_guard lock(allocation_mutex);
|
|
if (next_reserved_index_table < reserved_index_tables.size()) {
|
|
auto& reserved = reserved_index_tables[next_reserved_index_table++];
|
|
const auto result = reserved;
|
|
reserved = {};
|
|
return result;
|
|
}
|
|
return allocate_index_table_buffers();
|
|
}
|
|
|
|
template<class T>
|
|
void store_field(void* owner, std::ptrdiff_t offset, T value) noexcept {
|
|
std::memcpy(static_cast<std::byte*>(owner) + offset, &value, sizeof(value));
|
|
}
|
|
|
|
template<class T>
|
|
T load_field(void* owner, std::ptrdiff_t offset) noexcept {
|
|
T result{};
|
|
std::memcpy(&result, static_cast<std::byte*>(owner) + offset, sizeof(result));
|
|
return result;
|
|
}
|
|
|
|
[[noreturn]] void allocation_failure() noexcept {
|
|
RaiseFailFastException(nullptr, nullptr, 0);
|
|
std::terminate();
|
|
}
|
|
|
|
void* __fastcall construct_index_table(void* self, void* first, void* second) {
|
|
const auto incoming_error = GetLastError();
|
|
const auto original = original_index_table_constructor.load(std::memory_order_acquire);
|
|
if (!self || !original) allocation_failure();
|
|
SetLastError(incoming_error);
|
|
auto* const result = original(self, first, second);
|
|
|
|
// The native constructor leaves an empty 8,193-node table in inline
|
|
// storage. Redirect that empty table before any resource can insert.
|
|
const auto buffers = take_index_table_buffers();
|
|
if (!buffers) allocation_failure();
|
|
auto* const node_end = buffers.nodes + index_node_bytes;
|
|
store_field(self, index_buckets_offset, buffers.buckets);
|
|
store_field(self, index_bucket_count_offset,
|
|
static_cast<std::uint32_t>(index_bucket_count));
|
|
store_field(self, index_free_list_offset, static_cast<std::byte*>(nullptr));
|
|
store_field(self, index_node_cursor_offset, buffers.nodes);
|
|
store_field(self, index_node_end_offset, node_end);
|
|
store_field(self, index_node_stride_offset, static_cast<std::size_t>(index_node_size));
|
|
return result;
|
|
}
|
|
|
|
void* __fastcall construct_native_renderer(void* self, void* configuration) {
|
|
const auto incoming_error = GetLastError();
|
|
const auto buffers = take_buffers();
|
|
const auto original = original_constructor.load(std::memory_order_acquire);
|
|
if (!self || !buffers || !original) allocation_failure();
|
|
|
|
auto* const record_end = buffers.records + record_bytes;
|
|
auto* const metadata_end = buffers.metadata + native_render_resource_capacity;
|
|
auto* const relation_mask_begin = reinterpret_cast<std::byte*>(buffers.relation_masks);
|
|
|
|
// The patched constructor loads its initial vector pointer from +0x90,
|
|
// constructs the native default record there, and advances +0x98 itself.
|
|
store_field(self, vector_begin_offset, buffers.records);
|
|
store_field(self, metadata_begin_offset, buffers.metadata);
|
|
store_field(self, metadata_end_offset, metadata_end);
|
|
store_field(self, resource_state_vector_offset, buffers.resource_states);
|
|
store_field(self, relation_mask_vector_offset, relation_mask_begin);
|
|
SetLastError(incoming_error);
|
|
auto* const result = original(self, configuration);
|
|
|
|
// The side-array fields are initialized by later renderer setup on the
|
|
// stock build. Keep the external values authoritative across construction.
|
|
store_field(self, metadata_begin_offset, buffers.metadata);
|
|
store_field(self, metadata_end_offset, metadata_end);
|
|
if (load_field<std::byte*>(self, vector_begin_offset) != buffers.records ||
|
|
load_field<std::byte*>(self, vector_capacity_offset) != record_end ||
|
|
load_field<std::byte*>(self, vector_end_offset) < buffers.records ||
|
|
load_field<std::byte*>(self, vector_end_offset) > record_end)
|
|
allocation_failure();
|
|
return result;
|
|
}
|
|
|
|
bool write_bytes(std::uintptr_t address, std::span<const std::uint8_t> bytes) noexcept {
|
|
DWORD previous{};
|
|
auto* const destination = reinterpret_cast<void*>(address);
|
|
if (!VirtualProtect(destination, bytes.size(), PAGE_EXECUTE_READWRITE, &previous)) return false;
|
|
std::memcpy(destination, bytes.data(), bytes.size());
|
|
const bool flushed = FlushInstructionCache(GetCurrentProcess(), destination, bytes.size()) != FALSE;
|
|
DWORD discarded{};
|
|
const bool restored = VirtualProtect(destination, bytes.size(), previous, &discarded) != FALSE;
|
|
return flushed && restored;
|
|
}
|
|
|
|
constexpr std::size_t resource_flag_stub_page_bytes = 0x1000;
|
|
constexpr std::size_t priority_stub_offset = 0x00;
|
|
constexpr std::size_t visibility_stub_offset = 0x40;
|
|
|
|
bool relative_jump(std::uintptr_t instruction, std::uintptr_t destination,
|
|
std::int32_t& displacement) noexcept {
|
|
const auto delta = static_cast<std::int64_t>(destination) -
|
|
static_cast<std::int64_t>(instruction + 5);
|
|
if (delta < std::numeric_limits<std::int32_t>::min() ||
|
|
delta > std::numeric_limits<std::int32_t>::max()) return false;
|
|
displacement = static_cast<std::int32_t>(delta);
|
|
return true;
|
|
}
|
|
|
|
std::byte* allocate_near_game_image(std::uintptr_t base) noexcept {
|
|
SYSTEM_INFO information{};
|
|
GetSystemInfo(&information);
|
|
const auto granularity = static_cast<std::uintptr_t>(information.dwAllocationGranularity);
|
|
const auto align_up = [granularity](std::uintptr_t value) {
|
|
return (value + granularity - 1) & ~(granularity - 1);
|
|
};
|
|
auto cursor = align_up(base + supported_build::game_image_size);
|
|
const auto limit = base + 0x70000000ULL;
|
|
while (cursor < limit) {
|
|
MEMORY_BASIC_INFORMATION region{};
|
|
if (!VirtualQuery(reinterpret_cast<const void*>(cursor), ®ion, sizeof(region))) break;
|
|
const auto region_base = reinterpret_cast<std::uintptr_t>(region.BaseAddress);
|
|
const auto region_end = region_base + region.RegionSize;
|
|
if (region.State == MEM_FREE) {
|
|
const auto candidate = align_up(cursor > region_base ? cursor : region_base);
|
|
if (candidate <= region_end && region_end - candidate >= resource_flag_stub_page_bytes) {
|
|
if (auto* const result = static_cast<std::byte*>(VirtualAlloc(
|
|
reinterpret_cast<void*>(candidate), resource_flag_stub_page_bytes,
|
|
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))) return result;
|
|
}
|
|
}
|
|
if (region_end <= cursor) break;
|
|
cursor = align_up(region_end);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
template<std::size_t N>
|
|
bool write_stub(std::byte* destination, std::span<const std::uint8_t, N> prefix,
|
|
std::uintptr_t return_address) noexcept {
|
|
std::array<std::uint8_t, N + 5> bytes{};
|
|
std::memcpy(bytes.data(), prefix.data(), prefix.size());
|
|
bytes[N] = 0xe9;
|
|
std::int32_t displacement{};
|
|
const auto jump = reinterpret_cast<std::uintptr_t>(destination) + N;
|
|
if (!relative_jump(jump, return_address, displacement)) return false;
|
|
std::memcpy(bytes.data() + N + 1, &displacement, sizeof(displacement));
|
|
std::memcpy(destination, bytes.data(), bytes.size());
|
|
return true;
|
|
}
|
|
|
|
bool prepare_resource_flag_stubs(std::uintptr_t base) noexcept {
|
|
resource_flag_stubs = allocate_near_game_image(base);
|
|
if (!resource_flag_stubs) return false;
|
|
std::memset(resource_flag_stubs, 0xcc, resource_flag_stub_page_bytes);
|
|
if (!write_stub(resource_flag_stubs + priority_stub_offset,
|
|
std::span{contract::priority_stub_prefix},
|
|
base + contract::resource_flags_priority_rva +
|
|
contract::resource_flags_read_priority.size()) ||
|
|
!write_stub(resource_flag_stubs + visibility_stub_offset,
|
|
std::span{contract::visibility_stub_prefix},
|
|
base + contract::resource_flags_visibility_rva +
|
|
contract::resource_flags_read_visibility.size())) {
|
|
VirtualFree(resource_flag_stubs, 0, MEM_RELEASE);
|
|
resource_flag_stubs = nullptr;
|
|
return false;
|
|
}
|
|
DWORD previous{};
|
|
if (!VirtualProtect(resource_flag_stubs, resource_flag_stub_page_bytes,
|
|
PAGE_EXECUTE_READ, &previous) ||
|
|
!FlushInstructionCache(GetCurrentProcess(), resource_flag_stubs,
|
|
resource_flag_stub_page_bytes)) {
|
|
VirtualFree(resource_flag_stubs, 0, MEM_RELEASE);
|
|
resource_flag_stubs = nullptr;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template<std::size_t N>
|
|
bool install_generated_jump(std::uintptr_t address, std::byte* stub,
|
|
std::span<const std::uint8_t, N> expected) noexcept {
|
|
std::array<std::uint8_t, N> replacement{};
|
|
replacement.fill(0x90);
|
|
replacement[0] = 0xe9;
|
|
std::int32_t displacement{};
|
|
if (!relative_jump(address, reinterpret_cast<std::uintptr_t>(stub), displacement)) return false;
|
|
std::memcpy(replacement.data() + 1, &displacement, sizeof(displacement));
|
|
return matches(address, expected) && write_bytes(address, replacement) &&
|
|
matches(address, replacement);
|
|
}
|
|
|
|
bool install_resource_flag_access_redirects(std::uintptr_t base, std::size_t& applied) noexcept {
|
|
applied = 0;
|
|
if (!install_generated_jump(base + contract::resource_flags_priority_rva,
|
|
resource_flag_stubs + priority_stub_offset,
|
|
std::span{contract::resource_flags_read_priority})) return false;
|
|
++applied;
|
|
if (!install_generated_jump(base + contract::resource_flags_visibility_rva,
|
|
resource_flag_stubs + visibility_stub_offset,
|
|
std::span{contract::resource_flags_read_visibility})) return false;
|
|
++applied;
|
|
return true;
|
|
}
|
|
|
|
void restore_resource_flag_access_redirects(std::uintptr_t base, std::size_t count) noexcept {
|
|
if (count >= 2) (void)write_bytes(base + contract::resource_flags_visibility_rva,
|
|
contract::resource_flags_read_visibility);
|
|
if (count >= 1) (void)write_bytes(base + contract::resource_flags_priority_rva,
|
|
contract::resource_flags_read_priority);
|
|
}
|
|
|
|
void free_resource_flag_stubs() noexcept {
|
|
if (resource_flag_stubs) VirtualFree(resource_flag_stubs, 0, MEM_RELEASE);
|
|
resource_flag_stubs = nullptr;
|
|
}
|
|
|
|
void restore_patches(std::uintptr_t base, std::size_t count) noexcept {
|
|
while (count) {
|
|
--count;
|
|
const auto& patch = contract::patches[count];
|
|
(void)write_bytes(base + patch.rva, patch.expected);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
bool start_native_render_resource_pool(std::uintptr_t base, std::string& error) {
|
|
std::lock_guard lock(install_mutex);
|
|
error.clear();
|
|
if (installed_base) {
|
|
if (installed_base == base) return true;
|
|
error = "Native render-resource pool support is already installed on another image";
|
|
return false;
|
|
}
|
|
if (!validate_contract(base)) {
|
|
error = "Native render-resource pool contract does not match the supported game";
|
|
return false;
|
|
}
|
|
|
|
{
|
|
std::lock_guard allocation_lock(allocation_mutex);
|
|
reserved_buffers = allocate_buffers();
|
|
for (auto& table : reserved_index_tables) table = allocate_index_table_buffers();
|
|
next_reserved_index_table = 0;
|
|
if (!reserved_buffers || !reserved_index_tables[0] || !reserved_index_tables[1]) {
|
|
free_reserved_allocations();
|
|
error = "Cannot reserve the enlarged native render-resource pools (Windows error " +
|
|
std::to_string(GetLastError()) + ")";
|
|
return false;
|
|
}
|
|
}
|
|
if (!prepare_resource_flag_stubs(base)) {
|
|
std::lock_guard allocation_lock(allocation_mutex);
|
|
free_reserved_allocations();
|
|
error = "Cannot reserve nearby executable redirects for native resource flags";
|
|
return false;
|
|
}
|
|
|
|
auto* const target = reinterpret_cast<void*>(base + contract::constructor_rva);
|
|
auto* const index_target = reinterpret_cast<void*>(base + contract::index_table_constructor_rva);
|
|
NativeConstructor original{};
|
|
IndexTableConstructor original_index{};
|
|
const auto prepared = hook_prepare(target, reinterpret_cast<void*>(&construct_native_renderer),
|
|
reinterpret_cast<void**>(&original));
|
|
if (prepared != HookOk || !original) {
|
|
if (prepared == HookOk) (void)hook_remove(target);
|
|
std::lock_guard allocation_lock(allocation_mutex);
|
|
free_reserved_allocations();
|
|
free_resource_flag_stubs();
|
|
error = "Cannot create native render-resource constructor hook (Detours hook service status " +
|
|
std::to_string(prepared) + ")";
|
|
return false;
|
|
}
|
|
const auto index_prepared = hook_prepare(index_target,
|
|
reinterpret_cast<void*>(&construct_index_table), reinterpret_cast<void**>(&original_index));
|
|
if (index_prepared != HookOk || !original_index) {
|
|
if (index_prepared == HookOk) (void)hook_remove(index_target);
|
|
(void)hook_remove(target);
|
|
std::lock_guard allocation_lock(allocation_mutex);
|
|
free_reserved_allocations();
|
|
free_resource_flag_stubs();
|
|
error = "Cannot create native resource-index table hook (Detours hook service status " +
|
|
std::to_string(index_prepared) + ")";
|
|
return false;
|
|
}
|
|
|
|
std::size_t applied{};
|
|
for (const auto& patch : contract::patches) {
|
|
if (!write_bytes(base + patch.rva, patch.replacement) ||
|
|
!matches(base + patch.rva, patch.replacement)) {
|
|
restore_patches(base, applied + 1);
|
|
(void)hook_remove(index_target);
|
|
(void)hook_remove(target);
|
|
std::lock_guard allocation_lock(allocation_mutex);
|
|
free_reserved_allocations();
|
|
free_resource_flag_stubs();
|
|
error = "Cannot install native render-resource pool instruction redirects";
|
|
return false;
|
|
}
|
|
++applied;
|
|
}
|
|
|
|
std::size_t generated_applied{};
|
|
if (!install_resource_flag_access_redirects(base, generated_applied)) {
|
|
restore_resource_flag_access_redirects(base, generated_applied);
|
|
restore_patches(base, applied);
|
|
(void)hook_remove(index_target);
|
|
(void)hook_remove(target);
|
|
std::lock_guard allocation_lock(allocation_mutex);
|
|
free_reserved_allocations();
|
|
free_resource_flag_stubs();
|
|
error = "Cannot install native per-resource flag access redirects";
|
|
return false;
|
|
}
|
|
|
|
original_constructor.store(original, std::memory_order_release);
|
|
original_index_table_constructor.store(original_index, std::memory_order_release);
|
|
const auto queued_index = hook_queue_enable(index_target);
|
|
const auto queued_renderer = queued_index == HookOk ? hook_queue_enable(target) : queued_index;
|
|
const auto enabled = queued_renderer == HookOk ? hook_apply_queued() : queued_renderer;
|
|
if (enabled != HookOk) {
|
|
original_constructor.store(nullptr, std::memory_order_release);
|
|
original_index_table_constructor.store(nullptr, std::memory_order_release);
|
|
restore_resource_flag_access_redirects(base, generated_applied);
|
|
restore_patches(base, applied);
|
|
(void)hook_remove(index_target);
|
|
(void)hook_remove(target);
|
|
std::lock_guard allocation_lock(allocation_mutex);
|
|
free_reserved_allocations();
|
|
free_resource_flag_stubs();
|
|
error = "Cannot enable native render-resource constructor hooks (Detours hook service status " +
|
|
std::to_string(enabled) + ")";
|
|
return false;
|
|
}
|
|
|
|
installed_base = base;
|
|
return true;
|
|
}
|
|
} // namespace dingosdk
|