Add native self-hosted instance connection to fluxer_desktop
Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 + tools/ci) with a "Connect to a Different Server" menu item and popout that lets the desktop app switch to any self-hosted Fluxer instance, plus fixes for well-known discovery on single-domain self-hosted deployments and a false-positive ERR_ABORTED on same-origin client redirects during the switch. Defaults to chat.fluxr.chat and uses an isolated userData directory from the official build.
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub const STOLEN_BYTES: usize = 16;
|
||||
|
||||
pub const NOP: u32 = 0xD503_201F;
|
||||
|
||||
pub const LDR_X16_PC8: u32 = 0x5800_0050;
|
||||
pub const BR_X16: u32 = 0xD61F_0200;
|
||||
pub const BLR_X16: u32 = 0xD63F_0200;
|
||||
|
||||
pub fn is_b(insn: u32) -> bool {
|
||||
(insn & 0xFC00_0000) == 0x1400_0000
|
||||
}
|
||||
|
||||
pub fn is_bl(insn: u32) -> bool {
|
||||
(insn & 0xFC00_0000) == 0x9400_0000
|
||||
}
|
||||
|
||||
pub fn needs_absolute_island(insn: u32) -> bool {
|
||||
is_b(insn) || is_bl(insn)
|
||||
}
|
||||
|
||||
pub fn branch_target(insn: u32, src_pc: u64) -> Option<u64> {
|
||||
if !is_b(insn) && !is_bl(insn) {
|
||||
return None;
|
||||
}
|
||||
let imm26 = (insn & 0x03FF_FFFF) as i32;
|
||||
let off = ((imm26 << 6) >> 6) as i64 * 4;
|
||||
Some((src_pc as i64 + off) as u64)
|
||||
}
|
||||
|
||||
pub fn encode_imm26(byte_off: i64) -> Option<u32> {
|
||||
if byte_off & 0b11 != 0 {
|
||||
return None;
|
||||
}
|
||||
let words = byte_off >> 2;
|
||||
if !(-(1 << 25)..(1 << 25)).contains(&words) {
|
||||
return None;
|
||||
}
|
||||
Some((words as u32) & 0x03FF_FFFF)
|
||||
}
|
||||
|
||||
pub fn append_abs_branch(out: &mut Vec<u8>, addr: u64, link: bool) {
|
||||
let branch = if link { BLR_X16 } else { BR_X16 };
|
||||
out.extend_from_slice(&LDR_X16_PC8.to_le_bytes());
|
||||
out.extend_from_slice(&branch.to_le_bytes());
|
||||
out.extend_from_slice(&addr.to_le_bytes());
|
||||
}
|
||||
|
||||
pub fn adrp_target(insn: u32, src_pc: u64) -> Option<u64> {
|
||||
if (insn & 0x9F00_0000) != 0x9000_0000 {
|
||||
return None;
|
||||
}
|
||||
let immlo = ((insn >> 29) & 0x3) as i64;
|
||||
let immhi = ((insn >> 5) & 0x7FFFF) as i64;
|
||||
let raw = (immhi << 2) | immlo;
|
||||
let imm21 = (raw << 43) >> 43;
|
||||
let page = (src_pc & !0xFFF) as i64 + imm21 * 4096;
|
||||
Some(page as u64)
|
||||
}
|
||||
|
||||
fn ldr_unsigned_64(insn: u32) -> Option<(u32, u32, u64)> {
|
||||
if (insn & 0xFFC0_0000) != 0xF940_0000 {
|
||||
return None;
|
||||
}
|
||||
let imm12 = ((insn >> 10) & 0xFFF) as u64;
|
||||
let rn = (insn >> 5) & 0x1F;
|
||||
let rt = insn & 0x1F;
|
||||
Some((rt, rn, imm12 * 8))
|
||||
}
|
||||
|
||||
fn br_register(insn: u32) -> Option<u32> {
|
||||
if (insn & 0xFFFF_FC1F) != 0xD61F_0000 {
|
||||
return None;
|
||||
}
|
||||
Some((insn >> 5) & 0x1F)
|
||||
}
|
||||
|
||||
pub unsafe fn import_thunk_target(prologue: &[u8], src_base: u64) -> Option<u64> {
|
||||
if prologue.len() < 12 {
|
||||
return None;
|
||||
}
|
||||
let adrp = u32::from_le_bytes(prologue[0..4].try_into().ok()?);
|
||||
let ldr = u32::from_le_bytes(prologue[4..8].try_into().ok()?);
|
||||
let br = u32::from_le_bytes(prologue[8..12].try_into().ok()?);
|
||||
|
||||
let adrp_reg = adrp & 0x1F;
|
||||
let page = adrp_target(adrp, src_base)?;
|
||||
let (ldr_rt, ldr_rn, offset) = ldr_unsigned_64(ldr)?;
|
||||
let br_rn = br_register(br)?;
|
||||
if adrp_reg != ldr_rn || ldr_rt != br_rn {
|
||||
return None;
|
||||
}
|
||||
let pointer_addr = page.checked_add(offset)?;
|
||||
let target = unsafe { core::ptr::read_unaligned(pointer_addr as *const u64) };
|
||||
(target != 0).then_some(target)
|
||||
}
|
||||
|
||||
pub fn emit_branch_to_island(insn: u32, dst_pc: u64, island_addr: u64) -> Option<u32> {
|
||||
let link = is_bl(insn);
|
||||
let off = island_addr as i64 - dst_pc as i64;
|
||||
let imm = encode_imm26(off)?;
|
||||
let opc = if link { 0x9400_0000 } else { 0x1400_0000 };
|
||||
Some(opc | imm)
|
||||
}
|
||||
|
||||
pub fn island_for_branch(insn: u32, src_pc: u64) -> Option<Vec<u8>> {
|
||||
let target = branch_target(insn, src_pc)?;
|
||||
let mut bytes = Vec::new();
|
||||
append_abs_branch(&mut bytes, target, is_bl(insn));
|
||||
Some(bytes)
|
||||
}
|
||||
|
||||
pub fn relocate_instruction(insn: u32, src_pc: u64, dst_pc: u64) -> Option<u32> {
|
||||
if (insn & 0x9F00_0000) == 0x9000_0000 {
|
||||
return relocate_adr(insn, src_pc, dst_pc, true);
|
||||
}
|
||||
if (insn & 0x9F00_0000) == 0x1000_0000 {
|
||||
return relocate_adr(insn, src_pc, dst_pc, false);
|
||||
}
|
||||
if is_b(insn) || is_bl(insn) {
|
||||
let target = branch_target(insn, src_pc)?;
|
||||
let off = target as i64 - dst_pc as i64;
|
||||
let imm = encode_imm26(off)?;
|
||||
return Some((insn & 0xFC00_0000) | imm);
|
||||
}
|
||||
if (insn & 0xFF00_0010) == 0x5400_0000 {
|
||||
return relocate_imm19_at5(insn, src_pc, dst_pc);
|
||||
}
|
||||
if (insn & 0x7F00_0000) == 0x3400_0000 {
|
||||
return relocate_imm19_at5(insn, src_pc, dst_pc);
|
||||
}
|
||||
if (insn & 0x7F00_0000) == 0x3600_0000 {
|
||||
return relocate_tbz(insn, src_pc, dst_pc);
|
||||
}
|
||||
if (insn & 0x3B00_0000) == 0x1800_0000 {
|
||||
return relocate_imm19_at5(insn, src_pc, dst_pc);
|
||||
}
|
||||
Some(insn)
|
||||
}
|
||||
|
||||
fn relocate_adr(insn: u32, src_pc: u64, dst_pc: u64, page: bool) -> Option<u32> {
|
||||
let immlo = ((insn >> 29) & 0x3) as i64;
|
||||
let immhi = ((insn >> 5) & 0x7FFFF) as i64;
|
||||
let raw = (immhi << 2) | immlo;
|
||||
let imm21 = (raw << 43) >> 43;
|
||||
let (src_ref, dst_ref, scale) = if page {
|
||||
(src_pc & !0xFFF, dst_pc & !0xFFF, 4096i64)
|
||||
} else {
|
||||
(src_pc, dst_pc, 1i64)
|
||||
};
|
||||
let target = src_ref as i64 + imm21 * scale;
|
||||
let new_off = target - dst_ref as i64;
|
||||
if scale != 1 && new_off & 0xFFF != 0 {
|
||||
return None;
|
||||
}
|
||||
let scaled = new_off / scale;
|
||||
if !(-(1 << 20)..(1 << 20)).contains(&scaled) {
|
||||
return None;
|
||||
}
|
||||
let new_raw = (scaled as u32) & 0x1F_FFFF;
|
||||
let new_immlo = (new_raw & 0x3) << 29;
|
||||
let new_immhi = ((new_raw >> 2) & 0x7FFFF) << 5;
|
||||
Some((insn & 0x9F00_001F) | new_immlo | new_immhi)
|
||||
}
|
||||
|
||||
fn relocate_imm19_at5(insn: u32, src_pc: u64, dst_pc: u64) -> Option<u32> {
|
||||
let imm19 = ((insn >> 5) & 0x7FFFF) as i64;
|
||||
let off = ((imm19 << 45) >> 45) * 4;
|
||||
let target = src_pc as i64 + off;
|
||||
let new_off = target - dst_pc as i64;
|
||||
if new_off & 0b11 != 0 {
|
||||
return None;
|
||||
}
|
||||
let words = new_off >> 2;
|
||||
if !(-(1 << 18)..(1 << 18)).contains(&words) {
|
||||
return None;
|
||||
}
|
||||
let new_imm19 = ((words as u32) & 0x7FFFF) << 5;
|
||||
Some((insn & !(0x7FFFF << 5)) | new_imm19)
|
||||
}
|
||||
|
||||
fn relocate_tbz(insn: u32, src_pc: u64, dst_pc: u64) -> Option<u32> {
|
||||
let imm14 = ((insn >> 5) & 0x3FFF) as i64;
|
||||
let off = ((imm14 << 50) >> 50) * 4;
|
||||
let target = src_pc as i64 + off;
|
||||
let new_off = target - dst_pc as i64;
|
||||
if new_off & 0b11 != 0 {
|
||||
return None;
|
||||
}
|
||||
let words = new_off >> 2;
|
||||
if !(-(1 << 13)..(1 << 13)).contains(&words) {
|
||||
return None;
|
||||
}
|
||||
let new_imm14 = ((words as u32) & 0x3FFF) << 5;
|
||||
Some((insn & !(0x3FFF << 5)) | new_imm14)
|
||||
}
|
||||
|
||||
pub fn assemble_trampoline(
|
||||
prologue: &[u8],
|
||||
src_base: u64,
|
||||
dst_base: u64,
|
||||
resume: u64,
|
||||
) -> Option<Vec<u8>> {
|
||||
if !prologue.len().is_multiple_of(4) {
|
||||
return None;
|
||||
}
|
||||
let count = prologue.len() / 4;
|
||||
const RETURN_BRANCH_BYTES: usize = 16;
|
||||
const ISLAND_BYTES: usize = 16;
|
||||
|
||||
let islands_base = dst_base + (count * 4) as u64 + RETURN_BRANCH_BYTES as u64;
|
||||
|
||||
let mut prologue_out: Vec<u8> = Vec::with_capacity(count * 4);
|
||||
let mut islands_out: Vec<u8> = Vec::new();
|
||||
let mut next_island = islands_base;
|
||||
|
||||
for i in 0..count {
|
||||
let insn = u32::from_le_bytes(prologue[i * 4..i * 4 + 4].try_into().ok()?);
|
||||
let src_pc = src_base + (i * 4) as u64;
|
||||
let dst_pc = dst_base + (i * 4) as u64;
|
||||
if needs_absolute_island(insn) {
|
||||
let island_addr = next_island;
|
||||
next_island += ISLAND_BYTES as u64;
|
||||
let relocated = emit_branch_to_island(insn, dst_pc, island_addr)?;
|
||||
prologue_out.extend_from_slice(&relocated.to_le_bytes());
|
||||
let island = island_for_branch(insn, src_pc)?;
|
||||
debug_assert_eq!(island.len(), ISLAND_BYTES);
|
||||
islands_out.extend_from_slice(&island);
|
||||
} else {
|
||||
let relocated = relocate_instruction(insn, src_pc, dst_pc)?;
|
||||
prologue_out.extend_from_slice(&relocated.to_le_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
let mut out = prologue_out;
|
||||
append_abs_branch(&mut out, resume, false);
|
||||
out.extend_from_slice(&islands_out);
|
||||
Some(out)
|
||||
}
|
||||
|
||||
pub fn relocated_prologue(prologue: &[u8], src_base: u64, dst_base: u64) -> Option<Vec<u8>> {
|
||||
let count = prologue.len() / 4;
|
||||
let body = assemble_trampoline(prologue, src_base, dst_base, src_base + STOLEN_BYTES as u64)?;
|
||||
Some(body[..count * 4].to_vec())
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,584 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
use crate::{
|
||||
GAME_CAPTURE_API_OPENGL, GAME_CAPTURE_FALLBACK_NONE,
|
||||
GAME_CAPTURE_FALLBACK_SHARED_TEXTURE_UNSUPPORTED, HookState, mark_present,
|
||||
publish_shared_texture_frame, set_capture_flags, set_fallback_reason, verbose_log,
|
||||
};
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
ptr::null_mut,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
use windows::{
|
||||
Win32::{
|
||||
Foundation::{HMODULE as WinHmodule, HWND as WinHwnd},
|
||||
Graphics::{
|
||||
Direct3D::D3D_DRIVER_TYPE_HARDWARE,
|
||||
Direct3D11::{
|
||||
D3D11_BIND_RENDER_TARGET, D3D11_BIND_SHADER_RESOURCE,
|
||||
D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_RESOURCE_MISC_SHARED, D3D11_SDK_VERSION,
|
||||
D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT, D3D11CreateDeviceAndSwapChain,
|
||||
ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
|
||||
},
|
||||
Dxgi::{
|
||||
Common::{
|
||||
DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_MODE_DESC,
|
||||
DXGI_MODE_SCALING_UNSPECIFIED, DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
|
||||
DXGI_RATIONAL, DXGI_SAMPLE_DESC,
|
||||
},
|
||||
DXGI_PRESENT, DXGI_SWAP_CHAIN_DESC, DXGI_SWAP_EFFECT_DISCARD,
|
||||
DXGI_USAGE_RENDER_TARGET_OUTPUT, IDXGIResource, IDXGISwapChain,
|
||||
},
|
||||
},
|
||||
},
|
||||
core::{BOOL as WinBool, Interface},
|
||||
};
|
||||
use windows_sys::Win32::{
|
||||
Foundation::HWND as SysHwnd,
|
||||
Graphics::OpenGL::{
|
||||
GL_COLOR_BUFFER_BIT, GL_LINEAR, GL_NEAREST, GL_NO_ERROR, GL_TEXTURE_2D,
|
||||
GL_TEXTURE_BINDING_2D, glBindTexture, glDeleteTextures, glFinish, glGenTextures,
|
||||
glGetError, glGetIntegerv, wglGetCurrentContext, wglGetProcAddress,
|
||||
},
|
||||
UI::WindowsAndMessaging::DestroyWindow,
|
||||
};
|
||||
|
||||
const WGL_ACCESS_READ_ONLY_NV: u32 = 0x0000;
|
||||
const WGL_ACCESS_READ_WRITE_NV: u32 = 0x0001;
|
||||
const WGL_ACCESS_WRITE_DISCARD_NV: u32 = 0x0002;
|
||||
|
||||
const GL_READ_FRAMEBUFFER: u32 = 0x8CA8;
|
||||
const GL_DRAW_FRAMEBUFFER: u32 = 0x8CA9;
|
||||
const GL_FRAMEBUFFER: u32 = 0x8D40;
|
||||
const GL_COLOR_ATTACHMENT0: u32 = 0x8CE0;
|
||||
const GL_FRAMEBUFFER_COMPLETE: u32 = 0x8CD5;
|
||||
const GL_READ_FRAMEBUFFER_BINDING: u32 = 0x8CAA;
|
||||
const GL_DRAW_FRAMEBUFFER_BINDING: u32 = 0x8CA6;
|
||||
|
||||
type DxOpenDeviceNvFn = unsafe extern "system" fn(dx_device: *mut c_void) -> *mut c_void;
|
||||
type DxCloseDeviceNvFn = unsafe extern "system" fn(device: *mut c_void) -> i32;
|
||||
type DxRegisterObjectNvFn = unsafe extern "system" fn(
|
||||
device: *mut c_void,
|
||||
dx_object: *mut c_void,
|
||||
name: u32,
|
||||
object_type: u32,
|
||||
access: u32,
|
||||
) -> *mut c_void;
|
||||
type DxUnregisterObjectNvFn =
|
||||
unsafe extern "system" fn(device: *mut c_void, object: *mut c_void) -> i32;
|
||||
type DxLockObjectsNvFn =
|
||||
unsafe extern "system" fn(device: *mut c_void, count: i32, objects: *const *mut c_void) -> i32;
|
||||
type DxUnlockObjectsNvFn =
|
||||
unsafe extern "system" fn(device: *mut c_void, count: i32, objects: *const *mut c_void) -> i32;
|
||||
|
||||
type GlGenFramebuffersFn = unsafe extern "system" fn(n: i32, framebuffers: *mut u32);
|
||||
type GlDeleteFramebuffersFn = unsafe extern "system" fn(n: i32, framebuffers: *const u32);
|
||||
type GlBindFramebufferFn = unsafe extern "system" fn(target: u32, framebuffer: u32);
|
||||
type GlFramebufferTexture2DFn = unsafe extern "system" fn(
|
||||
target: u32,
|
||||
attachment: u32,
|
||||
textarget: u32,
|
||||
texture: u32,
|
||||
level: i32,
|
||||
);
|
||||
type GlCheckFramebufferStatusFn = unsafe extern "system" fn(target: u32) -> u32;
|
||||
type GlBlitFramebufferFn = unsafe extern "system" fn(
|
||||
src_x0: i32,
|
||||
src_y0: i32,
|
||||
src_x1: i32,
|
||||
src_y1: i32,
|
||||
dst_x0: i32,
|
||||
dst_y0: i32,
|
||||
dst_x1: i32,
|
||||
dst_y1: i32,
|
||||
mask: u32,
|
||||
filter: u32,
|
||||
);
|
||||
|
||||
struct InteropProcs {
|
||||
open_device: DxOpenDeviceNvFn,
|
||||
close_device: DxCloseDeviceNvFn,
|
||||
register_object: DxRegisterObjectNvFn,
|
||||
unregister_object: DxUnregisterObjectNvFn,
|
||||
lock_objects: DxLockObjectsNvFn,
|
||||
unlock_objects: DxUnlockObjectsNvFn,
|
||||
gen_framebuffers: GlGenFramebuffersFn,
|
||||
delete_framebuffers: GlDeleteFramebuffersFn,
|
||||
bind_framebuffer: GlBindFramebufferFn,
|
||||
framebuffer_texture_2d: GlFramebufferTexture2DFn,
|
||||
check_framebuffer_status: GlCheckFramebufferStatusFn,
|
||||
blit_framebuffer: GlBlitFramebufferFn,
|
||||
}
|
||||
|
||||
pub(crate) struct GlInteropState {
|
||||
procs: InteropProcs,
|
||||
_device: ID3D11Device,
|
||||
_context: ID3D11DeviceContext,
|
||||
swap_chain: IDXGISwapChain,
|
||||
_texture: ID3D11Texture2D,
|
||||
dummy_hwnd: SysHwnd,
|
||||
shared_handle: u64,
|
||||
dx_device: *mut c_void,
|
||||
dx_object: *mut c_void,
|
||||
gl_texture: u32,
|
||||
draw_fbo: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
unsafe impl Send for GlInteropState {}
|
||||
|
||||
static GL_GPU_DISABLED: AtomicBool = AtomicBool::new(false);
|
||||
static GL_GPU_UNAVAILABLE_LOGGED: AtomicBool = AtomicBool::new(false);
|
||||
static GL_DUMMY_PRESENT_ACTIVE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
struct DummyPresentGuard;
|
||||
|
||||
impl DummyPresentGuard {
|
||||
fn enter() -> Self {
|
||||
GL_DUMMY_PRESENT_ACTIVE.store(true, Ordering::Release);
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DummyPresentGuard {
|
||||
fn drop(&mut self) {
|
||||
GL_DUMMY_PRESENT_ACTIVE.store(false, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
fn latch_disable(reason: &str) {
|
||||
if !GL_GPU_DISABLED.swap(true, Ordering::AcqRel) {
|
||||
verbose_log(&format!(
|
||||
"opengl interop: latch-disabling GPU path, falling back to glReadPixels CPU path ({reason})"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gpu_path_disabled() -> bool {
|
||||
GL_GPU_DISABLED.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub(crate) fn dummy_present_active() -> bool {
|
||||
GL_DUMMY_PRESENT_ACTIVE.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
unsafe fn load_proc<T>(name: &[u8]) -> Option<T> {
|
||||
debug_assert_eq!(
|
||||
name.last(),
|
||||
Some(&0),
|
||||
"wglGetProcAddress name must be NUL-terminated"
|
||||
);
|
||||
let proc = wglGetProcAddress(name.as_ptr());
|
||||
match proc {
|
||||
Some(proc) => Some(std::mem::transmute_copy::<_, T>(&proc)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl InteropProcs {
|
||||
unsafe fn load() -> Option<Self> {
|
||||
if wglGetCurrentContext().is_null() {
|
||||
return None;
|
||||
}
|
||||
let open_device = load_proc::<DxOpenDeviceNvFn>(b"wglDXOpenDeviceNV\0")?;
|
||||
let close_device = load_proc::<DxCloseDeviceNvFn>(b"wglDXCloseDeviceNV\0")?;
|
||||
let register_object = load_proc::<DxRegisterObjectNvFn>(b"wglDXRegisterObjectNV\0")?;
|
||||
let unregister_object = load_proc::<DxUnregisterObjectNvFn>(b"wglDXUnregisterObjectNV\0")?;
|
||||
let lock_objects = load_proc::<DxLockObjectsNvFn>(b"wglDXLockObjectsNV\0")?;
|
||||
let unlock_objects = load_proc::<DxUnlockObjectsNvFn>(b"wglDXUnlockObjectsNV\0")?;
|
||||
let gen_framebuffers = load_proc::<GlGenFramebuffersFn>(b"glGenFramebuffers\0")?;
|
||||
let delete_framebuffers = load_proc::<GlDeleteFramebuffersFn>(b"glDeleteFramebuffers\0")?;
|
||||
let bind_framebuffer = load_proc::<GlBindFramebufferFn>(b"glBindFramebuffer\0")?;
|
||||
let framebuffer_texture_2d =
|
||||
load_proc::<GlFramebufferTexture2DFn>(b"glFramebufferTexture2D\0")?;
|
||||
let check_framebuffer_status =
|
||||
load_proc::<GlCheckFramebufferStatusFn>(b"glCheckFramebufferStatus\0")?;
|
||||
let blit_framebuffer = load_proc::<GlBlitFramebufferFn>(b"glBlitFramebuffer\0")?;
|
||||
Some(Self {
|
||||
open_device,
|
||||
close_device,
|
||||
register_object,
|
||||
unregister_object,
|
||||
lock_objects,
|
||||
unlock_objects,
|
||||
gen_framebuffers,
|
||||
delete_framebuffers,
|
||||
bind_framebuffer,
|
||||
framebuffer_texture_2d,
|
||||
check_framebuffer_status,
|
||||
blit_framebuffer,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn create_interop_d3d11_device()
|
||||
-> Option<(ID3D11Device, ID3D11DeviceContext, IDXGISwapChain, SysHwnd)> {
|
||||
let dummy_hwnd = crate::create_dummy_window();
|
||||
if dummy_hwnd.is_null() {
|
||||
verbose_log("opengl interop: failed to create dummy D3D11 flush window");
|
||||
return None;
|
||||
}
|
||||
|
||||
let desc = DXGI_SWAP_CHAIN_DESC {
|
||||
BufferDesc: DXGI_MODE_DESC {
|
||||
Width: 2,
|
||||
Height: 2,
|
||||
RefreshRate: DXGI_RATIONAL {
|
||||
Numerator: 60,
|
||||
Denominator: 1,
|
||||
},
|
||||
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
ScanlineOrdering: DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
|
||||
Scaling: DXGI_MODE_SCALING_UNSPECIFIED,
|
||||
},
|
||||
SampleDesc: DXGI_SAMPLE_DESC {
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
|
||||
BufferCount: 2,
|
||||
OutputWindow: WinHwnd(dummy_hwnd),
|
||||
Windowed: WinBool(1),
|
||||
SwapEffect: DXGI_SWAP_EFFECT_DISCARD,
|
||||
Flags: 0,
|
||||
};
|
||||
let mut swap_chain = None;
|
||||
let mut device = None;
|
||||
let mut context = None;
|
||||
let result = D3D11CreateDeviceAndSwapChain(
|
||||
None,
|
||||
D3D_DRIVER_TYPE_HARDWARE,
|
||||
WinHmodule(null_mut()),
|
||||
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
||||
None,
|
||||
D3D11_SDK_VERSION,
|
||||
Some(&desc),
|
||||
Some(&mut swap_chain),
|
||||
Some(&mut device),
|
||||
None,
|
||||
Some(&mut context),
|
||||
);
|
||||
if result.is_err() {
|
||||
let _ = DestroyWindow(dummy_hwnd);
|
||||
return None;
|
||||
}
|
||||
match (device, context, swap_chain) {
|
||||
(Some(device), Some(context), Some(swap_chain)) => {
|
||||
Some((device, context, swap_chain, dummy_hwnd))
|
||||
}
|
||||
_ => {
|
||||
let _ = DestroyWindow(dummy_hwnd);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn create_shared_texture(
|
||||
device: &ID3D11Device,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Option<(ID3D11Texture2D, u64)> {
|
||||
let desc = D3D11_TEXTURE2D_DESC {
|
||||
Width: width,
|
||||
Height: height,
|
||||
MipLevels: 1,
|
||||
ArraySize: 1,
|
||||
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
SampleDesc: DXGI_SAMPLE_DESC {
|
||||
Count: 1,
|
||||
Quality: 0,
|
||||
},
|
||||
Usage: D3D11_USAGE_DEFAULT,
|
||||
BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
|
||||
CPUAccessFlags: 0,
|
||||
MiscFlags: D3D11_RESOURCE_MISC_SHARED.0 as u32,
|
||||
};
|
||||
let mut texture = None;
|
||||
if device
|
||||
.CreateTexture2D(&desc, None, Some(&mut texture))
|
||||
.is_err()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let texture = texture?;
|
||||
let handle = texture
|
||||
.cast::<IDXGIResource>()
|
||||
.and_then(|resource| resource.GetSharedHandle())
|
||||
.ok()?;
|
||||
Some((texture, handle.0 as usize as u64))
|
||||
}
|
||||
|
||||
impl GlInteropState {
|
||||
unsafe fn create(width: u32, height: u32) -> Option<Self> {
|
||||
let procs = InteropProcs::load()?;
|
||||
let (device, context, swap_chain, dummy_hwnd) = create_interop_d3d11_device()?;
|
||||
let (texture, shared_handle) = match create_shared_texture(&device, width, height) {
|
||||
Some(texture) => texture,
|
||||
None => {
|
||||
let _ = DestroyWindow(dummy_hwnd);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
if shared_handle == 0 {
|
||||
let _ = DestroyWindow(dummy_hwnd);
|
||||
return None;
|
||||
}
|
||||
|
||||
let dx_device = (procs.open_device)(device.as_raw());
|
||||
if dx_device.is_null() {
|
||||
verbose_log("opengl interop: wglDXOpenDeviceNV returned NULL");
|
||||
let _ = DestroyWindow(dummy_hwnd);
|
||||
return None;
|
||||
}
|
||||
verbose_log("opengl interop: wglDXOpenDeviceNV opened private D3D11 device");
|
||||
|
||||
let mut gl_texture = 0u32;
|
||||
glGenTextures(1, &mut gl_texture);
|
||||
if gl_texture == 0 {
|
||||
(procs.close_device)(dx_device);
|
||||
let _ = DestroyWindow(dummy_hwnd);
|
||||
return None;
|
||||
}
|
||||
|
||||
let dx_object = (procs.register_object)(
|
||||
dx_device,
|
||||
texture.as_raw(),
|
||||
gl_texture,
|
||||
GL_TEXTURE_2D,
|
||||
WGL_ACCESS_WRITE_DISCARD_NV,
|
||||
);
|
||||
if dx_object.is_null() {
|
||||
verbose_log("opengl interop: wglDXRegisterObjectNV returned NULL");
|
||||
glDeleteTextures(1, &gl_texture);
|
||||
(procs.close_device)(dx_device);
|
||||
let _ = DestroyWindow(dummy_hwnd);
|
||||
return None;
|
||||
}
|
||||
verbose_log(&format!(
|
||||
"opengl interop: registered D3D11 texture <-> GL texture {gl_texture} ({width}x{height} BGRA)"
|
||||
));
|
||||
|
||||
let mut draw_fbo = 0u32;
|
||||
(procs.gen_framebuffers)(1, &mut draw_fbo);
|
||||
if draw_fbo == 0 {
|
||||
(procs.unregister_object)(dx_device, dx_object);
|
||||
glDeleteTextures(1, &gl_texture);
|
||||
(procs.close_device)(dx_device);
|
||||
let _ = DestroyWindow(dummy_hwnd);
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
procs,
|
||||
_device: device,
|
||||
_context: context,
|
||||
swap_chain,
|
||||
_texture: texture,
|
||||
dummy_hwnd,
|
||||
shared_handle,
|
||||
dx_device,
|
||||
dx_object,
|
||||
gl_texture,
|
||||
draw_fbo,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
|
||||
fn matches(&self, width: u32, height: u32) -> bool {
|
||||
self.width == width && self.height == height
|
||||
}
|
||||
|
||||
unsafe fn blit_default_framebuffer(&self) -> bool {
|
||||
let objects = [self.dx_object];
|
||||
|
||||
let mut prev_read_fbo = 0i32;
|
||||
let mut prev_draw_fbo = 0i32;
|
||||
glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &mut prev_read_fbo);
|
||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &mut prev_draw_fbo);
|
||||
let mut prev_tex = 0i32;
|
||||
glGetIntegerv(GL_TEXTURE_BINDING_2D, &mut prev_tex);
|
||||
|
||||
if (self.procs.lock_objects)(self.dx_device, 1, objects.as_ptr()) == 0 {
|
||||
verbose_log("opengl interop: wglDXLockObjectsNV FAILED");
|
||||
return false;
|
||||
}
|
||||
|
||||
(self.procs.bind_framebuffer)(GL_DRAW_FRAMEBUFFER, self.draw_fbo);
|
||||
(self.procs.framebuffer_texture_2d)(
|
||||
GL_DRAW_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D,
|
||||
self.gl_texture,
|
||||
0,
|
||||
);
|
||||
let status = (self.procs.check_framebuffer_status)(GL_DRAW_FRAMEBUFFER);
|
||||
if status != GL_FRAMEBUFFER_COMPLETE {
|
||||
verbose_log(&format!(
|
||||
"opengl interop: draw FBO incomplete (status 0x{status:04X}); unlocking and falling back"
|
||||
));
|
||||
(self.procs.framebuffer_texture_2d)(
|
||||
GL_DRAW_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
(self.procs.bind_framebuffer)(GL_DRAW_FRAMEBUFFER, prev_draw_fbo as u32);
|
||||
(self.procs.bind_framebuffer)(GL_READ_FRAMEBUFFER, prev_read_fbo as u32);
|
||||
let _ = (self.procs.unlock_objects)(self.dx_device, 1, objects.as_ptr());
|
||||
return false;
|
||||
}
|
||||
|
||||
(self.procs.bind_framebuffer)(GL_READ_FRAMEBUFFER, 0);
|
||||
let w = self.width as i32;
|
||||
let h = self.height as i32;
|
||||
(self.procs.blit_framebuffer)(
|
||||
0,
|
||||
0,
|
||||
w,
|
||||
h,
|
||||
0,
|
||||
h,
|
||||
w,
|
||||
0,
|
||||
GL_COLOR_BUFFER_BIT,
|
||||
if w == self.width as i32 && h == self.height as i32 {
|
||||
GL_NEAREST
|
||||
} else {
|
||||
GL_LINEAR
|
||||
},
|
||||
);
|
||||
let blit_err = glGetError();
|
||||
|
||||
(self.procs.framebuffer_texture_2d)(
|
||||
GL_DRAW_FRAMEBUFFER,
|
||||
GL_COLOR_ATTACHMENT0,
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
(self.procs.bind_framebuffer)(GL_DRAW_FRAMEBUFFER, prev_draw_fbo as u32);
|
||||
(self.procs.bind_framebuffer)(GL_READ_FRAMEBUFFER, prev_read_fbo as u32);
|
||||
glBindTexture(GL_TEXTURE_2D, prev_tex as u32);
|
||||
|
||||
glFinish();
|
||||
|
||||
if (self.procs.unlock_objects)(self.dx_device, 1, objects.as_ptr()) == 0 {
|
||||
verbose_log("opengl interop: wglDXUnlockObjectsNV FAILED");
|
||||
return false;
|
||||
}
|
||||
self._context.Flush();
|
||||
let present_result = {
|
||||
let _guard = DummyPresentGuard::enter();
|
||||
self.swap_chain.Present(0, DXGI_PRESENT(0))
|
||||
};
|
||||
if present_result.is_err() {
|
||||
verbose_log(&format!(
|
||||
"opengl interop: dummy D3D11 Present flush failed hr={:#010x}",
|
||||
present_result.0 as u32
|
||||
));
|
||||
return false;
|
||||
}
|
||||
|
||||
if blit_err != GL_NO_ERROR {
|
||||
verbose_log(&format!(
|
||||
"opengl interop: glBlitFramebuffer raised GL error 0x{blit_err:04X}"
|
||||
));
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GlInteropState {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if !self.dx_object.is_null() {
|
||||
let _ = (self.procs.unregister_object)(self.dx_device, self.dx_object);
|
||||
}
|
||||
if self.draw_fbo != 0 {
|
||||
(self.procs.delete_framebuffers)(1, &self.draw_fbo);
|
||||
}
|
||||
if self.gl_texture != 0 {
|
||||
glDeleteTextures(1, &self.gl_texture);
|
||||
}
|
||||
if !self.dx_device.is_null() {
|
||||
let _ = (self.procs.close_device)(self.dx_device);
|
||||
}
|
||||
if !self.dummy_hwnd.is_null() {
|
||||
let _ = DestroyWindow(self.dummy_hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn interop_state_for_frame(
|
||||
state: &mut HookState,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Option<&mut GlInteropState> {
|
||||
let recreate = state
|
||||
.gl_interop
|
||||
.as_ref()
|
||||
.map(|interop| !interop.matches(width, height))
|
||||
.unwrap_or(true);
|
||||
if recreate {
|
||||
state.gl_interop = None;
|
||||
match GlInteropState::create(width, height) {
|
||||
Some(interop) => state.gl_interop = Some(interop),
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
state.gl_interop.as_mut()
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn capture_opengl_frame_gpu(
|
||||
state: &mut HookState,
|
||||
hwnd: SysHwnd,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> bool {
|
||||
if gpu_path_disabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(interop) = interop_state_for_frame(state, width, height) else {
|
||||
if !GL_GPU_UNAVAILABLE_LOGGED.swap(true, Ordering::AcqRel) {
|
||||
verbose_log(
|
||||
"opengl interop: WGL_NV_DX_interop2 unavailable or pipeline creation failed",
|
||||
);
|
||||
}
|
||||
latch_disable("interop pipeline creation failed");
|
||||
set_fallback_reason(state, GAME_CAPTURE_FALLBACK_SHARED_TEXTURE_UNSUPPORTED);
|
||||
return false;
|
||||
};
|
||||
|
||||
let shared_handle = interop.shared_handle;
|
||||
let blitted = interop.blit_default_framebuffer();
|
||||
if !blitted {
|
||||
latch_disable("lock/blit failed after successful registration");
|
||||
set_fallback_reason(state, GAME_CAPTURE_FALLBACK_SHARED_TEXTURE_UNSUPPORTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
mark_present(state, GAME_CAPTURE_API_OPENGL);
|
||||
set_capture_flags(state, 0);
|
||||
set_fallback_reason(state, GAME_CAPTURE_FALLBACK_NONE);
|
||||
let published = publish_shared_texture_frame(
|
||||
state,
|
||||
hwnd,
|
||||
width,
|
||||
height,
|
||||
DXGI_FORMAT(DXGI_FORMAT_B8G8R8A8_UNORM.0),
|
||||
shared_handle,
|
||||
);
|
||||
if published {
|
||||
verbose_log(&format!(
|
||||
"opengl interop: published shared-texture frame {width}x{height} (handle 0x{shared_handle:X})"
|
||||
));
|
||||
}
|
||||
published
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
use retour::Function;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) use aarch64_function::Function;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod aarch64_function {
|
||||
pub(crate) trait Function: Copy + Sync + 'static {
|
||||
unsafe fn from_ptr(ptr: *const ()) -> Self;
|
||||
fn to_ptr(&self) -> *const ();
|
||||
}
|
||||
|
||||
macro_rules! impl_function {
|
||||
($($arg:ident),*) => {
|
||||
impl<Ret: 'static, $($arg: 'static),*> Function
|
||||
for unsafe extern "system" fn($($arg),*) -> Ret
|
||||
{
|
||||
unsafe fn from_ptr(ptr: *const ()) -> Self {
|
||||
core::mem::transmute(ptr)
|
||||
}
|
||||
fn to_ptr(&self) -> *const () {
|
||||
*self as *const ()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_function!();
|
||||
impl_function!(A);
|
||||
impl_function!(A, B);
|
||||
impl_function!(A, B, C);
|
||||
impl_function!(A, B, C, D);
|
||||
impl_function!(A, B, C, D, E);
|
||||
impl_function!(A, B, C, D, E, F);
|
||||
}
|
||||
|
||||
pub(crate) struct Detour<T: Function> {
|
||||
inner: Inner<T>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
enum Inner<T: Function> {
|
||||
Retour(retour::GenericDetour<T>),
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
enum Inner<T: Function> {
|
||||
Aarch64(aarch64::Aarch64Detour<T>),
|
||||
}
|
||||
|
||||
impl<T: Function> Detour<T> {
|
||||
pub(crate) unsafe fn new(target: T, detour: T) -> Result<Self, ()> {
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
{
|
||||
match retour::GenericDetour::<T>::new(target, detour) {
|
||||
Ok(detour) => Ok(Self {
|
||||
inner: Inner::Retour(detour),
|
||||
}),
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
{
|
||||
aarch64::Aarch64Detour::<T>::new(target, detour).map(|detour| Self {
|
||||
inner: Inner::Aarch64(detour),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn enable(&self) -> Result<(), ()> {
|
||||
match &self.inner {
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
Inner::Retour(detour) => detour.enable().map_err(|_| ()),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
Inner::Aarch64(detour) => detour.enable(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn trampoline_fn(&self) -> T {
|
||||
match &self.inner {
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
Inner::Retour(detour) => unsafe {
|
||||
T::from_ptr(detour.trampoline() as *const () as *const ())
|
||||
},
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
Inner::Aarch64(detour) => detour.trampoline_fn(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
mod aarch64 {
|
||||
|
||||
use super::Function;
|
||||
use crate::arm64_reloc::{
|
||||
NOP, STOLEN_BYTES, append_abs_branch, assemble_trampoline, import_thunk_target,
|
||||
};
|
||||
use core::marker::PhantomData;
|
||||
use std::ptr;
|
||||
use windows_sys::Win32::System::{
|
||||
Diagnostics::Debug::FlushInstructionCache,
|
||||
Memory::{
|
||||
MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE,
|
||||
PAGE_PROTECTION_FLAGS, VirtualAlloc, VirtualFree, VirtualProtect,
|
||||
},
|
||||
Threading::GetCurrentProcess,
|
||||
};
|
||||
|
||||
const TRAMPOLINE_CAP: usize = 256;
|
||||
|
||||
pub(super) struct Aarch64Detour<T: Function> {
|
||||
target: *mut u8,
|
||||
detour: *const u8,
|
||||
trampoline: *mut u8,
|
||||
original_prologue: [u8; STOLEN_BYTES],
|
||||
enabled: std::cell::Cell<bool>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Function> Send for Aarch64Detour<T> {}
|
||||
unsafe impl<T: Function> Sync for Aarch64Detour<T> {}
|
||||
|
||||
impl<T: Function> Aarch64Detour<T> {
|
||||
pub(super) unsafe fn new(target: T, detour: T) -> Result<Self, ()> {
|
||||
let target_ptr = target.to_ptr() as *mut u8;
|
||||
let detour_ptr = detour.to_ptr() as *const u8;
|
||||
if target_ptr.is_null() || detour_ptr.is_null() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let mut original = [0u8; STOLEN_BYTES];
|
||||
ptr::copy_nonoverlapping(target_ptr, original.as_mut_ptr(), STOLEN_BYTES);
|
||||
|
||||
let trampoline = VirtualAlloc(
|
||||
ptr::null(),
|
||||
TRAMPOLINE_CAP,
|
||||
MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
) as *mut u8;
|
||||
if trampoline.is_null() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let trampoline_addr = trampoline as u64;
|
||||
let resume = target_ptr as u64 + STOLEN_BYTES as u64;
|
||||
let body =
|
||||
match assemble_trampoline(&original, target_ptr as u64, trampoline_addr, resume) {
|
||||
Some(body) => body,
|
||||
None => match import_thunk_target(&original, target_ptr as u64) {
|
||||
Some(target) => {
|
||||
let mut body = Vec::new();
|
||||
append_abs_branch(&mut body, target, false);
|
||||
body
|
||||
}
|
||||
None => {
|
||||
VirtualFree(trampoline.cast(), 0, MEM_RELEASE);
|
||||
return Err(());
|
||||
}
|
||||
},
|
||||
};
|
||||
if body.len() > TRAMPOLINE_CAP {
|
||||
VirtualFree(trampoline.cast(), 0, MEM_RELEASE);
|
||||
return Err(());
|
||||
}
|
||||
ptr::copy_nonoverlapping(body.as_ptr(), trampoline, body.len());
|
||||
|
||||
let mut old = 0 as PAGE_PROTECTION_FLAGS;
|
||||
VirtualProtect(
|
||||
trampoline.cast(),
|
||||
TRAMPOLINE_CAP,
|
||||
PAGE_EXECUTE_READ,
|
||||
&mut old,
|
||||
);
|
||||
FlushInstructionCache(GetCurrentProcess(), trampoline.cast(), TRAMPOLINE_CAP);
|
||||
|
||||
Ok(Self {
|
||||
target: target_ptr,
|
||||
detour: detour_ptr,
|
||||
trampoline,
|
||||
original_prologue: original,
|
||||
enabled: std::cell::Cell::new(false),
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) unsafe fn enable(&self) -> Result<(), ()> {
|
||||
if self.enabled.get() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut patch = Vec::new();
|
||||
append_abs_branch(&mut patch, self.detour as u64, false);
|
||||
if patch.len() > STOLEN_BYTES {
|
||||
return Err(());
|
||||
}
|
||||
while patch.len() < STOLEN_BYTES {
|
||||
patch.extend_from_slice(&NOP.to_le_bytes());
|
||||
}
|
||||
|
||||
let mut old = 0 as PAGE_PROTECTION_FLAGS;
|
||||
if VirtualProtect(
|
||||
self.target.cast(),
|
||||
STOLEN_BYTES,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
&mut old,
|
||||
) == 0
|
||||
{
|
||||
return Err(());
|
||||
}
|
||||
ptr::copy_nonoverlapping(patch.as_ptr(), self.target, STOLEN_BYTES);
|
||||
let mut restore = 0 as PAGE_PROTECTION_FLAGS;
|
||||
VirtualProtect(self.target.cast(), STOLEN_BYTES, old, &mut restore);
|
||||
FlushInstructionCache(GetCurrentProcess(), self.target.cast(), STOLEN_BYTES);
|
||||
self.enabled.set(true);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn disable(&self) {
|
||||
if !self.enabled.get() {
|
||||
return;
|
||||
}
|
||||
let mut old = 0 as PAGE_PROTECTION_FLAGS;
|
||||
if VirtualProtect(
|
||||
self.target.cast(),
|
||||
STOLEN_BYTES,
|
||||
PAGE_EXECUTE_READWRITE,
|
||||
&mut old,
|
||||
) != 0
|
||||
{
|
||||
ptr::copy_nonoverlapping(
|
||||
self.original_prologue.as_ptr(),
|
||||
self.target,
|
||||
STOLEN_BYTES,
|
||||
);
|
||||
let mut restore = 0 as PAGE_PROTECTION_FLAGS;
|
||||
VirtualProtect(self.target.cast(), STOLEN_BYTES, old, &mut restore);
|
||||
FlushInstructionCache(GetCurrentProcess(), self.target.cast(), STOLEN_BYTES);
|
||||
}
|
||||
self.enabled.set(false);
|
||||
}
|
||||
|
||||
pub(super) fn trampoline_fn(&self) -> T {
|
||||
unsafe { T::from_ptr(self.trampoline as *const ()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Function> Drop for Aarch64Detour<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.disable();
|
||||
if !self.trampoline.is_null() {
|
||||
VirtualFree(self.trampoline.cast(), 0, MEM_RELEASE);
|
||||
self.trampoline = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user