mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-18 22:35:22 +02:00
The renderer gives each frame 131,072 shader-visible descriptors past a persistent 120,000, in heaps of 333,333. Dense custom maps (the Los Santos City sublevels, San Andreas) draw more than that in a frame, and the renderer wrote past the end of the heap: shaders read garbage descriptors, which faulted or hung the GPU (page faults at nonsense addresses, in draws, texture fetches and ray tracing alike). ReSkate now patches the device setup at boot so each heap is D3D12's 1,000,000 and a frame may use 524,288; heaps are 644,288 descriptors. LS-B2, LS-B3 and San Andreas (210,000 a frame at its peak) now hold. Also in this change: - Memory: perf memory owners/heap/gpu/report, a ledger of who commits the game's RAM; the render-resource pool no longer allocates its 115 MB twice; the EadpErrorsData arena is capped at 32 MiB (EA's error reporting is blocked anyway). - Crash reports: tagged with the map, destination, graphics card and driver version (protocol v3), so Sentry can group GPU hangs by map and card. - GPU diagnostics (--gpu-diagnostics): a fuller DRED report, the device-removal and video memory watches, a large-draw watch, a record of which game code made and freed each GPU heap and resource, checks of every GPU address bound, and a meter of each descriptor heap's use. - World pools: peak use of entity pages, render instances and spatial blocks in the log. - dingosdk_mod_mesh_check: checks every MeshSet a mod ships against its geometry; the MeshSet reader now knows static meshes' layout.
80 lines
4.0 KiB
C++
80 lines
4.0 KiB
C++
#pragma once
|
|
#include <Windows.h>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <type_traits>
|
|
|
|
namespace dingosdk::memory {
|
|
inline constexpr std::uintptr_t highest_user_address = 0x00007fffffffffffULL;
|
|
inline bool read_bytes(std::uintptr_t address, void* destination, std::size_t size) noexcept {
|
|
if (!destination || !size || size > highest_user_address || address < 0x10000 ||
|
|
address > highest_user_address - size) return false;
|
|
SIZE_T count{};
|
|
return ReadProcessMemory(GetCurrentProcess(), reinterpret_cast<const void*>(address), destination, size, &count) && count == size;
|
|
}
|
|
template<class T> requires std::is_trivially_copyable_v<T>
|
|
bool read(std::uintptr_t address, T& value) noexcept { return read_bytes(address, &value, sizeof(value)); }
|
|
|
|
// The same read without a kernel transition: a plain copy that stops at an access violation.
|
|
// ReadProcessMemory above costs a system call (about 1 us) per read, which adds up on paths
|
|
// that run every game frame. Use peek there, for game objects expected to be readable; a
|
|
// failed peek costs an exception, so keep read for probing addresses that are often invalid.
|
|
inline bool peek_bytes(std::uintptr_t address, void* destination, std::size_t size) noexcept {
|
|
if (!destination || !size || size > highest_user_address || address < 0x10000 ||
|
|
address > highest_user_address - size) return false;
|
|
__try {
|
|
std::memcpy(destination, reinterpret_cast<const void*>(address), size);
|
|
return true;
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
return false;
|
|
}
|
|
}
|
|
template<class T> requires std::is_trivially_copyable_v<T>
|
|
bool peek(std::uintptr_t address, T& value) noexcept { return peek_bytes(address, &value, sizeof(value)); }
|
|
|
|
// Fresh read/write memory within reach of a rel32 jump from an image at base: the first free
|
|
// spot past its end, for a code stub that a patched instruction jumps to. Null when nothing is
|
|
// free within 1.75 GB of the image.
|
|
inline void* allocate_near_image(std::uintptr_t base, std::size_t image_size, std::size_t bytes) 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 + 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 >= bytes)
|
|
if (auto* const result = VirtualAlloc(reinterpret_cast<void*>(candidate), bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))
|
|
return result;
|
|
}
|
|
if (region_end <= cursor) break;
|
|
cursor = align_up(region_end);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// A NUL-terminated string copied the same way: the length (terminator written at destination[length]),
|
|
// or -1 if it is unreadable or has no terminator within capacity bytes. Reads byte by byte, so it never
|
|
// touches memory past the terminator.
|
|
inline std::ptrdiff_t peek_cstring(std::uintptr_t address, char* destination, std::size_t capacity) noexcept {
|
|
if (!destination || !capacity || address < 0x10000 || address > highest_user_address - capacity) return -1;
|
|
__try {
|
|
const auto* source = reinterpret_cast<const char*>(address);
|
|
for (std::size_t i = 0; i < capacity; ++i) {
|
|
destination[i] = source[i];
|
|
if (destination[i] == '\0') return static_cast<std::ptrdiff_t>(i);
|
|
}
|
|
return -1;
|
|
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|