mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-23 04:35:54 +02:00
Replay export: the export dialog's 8, 12 and 18 Mbps choices apply again
Every export was forced to 100 Mbps, about 750 MB per minute of replay, whichever quality was chosen. The bitrate patch is removed; the frame pacing fix stays. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
1 parent
fffd34ca80
commit
5728a91e7f
3 files changed
+3
-67
No files matched your search
@@ -11,17 +11,6 @@ inline constexpr std::uintptr_t submit_frame = 0x34f4610;
|
||||
inline constexpr std::array<unsigned char, 24> submit_frame_prefix{
|
||||
0x40,0x57,0x48,0x81,0xec,0x50,0x08,0x00,0x00,0x48,0x8b,0x05,0xa0,0xfd,0xcc,0x03,
|
||||
0x48,0x33,0xc4,0x48,0x89,0x84,0x24,0x20};
|
||||
// Maps the export dialog's 8/12/18 Mbps selection to the H.264 encoder rate.
|
||||
// Raise all three outputs so export cannot silently fall back to the low-bitrate
|
||||
// preset. The replacement rate is 100 Mbps (100000 kbps).
|
||||
inline constexpr std::uintptr_t bitrate_switch = 0x62c240;
|
||||
inline constexpr std::array<unsigned char, 34> bitrate_switch_prefix{
|
||||
0x2b,0x85,0xc9,0x74,0x18,0x83,0xe9,0x01,0x74,0x05,0x83,0xf9,0x01,0x74,0x07,0xb8,
|
||||
0xe0,0x2e,0x00,0x00,0xeb,0x0c,0xb8,0x50,0x46,0x00,0x00,0xeb,0x05,0xb8,0x40,0x1f,
|
||||
0x00,0x00};
|
||||
inline constexpr std::array<std::size_t, 3> bitrate_immediate_offsets{16, 23, 30};
|
||||
inline constexpr std::array<std::uint32_t, 3> stock_bitrates_kbps{12000, 18000, 8000};
|
||||
inline constexpr std::uint32_t replay_bitrate_kbps = 100000;
|
||||
// The encoder the replay editor exports with; the engine has others.
|
||||
inline constexpr std::uintptr_t replay_encoder_vtable = 0x608c0b0;
|
||||
// Encoder byte: a frame may be encoded. A world update sets it and an encoded
|
||||
|
||||
@@ -19,52 +19,6 @@ using Submit = void (*)(std::uintptr_t encoder, std::uintptr_t pixels, std::uint
|
||||
std::atomic<Submit> original{};
|
||||
std::atomic<std::uintptr_t> image{};
|
||||
|
||||
bool force_export_bitrate(std::uintptr_t base) noexcept {
|
||||
const DWORD saved_error = GetLastError();
|
||||
const auto finish = [saved_error](bool result) {
|
||||
SetLastError(saved_error);
|
||||
return result;
|
||||
};
|
||||
if (!base || native::bitrate_switch >= supported_build::game_image_size ||
|
||||
native::bitrate_switch_prefix.size() > supported_build::game_image_size - native::bitrate_switch)
|
||||
return finish(false);
|
||||
|
||||
const auto address = base + native::bitrate_switch;
|
||||
auto* const destination = reinterpret_cast<void*>(address);
|
||||
std::array<unsigned char, native::bitrate_switch_prefix.size()> actual{};
|
||||
if (!memory::read_bytes(address, actual.data(), actual.size())) return finish(false);
|
||||
|
||||
bool needs_write{};
|
||||
for (std::size_t i = 0; i < actual.size(); ++i) {
|
||||
bool bitrate_byte{};
|
||||
for (std::size_t slot = 0; slot < native::bitrate_immediate_offsets.size(); ++slot) {
|
||||
const auto offset = native::bitrate_immediate_offsets[slot];
|
||||
if (i < offset || i >= offset + sizeof(std::uint32_t)) continue;
|
||||
bitrate_byte = true;
|
||||
if (i == offset) {
|
||||
std::uint32_t current{};
|
||||
std::memcpy(¤t, actual.data() + offset, sizeof(current));
|
||||
if (current != native::stock_bitrates_kbps[slot] && current != native::replay_bitrate_kbps)
|
||||
return finish(false);
|
||||
needs_write |= current != native::replay_bitrate_kbps;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!bitrate_byte && actual[i] != native::bitrate_switch_prefix[i]) return finish(false);
|
||||
}
|
||||
if (!needs_write) return finish(true);
|
||||
|
||||
DWORD previous{};
|
||||
if (!VirtualProtect(destination, actual.size(), PAGE_EXECUTE_READWRITE, &previous)) return finish(false);
|
||||
for (const auto offset : native::bitrate_immediate_offsets)
|
||||
std::memcpy(actual.data() + offset, &native::replay_bitrate_kbps, sizeof(native::replay_bitrate_kbps));
|
||||
std::memcpy(destination, actual.data(), actual.size());
|
||||
const bool flushed = FlushInstructionCache(GetCurrentProcess(), destination, actual.size()) != FALSE;
|
||||
DWORD discarded{};
|
||||
const bool restored = VirtualProtect(destination, actual.size(), previous, &discarded) != FALSE;
|
||||
return finish(flushed && restored);
|
||||
}
|
||||
|
||||
void submit(std::uintptr_t encoder, std::uintptr_t pixels, std::uintptr_t pitch, std::uintptr_t unused) {
|
||||
// Encoding is done on this thread before the call returns, so letting every
|
||||
// frame through also holds the renderer to the pace the encoder keeps.
|
||||
@@ -77,27 +31,21 @@ void submit(std::uintptr_t encoder, std::uintptr_t pixels, std::uintptr_t pitch,
|
||||
|
||||
bool start(std::uintptr_t base) noexcept {
|
||||
try {
|
||||
const bool bitrate = force_export_bitrate(base);
|
||||
logging::write(bitrate ? logging::Level::info : logging::Level::warning,
|
||||
logging::Channel::graphics, bitrate
|
||||
? "Replay video export bitrate override active: 100 Mbps H.264."
|
||||
: "Replay video export bitrate override skipped: game code did not match the supported build.");
|
||||
|
||||
std::array<unsigned char, native::submit_frame_prefix.size()> bytes{};
|
||||
if (!base || !memory::read_bytes(base + native::submit_frame, bytes.data(), bytes.size()) ||
|
||||
bytes != native::submit_frame_prefix) {
|
||||
logging::write(logging::Level::warning, logging::Channel::graphics,
|
||||
"Replay export frame pacing hook skipped: game code did not match the supported build.");
|
||||
return bitrate;
|
||||
return false;
|
||||
}
|
||||
image = base;
|
||||
auto* target = reinterpret_cast<void*>(base + native::submit_frame);
|
||||
void* previous{};
|
||||
if (hook_prepare(target, reinterpret_cast<void*>(&submit), &previous) != HookOk) return bitrate;
|
||||
if (hook_prepare(target, reinterpret_cast<void*>(&submit), &previous) != HookOk) return false;
|
||||
original = reinterpret_cast<Submit>(previous);
|
||||
if (hook_enable(target) != HookOk) {
|
||||
hook_remove(target);
|
||||
return bitrate;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (...) {
|
||||
|
||||
@@ -7,6 +7,5 @@ namespace dingosdk::replay_export {
|
||||
// drops the rest. Rendering faster than it updates (an uncapped frame rate),
|
||||
// an export came out with a fraction of its frames and played that much too
|
||||
// fast. Every rendered frame of a replay export is encoded instead.
|
||||
// The export dialog's 8/12/18 Mbps choice is also raised to 100 Mbps H.264.
|
||||
bool start(std::uintptr_t base) noexcept;
|
||||
}
|
||||
Reference in new issue
Block a user