use crate::abi::Registers;
use crate::{Load, Off};
const WAIT: &str = "48 3B ?? 44 8B ?? 44 0F 4C CA 48 8B 55 D8 45 33 C0 8B 4D E0 \
C7 44 24 20 01 00 00 00 FF 15";
const MESSAGE_WAIT: &str = "48 3B ?? 44 8B ?? 44 0F 4C C2 48 8B 55 D8 41 B9 FF 1C 00 00 8B 4D E0 \
C7 44 24 20 06 00 00 00 FF D0";
const WAIT_AT: u64 = 10;
const DIVIDE: [u8; 10] = [0x48, 0xB8, 0xDB, 0x34, 0xB6, 0xD7, 0x82, 0xDE, 0x1B, 0x43];
const DIVIDE_RBX: [u8; 7] = [0x48, 0xF7, 0xEB, 0x48, 0xC1, 0xFA, 0x12];
const DIVIDE_SPAN: usize = 0x80;
const SERVER_WAIT: &str = "44 39 BB 3C 05 00 00 75 08 41 B9 00 5A 62 02 EB 12 48 81 7D ?? 80 9F D5 00 \
41 B9 40 42 0F 00 4C 0F 4F CF 4D 8B C4 48 8D 55 ?? 48 8D 4D ?? E8";
const SERVER_WAIT_AT: u64 = 35;
const WORKERS: &str = "41 0F B6 47 09 8B CF 40 3A C7 0F 42 C8 84 DB 89 8D ?? ?? ?? ?? 8B CF 0F B6 C3 \
0F 45 C8 41 0F B6 47 0A 44 0F B6 E9";
const DEFAULT_WORKERS_AT: u64 = 5;
const BACKGROUND_WORKERS_AT: u64 = 34;
pub const SERVER_IDLE_WAIT_NS: u64 = 40_000_000;
pub const SERVER_ONLINE_WAIT_NS: u64 = 1_000_000;
pub const SERVER_SLOW_WAIT_NS: u64 = 100_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct LoopWait {
pub ms: u32,
pub left_ns: i64,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct LoopWaits {
pub handles: bool,
pub messages: bool,
}
fn divides_rbx(before: &[u8]) -> bool {
before
.windows(DIVIDE.len())
.enumerate()
.any(|(at, window)| {
window == DIVIDE
&& before[at + DIVIDE.len()..]
.windows(DIVIDE_RBX.len())
.take(9)
.any(|next| next == DIVIDE_RBX)
})
}
fn checks(code: &[u8], site: u64) -> bool {
let start = code.as_ptr().addr() as u64;
let Some(end) = site
.checked_sub(start)
.and_then(|end| usize::try_from(end).ok())
else {
return false;
};
code.get(end.saturating_sub(DIVIDE_SPAN)..end)
.is_some_and(divides_rbx)
}
fn site(load: &Load<'_>, pattern: &str, at: u64) -> Option<u64> {
load.find_code(pattern).ok().map(|found| found + at)
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct TaskWorkers {
pub default: u8,
pub background: u8,
}
impl TaskWorkers {
#[must_use]
pub const fn at_most(workers: u8) -> Self {
Self {
default: workers,
background: workers,
}
}
}
impl Load<'_> {
pub fn on_loop_wait<F>(&mut self, handler: F) -> Result<LoopWaits, Off>
where
F: Fn(LoopWait) + Clone + Send + Sync + 'static,
{
let code = self.code()?;
let wait_site = site(self, WAIT, WAIT_AT).filter(|&at| checks(code, at));
let message_site = site(self, MESSAGE_WAIT, WAIT_AT).filter(|&at| checks(code, at));
let mut found = LoopWaits::default();
let wait = |ms: u64, left: u64| LoopWait {
ms: u32::try_from(ms & 0xffff_ffff).unwrap_or(u32::MAX),
left_ns: i64::from_ne_bytes(left.to_ne_bytes()),
};
if let Some(at) = wait_site {
let handler = handler.clone();
unsafe {
self.hook_at(at, move |registers: &mut Registers| {
handler(wait(registers.r9, registers.rbx));
})
}?;
found.handles = true;
}
if let Some(at) = message_site {
unsafe {
self.hook_at(at, move |registers: &mut Registers| {
handler(wait(registers.r8, registers.rbx));
})
}?;
found.messages = true;
}
Ok(found)
}
pub fn on_server_wait<F>(&mut self, handler: F) -> Result<bool, Off>
where
F: Fn(&mut u64) + Send + Sync + 'static,
{
let Some(at) = site(self, SERVER_WAIT, SERVER_WAIT_AT) else {
return Ok(false);
};
unsafe {
self.hook_at(at, move |registers: &mut Registers| {
handler(&mut registers.r9);
})
}?;
Ok(true)
}
pub fn cap_task_workers(&mut self, caps: TaskWorkers) -> Result<bool, Off> {
if caps == TaskWorkers::default() || self.host().build().side != crate::abi::Side::SERVER {
return Ok(false);
}
let Some(at) = site(self, WORKERS, 0) else {
return Ok(false);
};
for (offset, cap) in [
(DEFAULT_WORKERS_AT, caps.default),
(BACKGROUND_WORKERS_AT, caps.background),
] {
if cap > 0 {
unsafe {
self.hook_at(at + offset, move |registers: &mut Registers| {
registers.rax = registers.rax.min(u64::from(cap));
})
}?;
}
}
Ok(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_division_of_rbx_is_recognised() {
let mut server = DIVIDE.to_vec();
server.extend([0xBF, 0xFF, 0xFF, 0xFF, 0xFF]);
server.extend(DIVIDE_RBX);
assert!(divides_rbx(&server));
let mut client = vec![0x90; 4];
client.extend(DIVIDE);
client.extend(DIVIDE_RBX);
assert!(divides_rbx(&client));
let mut other = DIVIDE.to_vec();
other.extend([0x48, 0xF7, 0xE9, 0x48, 0xC1, 0xFA, 0x12]);
assert!(!divides_rbx(&other));
assert!(!divides_rbx(&DIVIDE));
assert!(!divides_rbx(&[]));
}
#[test]
fn the_patterns_parse() {
for pattern in [WAIT, MESSAGE_WAIT, SERVER_WAIT, WORKERS] {
assert!(crate::code::Pattern::parse(pattern).is_ok(), "{pattern}");
}
}
}