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:
+375
@@ -0,0 +1,375 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "fluxer_game_hook"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"retour",
|
||||
"windows",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced-x86"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c447cff8c7f384a7d4f741cfcff32f75f3ad02b406432e8d6c878d56b1edf6b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mmap-fixed-fixed"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0681853891801e4763dc252e843672faf32bcfee27a0aa3b19733902af450acc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "region"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"mach2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "retour"
|
||||
version = "0.4.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ead4bc8e12d553ff70769c5f5c21f5f4f0e73c0018068a6bb5a3d7d3b9e57ec7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generic-array",
|
||||
"iced-x86",
|
||||
"libc",
|
||||
"mmap-fixed-fixed",
|
||||
"once_cell",
|
||||
"region",
|
||||
"slice-pool2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slice-pool2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a3d689654af89bdfeba29a914ab6ac0236d382eb3b764f7454dde052f2821f8"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "fluxer_game_hook"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
license = "AGPL-3.0-or-later"
|
||||
publish = false
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
# `retour` only ships x86/x86_64 trampoline + patcher backends (its `arch`
|
||||
# module has no aarch64 variant), so it does not even compile for
|
||||
# aarch64-pc-windows-msvc. Gate it to the architectures it supports; the
|
||||
# aarch64 hook uses the in-crate `inline_hook::aarch64` backend instead.
|
||||
[target.'cfg(all(target_os = "windows", any(target_arch = "x86", target_arch = "x86_64")))'.dependencies]
|
||||
retour = "0.4.0-alpha.4"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows = {version = "0.62.2", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Direct3D",
|
||||
"Win32_Graphics_Direct3D9",
|
||||
"Win32_Graphics_Direct3D10",
|
||||
"Win32_Graphics_Direct3D11",
|
||||
"Win32_Graphics_Direct3D11on12",
|
||||
"Win32_Graphics_Direct3D12",
|
||||
"Win32_Graphics_Dxgi",
|
||||
"Win32_Graphics_Dxgi_Common",
|
||||
"Win32_Graphics_Gdi",
|
||||
]}
|
||||
windows-sys = {version = "0.61.2", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Gdi",
|
||||
"Win32_Graphics_OpenGL",
|
||||
"Win32_Security",
|
||||
"Win32_System_Diagnostics_Debug",
|
||||
"Win32_System_LibraryLoader",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Performance",
|
||||
"Win32_System_SystemServices",
|
||||
"Win32_System_Threading",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
]}
|
||||
@@ -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
@@ -0,0 +1,412 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
#[path = "../src/arm64_reloc.rs"]
|
||||
mod arm64_reloc;
|
||||
|
||||
use arm64_reloc::*;
|
||||
|
||||
fn adr(rd: u32, imm21: i32) -> u32 {
|
||||
let raw = (imm21 as u32) & 0x1F_FFFF;
|
||||
let immlo = (raw & 0x3) << 29;
|
||||
let immhi = ((raw >> 2) & 0x7FFFF) << 5;
|
||||
0x1000_0000 | immlo | immhi | (rd & 0x1F)
|
||||
}
|
||||
fn adrp(rd: u32, imm21: i32) -> u32 {
|
||||
let raw = (imm21 as u32) & 0x1F_FFFF;
|
||||
let immlo = (raw & 0x3) << 29;
|
||||
let immhi = ((raw >> 2) & 0x7FFFF) << 5;
|
||||
0x9000_0000 | immlo | immhi | (rd & 0x1F)
|
||||
}
|
||||
fn b(off_words: i32) -> u32 {
|
||||
0x1400_0000 | ((off_words as u32) & 0x03FF_FFFF)
|
||||
}
|
||||
fn bl(off_words: i32) -> u32 {
|
||||
0x9400_0000 | ((off_words as u32) & 0x03FF_FFFF)
|
||||
}
|
||||
fn bcond(cond: u32, off_words: i32) -> u32 {
|
||||
0x5400_0000 | (((off_words as u32) & 0x7FFFF) << 5) | (cond & 0xF)
|
||||
}
|
||||
fn cbz(rt: u32, off_words: i32) -> u32 {
|
||||
0xB400_0000 | (((off_words as u32) & 0x7FFFF) << 5) | (rt & 0x1F)
|
||||
}
|
||||
fn tbz(rt: u32, bit: u32, off_words: i32) -> u32 {
|
||||
let b5 = (bit & 0x20) << (31 - 5);
|
||||
let b40 = (bit & 0x1F) << 19;
|
||||
0x3600_0000 | b5 | b40 | (((off_words as u32) & 0x3FFF) << 5) | (rt & 0x1F)
|
||||
}
|
||||
fn ldr_lit(rt: u32, off_words: i32) -> u32 {
|
||||
0x5800_0000 | (((off_words as u32) & 0x7FFFF) << 5) | (rt & 0x1F)
|
||||
}
|
||||
|
||||
fn adr_target(insn: u32, pc: u64, page: bool) -> u64 {
|
||||
let immlo = ((insn >> 29) & 0x3) as i64;
|
||||
let immhi = ((insn >> 5) & 0x7FFFF) as i64;
|
||||
let raw = (immhi << 2) | immlo;
|
||||
let imm21 = (raw << 43) >> 43;
|
||||
if page {
|
||||
((pc & !0xFFF) as i64 + imm21 * 4096) as u64
|
||||
} else {
|
||||
(pc as i64 + imm21) as u64
|
||||
}
|
||||
}
|
||||
fn imm19_target(insn: u32, pc: u64) -> u64 {
|
||||
let imm19 = ((insn >> 5) & 0x7FFFF) as i64;
|
||||
let off = ((imm19 << 45) >> 45) * 4;
|
||||
(pc as i64 + off) as u64
|
||||
}
|
||||
fn imm14_target(insn: u32, pc: u64) -> u64 {
|
||||
let imm14 = ((insn >> 5) & 0x3FFF) as i64;
|
||||
let off = ((imm14 << 50) >> 50) * 4;
|
||||
(pc as i64 + off) as u64
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_pc_relative_copied_verbatim() {
|
||||
let stp = 0xA9BF_7BFD;
|
||||
assert_eq!(relocate_instruction(stp, 0x1000, 0x9000), Some(stp));
|
||||
let mov = 0xAA01_03E0;
|
||||
assert_eq!(relocate_instruction(mov, 0x1000, 0x9000), Some(mov));
|
||||
let sub = 0xD100_83FF;
|
||||
assert_eq!(relocate_instruction(sub, 0x1000, 0x9000), Some(sub));
|
||||
let mov_fp_sp = 0x9100_03FD;
|
||||
assert_eq!(
|
||||
relocate_instruction(mov_fp_sp, 0x1000, 0x9000),
|
||||
Some(mov_fp_sp)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adr_relocates_to_same_target() {
|
||||
let src_pc = 0x140_0010_0000u64;
|
||||
let dst_pc = 0x140_0010_8000u64;
|
||||
let insn = adr(0, 0x4000);
|
||||
let original = adr_target(insn, src_pc, false);
|
||||
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
|
||||
assert_eq!(adr_target(reloc, dst_pc, false), original);
|
||||
assert_eq!(reloc & 0x1F, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adr_negative_offset() {
|
||||
let src_pc = 0x140_0010_0000u64;
|
||||
let dst_pc = 0x140_0010_0010u64;
|
||||
let insn = adr(5, -0x100);
|
||||
let original = adr_target(insn, src_pc, false);
|
||||
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
|
||||
assert_eq!(adr_target(reloc, dst_pc, false), original);
|
||||
assert_eq!(reloc & 0x1F, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adr_out_of_range_refused() {
|
||||
let src_pc = 0x0000_0000_0000u64;
|
||||
let dst_pc = 0x0000_0080_0000u64;
|
||||
let insn = adr(0, 0x1000);
|
||||
assert_eq!(relocate_instruction(insn, src_pc, dst_pc), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adrp_relocates_to_same_page() {
|
||||
let src_pc = 0x140_0010_0000u64;
|
||||
let dst_pc = 0x140_0030_0000u64;
|
||||
let insn = adrp(9, 0x10);
|
||||
let original = adr_target(insn, src_pc, true);
|
||||
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
|
||||
assert_eq!(adr_target(reloc, dst_pc, true), original);
|
||||
assert_eq!(reloc & 0x1F, 9);
|
||||
assert_eq!(reloc & 0x9F00_0000, 0x9000_0000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adrp_negative() {
|
||||
let src_pc = 0x140_0090_0000u64;
|
||||
let dst_pc = 0x140_0050_0000u64;
|
||||
let insn = adrp(1, -0x20);
|
||||
let original = adr_target(insn, src_pc, true);
|
||||
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
|
||||
assert_eq!(adr_target(reloc, dst_pc, true), original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bcond_relocates() {
|
||||
let src_pc = 0x10_0000u64;
|
||||
let dst_pc = 0x12_0000u64;
|
||||
let insn = bcond(0x0, 0x40);
|
||||
let original = imm19_target(insn, src_pc);
|
||||
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
|
||||
assert_eq!(imm19_target(reloc, dst_pc), original);
|
||||
assert_eq!(reloc & 0xF, 0x0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bcond_out_of_range_refused() {
|
||||
let src_pc = 0x0u64;
|
||||
let dst_pc = 0x20_0000u64;
|
||||
let insn = bcond(0x1, 0x10);
|
||||
assert_eq!(relocate_instruction(insn, src_pc, dst_pc), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cbz_relocates() {
|
||||
let src_pc = 0x10_0000u64;
|
||||
let dst_pc = 0x10_8000u64;
|
||||
let insn = cbz(3, -0x20);
|
||||
let original = imm19_target(insn, src_pc);
|
||||
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
|
||||
assert_eq!(imm19_target(reloc, dst_pc), original);
|
||||
assert_eq!(reloc & 0x1F, 3);
|
||||
assert_eq!(reloc & 0x8000_0000, 0x8000_0000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tbz_relocates() {
|
||||
let src_pc = 0x10_0000u64;
|
||||
let dst_pc = 0x10_1000u64;
|
||||
let insn = tbz(7, 5, 0x10);
|
||||
let original = imm14_target(insn, src_pc);
|
||||
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
|
||||
assert_eq!(imm14_target(reloc, dst_pc), original);
|
||||
assert_eq!(reloc & 0x1F, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tbz_out_of_range_refused() {
|
||||
let src_pc = 0x0u64;
|
||||
let dst_pc = 0x1_0000u64;
|
||||
let insn = tbz(0, 1, 0x8);
|
||||
assert_eq!(relocate_instruction(insn, src_pc, dst_pc), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ldr_literal_relocates() {
|
||||
let src_pc = 0x20_0000u64;
|
||||
let dst_pc = 0x20_4000u64;
|
||||
let insn = ldr_lit(2, 0x100);
|
||||
let original = imm19_target(insn, src_pc);
|
||||
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
|
||||
assert_eq!(imm19_target(reloc, dst_pc), original);
|
||||
assert_eq!(reloc & 0x1F, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn direct_b_relocation_in_range() {
|
||||
let src_pc = 0x10_0000u64;
|
||||
let dst_pc = 0x14_0000u64;
|
||||
let insn = b(0x100);
|
||||
let target = branch_target(insn, src_pc).unwrap();
|
||||
let reloc = relocate_instruction(insn, src_pc, dst_pc).expect("in range");
|
||||
assert_eq!(branch_target(reloc, dst_pc).unwrap(), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn branch_target_decode() {
|
||||
let pc = 0x10_0000u64;
|
||||
assert_eq!(branch_target(b(4), pc), Some(pc + 16));
|
||||
assert_eq!(branch_target(b(-4), pc), Some(pc - 16));
|
||||
assert_eq!(branch_target(bl(1), pc), Some(pc + 4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abs_branch_encoding() {
|
||||
let mut bytes = Vec::new();
|
||||
append_abs_branch(&mut bytes, 0x1234_5678_9ABC_DEF0, false);
|
||||
assert_eq!(bytes.len(), 16);
|
||||
assert_eq!(
|
||||
u32::from_le_bytes(bytes[0..4].try_into().unwrap()),
|
||||
LDR_X16_PC8
|
||||
);
|
||||
assert_eq!(u32::from_le_bytes(bytes[4..8].try_into().unwrap()), BR_X16);
|
||||
assert_eq!(
|
||||
u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
|
||||
0x1234_5678_9ABC_DEF0
|
||||
);
|
||||
let mut linked = Vec::new();
|
||||
append_abs_branch(&mut linked, 0xDEAD_BEEF, true);
|
||||
assert_eq!(
|
||||
u32::from_le_bytes(linked[4..8].try_into().unwrap()),
|
||||
BLR_X16
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classify_branches() {
|
||||
assert!(is_b(b(1)));
|
||||
assert!(!is_bl(b(1)));
|
||||
assert!(is_bl(bl(1)));
|
||||
assert!(!is_b(bl(1)));
|
||||
assert!(needs_absolute_island(b(1)));
|
||||
assert!(needs_absolute_island(bl(1)));
|
||||
assert!(!needs_absolute_island(adr(0, 1)));
|
||||
assert!(!needs_absolute_island(NOP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assemble_trampoline_relocates_prologue() {
|
||||
let stp = 0xA9BF_7BFDu32;
|
||||
let mov = 0x9100_03FDu32;
|
||||
let adrp_insn = adrp(8, 0x20);
|
||||
let mut prologue = Vec::new();
|
||||
for insn in [stp, mov, adrp_insn, NOP] {
|
||||
prologue.extend_from_slice(&insn.to_le_bytes());
|
||||
}
|
||||
let src_base = 0x140_0010_0000u64;
|
||||
let dst_base = 0x140_0030_0000u64;
|
||||
let resume = src_base + STOLEN_BYTES as u64;
|
||||
let body = assemble_trampoline(&prologue, src_base, dst_base, resume).expect("relocatable");
|
||||
assert_eq!(body.len(), 32);
|
||||
assert_eq!(u32::from_le_bytes(body[0..4].try_into().unwrap()), stp);
|
||||
assert_eq!(u32::from_le_bytes(body[4..8].try_into().unwrap()), mov);
|
||||
assert_eq!(u32::from_le_bytes(body[12..16].try_into().unwrap()), NOP);
|
||||
let orig_target = adr_target(adrp_insn, src_base + 8, true);
|
||||
let reloc_adrp = u32::from_le_bytes(body[8..12].try_into().unwrap());
|
||||
assert_eq!(adr_target(reloc_adrp, dst_base + 8, true), orig_target);
|
||||
assert_eq!(
|
||||
u32::from_le_bytes(body[16..20].try_into().unwrap()),
|
||||
LDR_X16_PC8
|
||||
);
|
||||
assert_eq!(u32::from_le_bytes(body[20..24].try_into().unwrap()), BR_X16);
|
||||
assert_eq!(u64::from_le_bytes(body[24..32].try_into().unwrap()), resume);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assemble_trampoline_promotes_leading_branch() {
|
||||
let lead_b = b(0x4000);
|
||||
let mut prologue = Vec::new();
|
||||
for insn in [lead_b, NOP, NOP, NOP] {
|
||||
prologue.extend_from_slice(&insn.to_le_bytes());
|
||||
}
|
||||
let src_base = 0x140_0010_0000u64;
|
||||
let dst_base = 0x0000_7000_0000u64;
|
||||
let resume = src_base + STOLEN_BYTES as u64;
|
||||
let body = assemble_trampoline(&prologue, src_base, dst_base, resume).expect("island path");
|
||||
assert_eq!(body.len(), 48);
|
||||
let first = u32::from_le_bytes(body[0..4].try_into().unwrap());
|
||||
assert!(is_b(first));
|
||||
let island_addr = branch_target(first, dst_base).unwrap();
|
||||
assert_eq!(island_addr, dst_base + 16 + 16);
|
||||
let original_b_target = branch_target(lead_b, src_base).unwrap();
|
||||
assert_eq!(
|
||||
u32::from_le_bytes(body[32..36].try_into().unwrap()),
|
||||
LDR_X16_PC8
|
||||
);
|
||||
assert_eq!(u32::from_le_bytes(body[36..40].try_into().unwrap()), BR_X16);
|
||||
assert_eq!(
|
||||
u64::from_le_bytes(body[40..48].try_into().unwrap()),
|
||||
original_b_target
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assemble_trampoline_promotes_leading_bl_with_blr() {
|
||||
let lead_bl = bl(0x100);
|
||||
let mut prologue = Vec::new();
|
||||
for insn in [lead_bl, NOP, NOP, NOP] {
|
||||
prologue.extend_from_slice(&insn.to_le_bytes());
|
||||
}
|
||||
let src_base = 0x140_0010_0000u64;
|
||||
let dst_base = 0x0000_7000_0000u64;
|
||||
let resume = src_base + STOLEN_BYTES as u64;
|
||||
let body = assemble_trampoline(&prologue, src_base, dst_base, resume).expect("island path");
|
||||
assert_eq!(body.len(), 48);
|
||||
let first = u32::from_le_bytes(body[0..4].try_into().unwrap());
|
||||
assert!(is_bl(first));
|
||||
let original = branch_target(lead_bl, src_base).unwrap();
|
||||
assert_eq!(
|
||||
u32::from_le_bytes(body[36..40].try_into().unwrap()),
|
||||
BLR_X16
|
||||
);
|
||||
assert_eq!(
|
||||
u64::from_le_bytes(body[40..48].try_into().unwrap()),
|
||||
original
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assemble_trampoline_two_islands() {
|
||||
let b0 = b(0x10);
|
||||
let bl1 = bl(0x20);
|
||||
let mut prologue = Vec::new();
|
||||
for insn in [b0, NOP, bl1, NOP] {
|
||||
prologue.extend_from_slice(&insn.to_le_bytes());
|
||||
}
|
||||
let src_base = 0x140_0010_0000u64;
|
||||
let dst_base = 0x0000_7000_0000u64;
|
||||
let resume = src_base + STOLEN_BYTES as u64;
|
||||
let body = assemble_trampoline(&prologue, src_base, dst_base, resume).expect("islands");
|
||||
assert_eq!(body.len(), 64);
|
||||
let first = u32::from_le_bytes(body[0..4].try_into().unwrap());
|
||||
let third = u32::from_le_bytes(body[8..12].try_into().unwrap());
|
||||
let island0 = branch_target(first, dst_base).unwrap();
|
||||
let island1 = branch_target(third, dst_base + 8).unwrap();
|
||||
assert_eq!(island0, dst_base + 32);
|
||||
assert_eq!(island1, dst_base + 48);
|
||||
assert_eq!(
|
||||
u64::from_le_bytes(body[40..48].try_into().unwrap()),
|
||||
branch_target(b0, src_base).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
u64::from_le_bytes(body[56..64].try_into().unwrap()),
|
||||
branch_target(bl1, src_base + 8).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assemble_trampoline_refuses_unrelocatable_narrow_branch() {
|
||||
let cond = bcond(0x2, 0x10);
|
||||
let mut prologue = Vec::new();
|
||||
for insn in [NOP, cond, NOP, NOP] {
|
||||
prologue.extend_from_slice(&insn.to_le_bytes());
|
||||
}
|
||||
let src_base = 0x0u64;
|
||||
let dst_base = 0x0000_0080_0000u64;
|
||||
let resume = src_base + STOLEN_BYTES as u64;
|
||||
assert_eq!(
|
||||
assemble_trampoline(&prologue, src_base, dst_base, resume),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relocated_prologue_matches_assemble_prefix() {
|
||||
let stp = 0xA9BF_7BFDu32;
|
||||
let mut prologue = Vec::new();
|
||||
for insn in [stp, NOP, NOP, NOP] {
|
||||
prologue.extend_from_slice(&insn.to_le_bytes());
|
||||
}
|
||||
let src_base = 0x140_0010_0000u64;
|
||||
let dst_base = 0x140_0030_0000u64;
|
||||
let pro = relocated_prologue(&prologue, src_base, dst_base).expect("ok");
|
||||
assert_eq!(pro.len(), 16);
|
||||
assert_eq!(u32::from_le_bytes(pro[0..4].try_into().unwrap()), stp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn island_holds_absolute_branch_to_original_target() {
|
||||
let lead_bl = bl(0x100);
|
||||
let src_base = 0x140_0010_0000u64;
|
||||
let original_target = branch_target(lead_bl, src_base).unwrap();
|
||||
let island = island_for_branch(lead_bl, src_base).expect("island");
|
||||
assert_eq!(island.len(), 16);
|
||||
assert_eq!(
|
||||
u32::from_le_bytes(island[4..8].try_into().unwrap()),
|
||||
BLR_X16
|
||||
);
|
||||
assert_eq!(
|
||||
u64::from_le_bytes(island[8..16].try_into().unwrap()),
|
||||
original_target
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stolen_bytes_is_four_instructions() {
|
||||
assert_eq!(STOLEN_BYTES, 16);
|
||||
assert_eq!(STOLEN_BYTES % 4, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_imm26_rejects_unaligned_and_overflow() {
|
||||
assert_eq!(encode_imm26(3), None);
|
||||
assert_eq!(encode_imm26(4), Some(1));
|
||||
assert_eq!(encode_imm26(-4), Some((-1i32 as u32) & 0x03FF_FFFF));
|
||||
assert_eq!(encode_imm26(1 << 27), None);
|
||||
}
|
||||
Reference in New Issue
Block a user