From ccaaeac251ebf031b5dbbd1677cbac6fa0bf7c1f Mon Sep 17 00:00:00 2001 From: Lucy Date: Sat, 3 Oct 2026 21:22:58 -0400 Subject: [PATCH 01/20] convert to byond-scan and fix a lot of shit --- Cargo.lock | 89 +----- Cargo.toml | 3 +- README.md | 8 + auxcov/src/lib.rs | 13 +- auxtools-impl/Cargo.toml | 1 - auxtools-impl/src/lib.rs | 49 +--- auxtools/Cargo.toml | 9 +- auxtools/build.rs | 4 + auxtools/src/hooks.cpp | 23 +- auxtools/src/hooks.rs | 110 +++---- auxtools/src/lib.rs | 242 +--------------- auxtools/src/raw_types/funcs.cpp | 74 ++++- auxtools/src/raw_types/lists.rs | 27 +- auxtools/src/raw_types/procs.rs | 18 +- auxtools/src/raw_types/values.rs | 43 ++- auxtools/src/sigscan.rs | 211 -------------- auxtools/src/sigscan/linux.rs | 99 ------- auxtools/src/sigscan/windows.rs | 95 ------ auxtools/src/symbols.rs | 119 ++++++++ auxtools/src/symbols/linux.rs | 270 ++++++++++++++++++ auxtools/src/symbols/windows.rs | 251 ++++++++++++++++ auxtools/src/version.rs | 76 +---- debug_server/Cargo.toml | 5 +- debug_server/src/ckey_override.rs | 32 ++- debug_server/src/stddef.rs | 52 +--- instruction_hooking/Cargo.toml | 8 - .../src/execute_instruction_hook.unix.S | 7 +- instruction_hooking/src/lib.rs | 46 +-- 28 files changed, 970 insertions(+), 1014 deletions(-) delete mode 100644 auxtools/src/sigscan.rs delete mode 100644 auxtools/src/sigscan/linux.rs delete mode 100644 auxtools/src/sigscan/windows.rs create mode 100644 auxtools/src/symbols.rs create mode 100644 auxtools/src/symbols/linux.rs create mode 100644 auxtools/src/symbols/windows.rs diff --git a/Cargo.lock b/Cargo.lock index 41a5625c..c134ab5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -198,14 +198,11 @@ version = "0.1.0" dependencies = [ "ahash", "auxtools-impl", + "byond-scan", "cc", "ctor", - "dashmap", "fxhash", "inventory", - "lazy_static", - "libc", - "once_cell", "retour", "winapi", ] @@ -214,7 +211,6 @@ dependencies = [ name = "auxtools-impl" version = "0.1.0" dependencies = [ - "hex", "proc-macro2", "quote", "syn 2.0.91", @@ -297,6 +293,16 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byond-scan" +version = "0.1.0" +source = "git+https://github.com/Absolucy/byond-scan#362cbe051c27aaa6d9a06f0aaa245db8c35ebd00" +dependencies = [ + "iced-x86", + "libc", + "thiserror", +] + [[package]] name = "byond_get" version = "0.1.0" @@ -639,23 +645,9 @@ dependencies = [ "syn 2.0.91", ] -[[package]] -name = "dashmap" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - [[package]] name = "debug_server" -version = "2.3.7" +version = "2.3.8" dependencies = [ "auxtools", "bincode", @@ -663,7 +655,6 @@ dependencies = [ "dmasm", "instruction_hooking", "lazy_static", - "libc", "region", "retour", "serde", @@ -1061,12 +1052,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - [[package]] name = "hashbrown" version = "0.15.2" @@ -1100,12 +1085,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hmac" version = "0.12.1" @@ -1468,11 +1447,7 @@ dependencies = [ "auxtools", "cc", "dmasm", - "libc", "retour", - "symbolic-common", - "symbolic-demangle", - "winapi", ] [[package]] @@ -1573,16 +1548,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - [[package]] name = "lockfree-object-pool" version = "0.1.6" @@ -1848,19 +1813,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets", -] - [[package]] name = "parse-zoneinfo" version = "0.3.1" @@ -2151,15 +2103,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags 2.6.0", -] - [[package]] name = "regex" version = "1.11.1" @@ -2388,12 +2331,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "security-framework" version = "2.11.1" diff --git a/Cargo.toml b/Cargo.toml index 7dd5c6ec..98fc9a43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,14 @@ authors = ["Sophie Wallace ", "Lucy "] edition = "2021" license = "MIT" repository = "https://github.com/wilox/auxtools" -rust-version = "1.76" +rust-version = "1.88" version = "0.1.0" [workspace.dependencies] lazy_static = "1" retour = { version = "=0.4.0-alpha.4", default-features = false } dmasm = { git = "https://github.com/willox/dmasm" } +byond-scan = { git = "https://github.com/Absolucy/byond-scan" } [workspace.lints.rust] static_mut_refs = "allow" diff --git a/README.md b/README.md index d53bdabc..59cfcd45 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,14 @@ Debug Server - Working with SpaceManiac's [SpacemanDMM](https://github.com/Space *Dissassembly* - When currently stopped at a breakpoint, enter the `#dis` command into the Debug Console in VSC to see the DM bytecode for the current proc. You can also provide it any arbitrary proc path like `#dis /mob/proc/Life`. +## Supported BYOND versions + +516.1659 through 516.1688, on Windows and Linux. Those are the builds the patterns that find BYOND's functions were checked against. A newer build is still tried, and fails at init with the name of whatever stopped matching. An older build, or another major version, is refused at init with an error that names the build. + +The patterns are run by [byond-scan](https://github.com/Absolucy/byond-scan) and live in `auxtools/src/symbols/`. They are copies of the ones in the byond-re repo's `byond_catalog`, which checks each against every local build, so a change to one has to land in both. + +What was run on Linux (516.1687): the `auxtest` suite, and the debug server starting up. The code coverage tool's report was checked on Windows and Linux 1687 against a world with a known set of lines. Stepping and breakpoints with a real debugger attached were not tested on either platform. + ## Dependencies *These instructions were taken directly from tgstation's [rust-g](https://github.com/tgstation/rust-g) documentation.* diff --git a/auxcov/src/lib.rs b/auxcov/src/lib.rs index 931166e4..7cadc054 100644 --- a/auxcov/src/lib.rs +++ b/auxcov/src/lib.rs @@ -2,8 +2,6 @@ mod codecov; -use std::any::{Any, TypeId}; - use auxtools::*; use codecov::Tracker; use instruction_hooking::INSTRUCTION_HOOKS; @@ -15,14 +13,13 @@ where unsafe { let hooks = INSTRUCTION_HOOKS.get_mut(); - let tracker_tid = TypeId::of::(); - let tracker_option = hooks.iter_mut().find(|hook| (*hook).as_ref().type_id() == tracker_tid); + // `type_id()` on the boxed hook itself would give the id of the trait + // object, never `Tracker`'s, so ask the hook for its concrete + // value as `Any` first. + let tracker_option = hooks.iter_mut().find_map(|hook| hook.as_mut().as_any().downcast_mut::()); match tracker_option { - Some(existing_hook) => { - let mut_hook = existing_hook.as_mut(); - let any_hook = mut_hook.as_any(); - let existing_tracker = any_hook.downcast_mut::().unwrap(); + Some(existing_tracker) => { f(existing_tracker); } None => { diff --git a/auxtools-impl/Cargo.toml b/auxtools-impl/Cargo.toml index 42b25946..98297de8 100644 --- a/auxtools-impl/Cargo.toml +++ b/auxtools-impl/Cargo.toml @@ -15,7 +15,6 @@ doctest = false [dependencies] proc-macro2 = "1.0" quote = "1.0" -hex = "0.4" syn = { version = "2", features = ["full"] } [lints] diff --git a/auxtools-impl/src/lib.rs b/auxtools-impl/src/lib.rs index 145481c7..e65a7cf1 100644 --- a/auxtools-impl/src/lib.rs +++ b/auxtools-impl/src/lib.rs @@ -2,60 +2,13 @@ mod versioned_struct; use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, spanned::Spanned, Lit}; - -fn from_signature(s: String) -> Vec> { - s.trim() - .split(' ') - .map(|byte| { - let byte = byte.trim(); - match byte.len() { - 2 => { - if byte == "??" { - None - } else { - hex::decode(byte).map(|decoded_byte| decoded_byte[0]).ok() - } - } - _ => None - } - }) - .collect() -} +use syn::{spanned::Spanned, Lit}; #[proc_macro_attribute] pub fn versioned(attr: TokenStream, item: TokenStream) -> TokenStream { versioned_struct::versioned(attr.into(), item.into()).into() } -#[proc_macro] -pub fn convert_signature(input: TokenStream) -> TokenStream { - let string = parse_macro_input!(input as Lit); - let string = match string { - Lit::Str(lit) => lit.value(), - _ => panic!("not string input") - }; - - let streams: Vec = from_signature(string) - .into_iter() - .map(|x| match x { - Some(byte) => { - quote! { - Some(#byte as u8) - } - } - None => { - quote! { None } - } - }) - .collect(); - - let result = quote! { - &[ #( #streams, )* ] - }; - result.into() -} - fn extract_args(a: &syn::FnArg) -> &syn::PatType { match a { syn::FnArg::Typed(p) => p, diff --git a/auxtools/Cargo.toml b/auxtools/Cargo.toml index b9593533..b51cd8df 100644 --- a/auxtools/Cargo.toml +++ b/auxtools/Cargo.toml @@ -16,20 +16,15 @@ cc = "1.0" [dependencies] auxtools-impl = { path = "../auxtools-impl", version = "0.1.0", package = "auxtools-impl" } -once_cell = "1" +byond-scan = { workspace = true } inventory = "0.3" -lazy_static = { workspace = true } -dashmap = "6" ahash = "0.8" fxhash = "0.2" ctor = "0.2" retour = { workspace = true } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.9", features = ["winuser", "libloaderapi", "psapi", "processthreadsapi"] } - -[target.'cfg(unix)'.dependencies] -libc = "0.2" +winapi = { version = "0.3.9", features = ["libloaderapi"] } [lints] workspace = true diff --git a/auxtools/build.rs b/auxtools/build.rs index 80947482..50e87e83 100644 --- a/auxtools/build.rs +++ b/auxtools/build.rs @@ -1,4 +1,8 @@ fn main() { + println!("cargo:rerun-if-changed=src/hooks.cpp"); + println!("cargo:rerun-if-changed=src/hooks.h"); + println!("cargo:rerun-if-changed=src/raw_types/funcs.cpp"); + cc::Build::new() .include("src/") .file("src/hooks.cpp") diff --git a/auxtools/src/hooks.cpp b/auxtools/src/hooks.cpp index 652e86b1..decc8d1c 100644 --- a/auxtools/src/hooks.cpp +++ b/auxtools/src/hooks.cpp @@ -3,7 +3,16 @@ // The type of the func defined in Byond using Runtime_Ptr = void(*)(char *pError); -using CallProcById_Ptr = Value(LINUX_REGPARM3 *)(Value, uint32_t, uint32_t, uint32_t, Value, Value*, uint32_t, uint32_t, uint32_t); +// Plain cdecl on both platforms. This carried regparm(3) on Linux, which was +// never right: `call_proc_by_id` reads all twelve of its dwords off the stack, the +// first being the hidden buffer the Value return is written through, and ends +// `retn 4` to pop that dword itself. Declaring regparm(3) made GCC pass the buffer +// in eax and `usr` in edx:ecx, which shifted every argument the trampoline read +// (the proc id it looked up was really the output pointer, so no #[hook] ever +// matched) and left nothing on the stack for `retn 4` to pop, so the stack drifted +// until an SSE store faulted somewhere inside BYOND. Read byte-identical on +// 516.1669 and 516.1687. +using CallProcById_Ptr = Value(*)(Value, uint32_t, uint32_t, uint32_t, Value, Value*, uint32_t, uint32_t, uint32_t); // The type of the hook defined in hooks.rs using CallProcById_Hook_Ptr = Value(*)(Value, uint32_t, uint32_t, uint32_t, Value, Value*, uint32_t, uint32_t, uint32_t); @@ -49,9 +58,10 @@ extern "C" uint8_t call_proc_by_id_hook( uint32_t unk_1, uint32_t unk_2); -// A little function to handle the odd calling convention on Linux and pass-through to our rust hook +// Stands in for `call_proc_by_id` itself, so its shape has to match that function +// exactly (see the note on `CallProcById_Ptr` above). Passes through to our rust hook. // Used on Windows too -extern "C" Value LINUX_REGPARM3 call_proc_by_id_hook_trampoline( +extern "C" Value call_proc_by_id_hook_trampoline( Value usr, uint32_t proc_type, uint32_t proc_id, @@ -64,6 +74,13 @@ extern "C" Value LINUX_REGPARM3 call_proc_by_id_hook_trampoline( ) { Value ret; + // A shim running with `RuntimeContext(true)` wants the errors its own BYOND + // function raises, not the ones raised inside a DM proc that function calls. + // Those have to take BYOND's normal path so they get reported and reach the + // callee's own `try`. Every DM proc call passes through here, so this is the + // one place that can hand the called proc a clean context. + RuntimeContext runtime_scope(false); + if (call_proc_by_id_hook(&ret, usr, proc_type, proc_id, unk_0, src, args, args_count, unk_1, unk_2)) { clean(ret); return ret; diff --git a/auxtools/src/hooks.rs b/auxtools/src/hooks.rs index b27e8279..3ca0b725 100644 --- a/auxtools/src/hooks.rs +++ b/auxtools/src/hooks.rs @@ -1,6 +1,8 @@ use std::{ cell::RefCell, + collections::HashMap, ffi::{c_void, CStr}, + hash::BuildHasherDefault, os::raw::c_char }; @@ -114,24 +116,32 @@ pub fn shutdown() { pub type ProcHook = fn(&Value, &Value, Vec) -> DMResult; -thread_local! { - static PROC_HOOKS: RefCell> = RefCell::new(FxHashMap::default()); -} +/// Lets a `RefCell` live in a plain `static`. +/// +/// Everything here is only touched from BYOND's game thread: its proc calls and +/// init/shutdown, which it also drives. The table used to be a `thread_local!`, +/// but in a dlopen'd .so every access goes through `__tls_get_addr`, and it is +/// read on every single DM proc call. +struct GameThreadOnly(T); + +// SAFETY: only the game thread ever reaches these statics, see above +unsafe impl Sync for GameThreadOnly {} + +static PROC_HOOKS: GameThreadOnly>> = + GameThreadOnly(RefCell::new(HashMap::with_hasher(BuildHasherDefault::new()))); fn hook_by_id(id: raw_types::procs::ProcId, hook: ProcHook, hook_path: String) -> Result<(), HookFailure> { - PROC_HOOKS.with(|h| { - let mut map = h.borrow_mut(); - if let std::collections::hash_map::Entry::Vacant(e) = map.entry(id) { - e.insert((hook, hook_path)); - Ok(()) - } else { - Err(HookFailure::AlreadyHooked) - } - }) + let mut map = PROC_HOOKS.0.borrow_mut(); + if let std::collections::hash_map::Entry::Vacant(e) = map.entry(id) { + e.insert((hook, hook_path)); + Ok(()) + } else { + Err(HookFailure::AlreadyHooked) + } } pub fn clear_hooks() { - PROC_HOOKS.with(|h| h.borrow_mut().clear()); + PROC_HOOKS.0.borrow_mut().clear(); } pub fn hook>(name: S, hook: ProcHook) -> Result<(), HookFailure> { @@ -169,46 +179,42 @@ extern "C" fn call_proc_by_id_hook( _unknown2: u32, _unknown3: u32 ) -> u8 { - match PROC_HOOKS.with(|h| match h.borrow().get(&proc_id) { - Some((hook, path)) => { - let (src, usr, args) = unsafe { - ( - Value::from_raw(src_raw), - Value::from_raw(usr_raw), - // Taking ownership of args here - std::slice::from_raw_parts(args_ptr, num_args) - .iter() - .map(|v| Value::from_raw_owned(*v)) - .collect() - ) - }; - - let result = hook(&src, &usr, args); - - match result { - Ok(r) => { - let result_raw = r.raw; - // Stealing our reference out of the Value - std::mem::forget(r); - Some(result_raw) - } - Err(e) => { - Proc::find("/proc/auxtools_stack_trace") - .unwrap() - .call(&[&Value::from_string(format!("{} HookPath: {}", e.message.as_str(), path.as_str())).unwrap()]) - .unwrap(); - Some(Value::NULL.raw) - } - } + // copy the fn out so no borrow is held while the hook runs DM code, which + // can call back into here or register hooks + let Some(hook) = PROC_HOOKS.0.borrow().get(&proc_id).map(|(hook, _)| *hook) else { + return 0; + }; + + let (src, usr, args) = unsafe { + ( + Value::from_raw(src_raw), + Value::from_raw(usr_raw), + // Taking ownership of args here + std::slice::from_raw_parts(args_ptr, num_args) + .iter() + .map(|v| Value::from_raw_owned(*v)) + .collect() + ) + }; + + let result = match hook(&src, &usr, args) { + Ok(r) => { + let result_raw = r.raw; + // Stealing our reference out of the Value + std::mem::forget(r); + result_raw } - None => None - }) { - Some(result) => { - unsafe { - *ret = result; - } - 1 + Err(e) => { + let path = PROC_HOOKS.0.borrow()[&proc_id].1.clone(); + Proc::find("/proc/auxtools_stack_trace") + .unwrap() + .call(&[&Value::from_string(format!("{} HookPath: {}", e.message.as_str(), path)).unwrap()]) + .unwrap(); + Value::NULL.raw } - None => 0 + }; + unsafe { + *ret = result; } + 1 } diff --git a/auxtools/src/lib.rs b/auxtools/src/lib.rs index a12065a5..2015100f 100644 --- a/auxtools/src/lib.rs +++ b/auxtools/src/lib.rs @@ -12,20 +12,19 @@ mod list; mod proc; pub mod raw_types; mod runtime; -pub mod sigscan; mod string; mod string_intern; +mod symbols; mod value; mod value_from; pub mod version; mod weak_value; -use std::{ - ffi::c_void, - sync::atomic::{AtomicBool, Ordering} -}; +use std::sync::atomic::{AtomicBool, Ordering}; pub use auxtools_impl::{full_shutdown, hook, init, pin_dll, runtime_handler, shutdown}; +/// For crates that need to find something else in BYOND's binary. +pub use byond_scan; /// Used by the [pin_dll] macro to set dll pinning pub use ctor; pub use hooks::{CompileTimeHook, RuntimeErrorHook}; @@ -39,6 +38,7 @@ pub use raw_types::variables::VariableNameIdTable; pub use runtime::{DMResult, Runtime}; pub use string::StringRef; pub use string_intern::InternedString; +pub use symbols::find_recipe; pub use value::Value; pub use weak_value::WeakValue; @@ -47,161 +47,6 @@ pub use weak_value::WeakValue; #[cfg(windows)] extern crate winapi; -#[cfg(windows)] -pub const BYONDCORE: &str = "byondcore.dll"; -#[cfg(windows)] -signatures! { - get_proc_array_entry => version_dependent_signature!( - 1630.. => (call, "E8 ?? ?? ?? ?? 8B 4D 0C 8B D0 83 C4 04 89 55 DC 8D 46 10 F6 C1 01"), - ..1630 => (call, "E8 ?? ?? ?? ?? 8B C8 8D 45 ?? 6A 01 50 FF 76 ?? 8A 46 ?? FF 76 ?? FE C0") - ), - get_string_id => universal_signature!("55 8B EC 8B 45 ?? 83 EC ?? 53 56 8B 35 ?? ?? ?? ?? 57 85 C0 75 ?? 68 ?? ?? ?? ??"), - call_proc_by_id => version_dependent_signature!( - 1648.. => "55 8B EC 81 EC 9C 00 00 00 A1 ?? ?? ?? ?? 33 C5 89 45 ?? 8B 55 ?? 8B 45 ??", - 1602..1648 => "55 8B EC 81 EC 98 00 00 00 A1 ?? ?? ?? ?? 33 C5 89 45 FC 8B 55", - ..1602 => (call, "E8 ?? ?? ?? ?? 83 C4 2C 89 45 F4 89 55 F8 8B 45 F4 8B 55 F8 5F 5E 5B 8B E5 5D C3 CC 55 8B EC 83 EC 0C 53 8B 5D 10 8D 45 FF") - ), - get_variable => version_dependent_signature!( - 1648.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 0C 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 8B 75 ??", - 1615..1648 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 28 A1 ?? ?? ?? ?? 33 C5 89 45 F0 53 56 57 50 8D 45 F4 64 A3 00 00 00 00 8B 5D", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 0C 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 4D", - ..1602 => "55 8B EC 8B 4D ?? 0F B6 C1 48 83 F8 ?? 0F 87 ?? ?? ?? ?? 0F B6 80 ?? ?? ?? ?? FF 24 85 ?? ?? ?? ?? FF 75 ?? FF 75 ?? E8" - ), - set_variable => version_dependent_signature!( - 1648.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 24 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 4D ??", - 1615..1648 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 0C 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 4D", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 08 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 4D", - ..1602 => "55 8B EC 8B 4D 08 0F B6 C1 48 57 8B 7D 10 83 F8 53 0F ?? ?? ?? ?? ?? 0F B6 80 ?? ?? ?? ?? FF 24 85 ?? ?? ?? ?? FF 75 18 FF 75 14 57 FF 75 0C E8 ?? ?? ?? ?? 83 C4 10 5F 5D C3"), - get_string_table_entry => universal_signature!("55 8B EC 8B 4D 08 3B 0D ?? ?? ?? ?? 73 10 A1"), - call_datum_proc_by_name => version_dependent_signature!( - 1615.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 14 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 75 1C 8D 45 F3 8B 7D 18 8B 5D 10 6A 00", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 18 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 75 14 8D 45 F3 8B 5D 10 6A 01", - ..1602 => "55 8B EC 83 EC 0C 53 8B 5D 10 8D 45 FF 56 8B 75 14 57 6A 01 50 FF 75 1C C6 45 FF 00 FF 75 18 6A 00 56" - ), - /* ..1614 "E8 ?? ?? ?? ?? 83 C4 0C 81 FF FF FF 00 00 74 ?? 85 FF 74 ?? 57 FF 75 ??" - 1615.. "E8 ?? ?? ?? ?? 83 C4 0C 81 FF FF FF 00 00 74 13 85 FF 74 0F 8B 4D 14 57 53 56" */ - dec_ref_count => universal_signature!(call, "E8 ?? ?? ?? ?? 83 C4 0C 81 FF FF FF 00 00 74 ?? 85 FF 74"), - /* ..1614 "E8 ?? ?? ?? ?? FF 77 ?? FF 77 ?? E8 ?? ?? ?? ?? 8D 77 ?? 56 E8 ?? ?? ?? ??" - 1615.. "E8 ?? ?? ?? ?? FF 73 14 FF 73 10 E8 ?? ?? ?? ?? 8D 73 30 56 E8" */ - inc_ref_count => universal_signature!(call, "E8 ?? ?? ?? ?? FF ?? ?? FF ?? ?? E8 ?? ?? ?? ?? 8D ?? ?? 56 E8"), - get_assoc_element => version_dependent_signature!( - 1614.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 51 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 5D 08 80", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 10 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 4D", - ..1602 => "55 8B EC 51 8B 4D 08 C6 45 FF 00 80 F9 05 76 11 80 F9 21 74 10 80 F9 0D 74 0B 80 F9 0E 75 65 EB 04 84 C9 74 5F 6A 00 8D 45 FF 50 FF 75 0C 51 6A 00 6A 7B" - ), - set_assoc_element => version_dependent_signature!( - 1648.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 1C 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 80 FB 0F", - 1615..1648 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 14 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 5D 08 80", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 14 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 4D", - ..1602 => "55 8B EC 83 EC 14 8B 4D 08 C6 45 FF 00 80 F9 05 76 15 80 F9 21 74 14 80 F9 0D 74 0F 80 F9 0E 0F 85 ?? ?? ?? ?? EB 04 84 C9 74 7A 6A 00" - ), - create_list => version_dependent_signature!( - 1615.. => "55 8B EC 8B 0D ?? ?? ?? ?? 56 85 C9 74 1B A1 ?? ?? ?? ?? 49 89 0D ?? ?? ?? ?? 8B 34 88 81 FE FF FF 00 00 0F 85 EC 00 00 00 8B 35 ?? ?? ?? ?? 81 FE FF FF FF 00 75 1B 56 68 00 01 00 00 68 ?? ?? ?? ?? E8 ?? ?? ?? ?? 83 C4 0C B8 FF FF 00 00 5E 5D C3", - ..1614 => "55 8B EC 8B ?? ?? ?? ?? ?? 56 85 C9 74 1B A1 ?? ?? ?? ?? 49 89 ?? ?? ?? ?? ?? 8B 34 88 81 FE ?? ?? ?? ?? 0F 85 ?? ?? ?? ?? 8B ?? ?? ?? ?? ?? 8B F1 81 F9 ?? ?? ?? ?? 75 1B 51 68 ?? ?? ?? ?? 68 ?? ?? ?? ?? E8 ?? ?? ?? ?? 83 C4 0C B8 ?? ?? ?? ?? 5E 5D C3" - ), - append_to_list => version_dependent_signature!( - 1648.. => "55 8B EC 8B 4D ?? 0F B6 C1 48 56 57 83 F8 54 0F 87 ?? ?? ?? ?? 0F B6 80 ?? ?? ?? ??", - 1615..1648 => "55 8B EC 8B 4D 08 0F B6 C1 48 56 57 83 F8 53 0F 87 AA 00 00 00 0F B6 80 ?? ?? ?? ?? FF 24 85 ?? ?? ?? ?? FF 75 0C E8 ?? ?? ?? ?? 8B F0 83 C4 04 85 F6 0F 84 94 00 00 00 8B 46 0C 40 50 56 E8 ?? ?? ?? ?? 8B 7E 0C 83 C4 08 85", - ..1614 => "55 8B EC 8B 4D 08 0F B6 C1 48 56 83 F8 53 0F 87 ?? ?? ?? ?? 0F B6 ?? ?? ?? ?? ?? FF 24 ?? ?? ?? ?? ?? FF 75 0C E8 ?? ?? ?? ?? 8B F0 83 C4 04 85 F6 0F 84 ?? ?? ?? ?? 8B 46 0C 40 50 56 E8 ?? ?? ?? ?? 8B 56 0C 83 C4 08 85 D2" - ), - remove_from_list => version_dependent_signature!( - 1648.. => "55 8B EC 83 EC 08 53 8B 5D ?? 0F B6 C3 48 56 57 83 F8 54 0F 87 ?? ?? ?? ??", - 1615..1648 => "55 8B EC 83 EC 08 53 8B 5D 08 0F B6 C3 48 56 57 83 F8 53 0F 87 23 01 00 00 0F B6 80 ?? ?? ?? ?? 8B 4D 10 FF 24 85 ?? ?? ?? ?? 8B 7D 0C 6A 0F 57 53 E8 ?? ?? ?? ?? 50 E8 ?? ?? ?? ?? 83 C4 10 85 C0 0F 84 02 01 00 00 8B 08 8B 40 0C 85 C0 0F 84 F5 00 00 00 8B 75 14 8B 5D 10", - ..1614 => "55 8B EC 8B 4D 08 83 EC 0C 0F B6 C1 48 53 83 F8 53 0F 87 ?? ?? ?? ?? 0F B6 ?? ?? ?? ?? ?? 8B 55 10 FF 24 ?? ?? ?? ?? ?? 6A 0F FF 75 0C 51 E8 ?? ?? ?? ?? 50 E8 ?? ?? ?? ?? 83 C4 10 85 C0 0F 84 ?? ?? ?? ?? 8B 48 0C 8B 10 85 C9 0F 84 ?? ?? ?? ?? 8B 45 14 8B 5D 10" - ), - get_length => universal_signature!("55 8B EC 8B 4D ?? 83 EC ?? 0F B6 C1 48 53 56 57 83 F8 ?? 0F 87 ?? ?? ?? ??" - ), - get_misc_by_id => version_dependent_signature!( - 1615.. => (call, "E8 ?? ?? ?? ?? 83 C4 04 85 C0 74 08 0F B7 38 8B 70 08 EB 04 33 FF 33 F6 0F B7 C7 50 89 45 F8 E8 ?? ?? ?? ??"), - ..1614 => (call, "E8 ?? ?? ?? ?? 83 C4 04 85 C0 75 ?? FF 75 ?? E8 ?? ?? ?? ?? FF 30 68 ?? ?? ?? ?? E8 ?? ?? ?? ?? A1 ?? ?? ?? ??") - ), - runtime => universal_signature!(call, "E8 ?? ?? ?? ?? 83 C4 04 8B 85 ?? ?? ?? ?? 0F B6 C0 51 66 0F 6E C0 0F 5B C0"), - suspended_procs => version_dependent_signature!( - 1615.. => (2, "8B 1D ?? ?? ?? ?? 56 8B 75 ?? 57 8B 3D ?? ?? ?? ?? 89 7D ?? 8B 86"), - ..1614 => (1, "A1 ?? ?? ?? ?? 8B D8 89 45 ?? 89 75 ?? 3B DA 73 ?? 8D 0C ?? D1 E9 8B 04 ??") - ), - suspended_procs_buffer => version_dependent_signature!( - 1615.. => (2, "8B 3D ?? ?? ?? ?? 89 7D ?? 8B 86 ?? ?? ?? ?? 89 45 ?? A1 ?? ?? ?? ?? 8B"), - ..1614 => (2, "8B 35 ?? ?? ?? ?? 8B 80 ?? ?? ?? ?? 57 8B 3D ?? ?? ?? ?? 8B D7 89 45 ??") - ), - to_string => version_dependent_signature!( - 1686.. => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 38 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 0F B6 C3 48 83 F8 58 0F 87", - 1648..1686 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 38 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 75 ?? 8B 5D ??", - 1615..1648 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 24 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 75 ?? 8B 5D ?? BF 00 90 00 00 0F B6 C3 48 83 F8 53 0F 87", - 1602..1614 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC 3C 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 75", - 1585..1602 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC ?? 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 1D ?? ?? ?? ??", - 1561..1585 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 18 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 4D ?? 0F B6 C1", - 1543..1561 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 14 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 4D ??", - ..1543 => "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 10 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 0F B6 C3"), - - current_execution_context => version_dependent_signature!( - 1615.. => (1, "A1 ?? ?? ?? ?? 56 53 6A 00 8B 00 57 6A ?? 89 4D ?? FF 70 ?? 8B 4D ?? FF"), - ..1614 => (1, "A1 ?? ?? ?? ?? FF 75 ?? 89 4D ?? 8B 4D ?? 8B 00 6A 00 52 6A 12 FF 70 ??") - ), - variable_names => version_dependent_signature!( - 1615.. => (2, "8B 0D ?? ?? ?? ?? 0F B7 C2 89 45 08 8B 04 87 39 34 81 74 35 0F B7 F3 66 3B DA 76 25 0F 1F 84 00 00 00 00 00"), - ..1614 => (2, "8B 1D ?? ?? ?? ?? 2B 0C ?? 8B 5D ?? 74 ?? 85 C9 79 ?? 0F B7 D0 EB ?? 83 C0 02") - ) -} - -#[cfg(unix)] -pub const BYONDCORE: &str = "libbyond.so"; -#[cfg(unix)] -signatures! { - get_proc_array_entry => version_dependent_signature!( - 1584.. => (call, "E8 ?? ?? ?? ?? 0F B7 F6 89 C7 89 B5 ?? ?? ?? ?? 89 34 24 E8 ?? ?? ?? ??"), - ..1584 => (call, "E8 ?? ?? ?? ?? 8B 00 89 04 24 E8 ?? ?? ?? ?? 8B 00 89 44 24 ?? 8D 45 ??") - ), - get_string_id => universal_signature!("55 89 E5 57 56 89 CE 53 89 D3 83 EC 5C 8B 55 ?? 85 C0 88 55 ?? 0F 84 ?? ?? ?? ??"), - call_proc_by_id => universal_signature!(call, "E8 ?? ?? ?? ?? 8B 45 ?? 8B 55 ?? 89 45 ?? 89 55 ?? 8B 55 ?? 8B 4D ?? 8B 5D ??"), - get_variable => universal_signature!("55 89 E5 81 EC C8 00 00 00 8B 55 ?? 89 5D ?? 8B 5D ?? 89 75 ?? 8B 75 ??"), - set_variable => version_dependent_signature!( - 1560.. => (call, "E8 ?? ?? ?? ?? 8B 45 ?? 8D 65 ?? 5B 5E 5F 5D C3 8D B4 26 00 00 00 00 8B 40 ??"), - 1543..1560 => "55 89 E5 81 EC A8 00 00 00 8B 55 ?? 89 5D ?? 8B 4D ?? 89 7D ?? 8B 5D ??", - ..1543 => "55 89 E5 81 EC A8 00 00 00 8B 55 ?? 8B 45 ?? 89 5D ?? 8B 5D ?? 89 7D ??" - ), - - get_string_table_entry => universal_signature!("55 89 E5 83 EC 18 8B 45 ?? 39 05 ?? ?? ?? ?? 76 ?? 8B 15 ?? ?? ?? ?? 8B 04 ??"), - call_datum_proc_by_name => version_dependent_signature!( - 1606.. => "55 89 E5 57 56 89 CE 53 89 D3 83 EC ?? 0F B6 55 ?? 89 45 ?? 8B 45 ?? 8B 7D ?? C6 45 E7 ?? 0F B6 CA 89 45 B0 8D 45 ?? 89 44 24 ?? 8B 45 ?? 89 ?? BC 31 C9 88 ?? BB 8B 55 ?? C7 44 24 ?? 01 00 00 00", - ..1606 => "55 89 E5 57 56 53 83 EC 5C 8B 55 ?? 0F B6 45 ?? 8B 4D ?? 8B 5D ?? 89 14 24 8B 55 ?? 88 45 ?? 0F B6 F8 8B 75 ?? 8D 45 ?? 89 44 24 ?? 89 F8 89 4C 24 ?? 31 C9 C6 45 ?? 00 C7 44 24 ?? 01 00 00 00" - ), - - dec_ref_count => version_dependent_signature!( - 1543.. => (call, "E8 ?? ?? ?? ?? C7 06 00 00 00 00 C7 46 ?? 00 00 00 00 A1 ?? ?? ?? ?? 0F B7 50 ??"), - ..1543 => (call, "E8 ?? ?? ?? ?? 8B 4D ?? C7 44 24 ?? 00 00 00 00 C7 44 24 ?? 00 00 00 00 89 0C 24") - ), - inc_ref_count => universal_signature!(call, "E8 ?? ?? ?? ?? 8B 43 ?? 80 48 ?? 04 8B 5D ?? 8B 75 ?? 8B 7D ?? 89 EC 5D"), - get_assoc_element => version_dependent_signature!( - 1602.. => "55 89 E5 83 EC ?? ?? ?? ?? ?? 5D F4 89 D3 89 75 F8 89 D6 89 7D FC 89 CF 89 45 B4 0F 84 B7 00 00 ??", - ..1602 => "55 89 E5 83 EC 68 89 4D ?? B9 7B 00 00 00 89 5D ?? 89 D3 89 75 ?? 89 C6" - ), - - set_assoc_element => version_dependent_signature!( - 1602.. => "55 89 E5 83 EC 68 89 75 F8 8B 75 08 89 5D F4 89 C3 8B 45 0C 89 7D FC 80 FB 3C 89 D7 88 5D BF 89 ??", - ..1602 => "55 B9 7C 00 00 00 89 E5 83 EC 58 89 7D ?? 8B 7D ?? 89 5D ?? 89 C3 8B 45 ??" - ), - - create_list => universal_signature!("55 89 E5 57 56 53 83 EC 2C A1 ?? ?? ?? ?? 8B 75 ?? 85 C0 0F 84 ?? ?? ?? ??"), - append_to_list => universal_signature!("55 89 E5 83 EC 38 3C 54 89 5D ?? 8B 5D ?? 89 75 ?? 8B 75 ?? 89 7D ?? 76 ??"), - remove_from_list => universal_signature!("55 89 E5 83 EC 48 3C 54 89 5D ?? 89 C3 89 75 ?? 8B 75 ?? 89 7D ?? 8B 7D ??"), - get_length => universal_signature!("55 89 E5 57 56 53 83 EC 6C 8B 45 ?? 8B 5D ?? 3C 54 76 ?? 31 F6 8D 65 ??"), - get_misc_by_id => universal_signature!(call, "E8 ?? ?? ?? ?? 0F B7 55 ?? 03 1F 0F B7 4B ?? 89 8D ?? ?? ?? ?? 0F B7 5B ??"), - runtime => universal_signature!(call, "E8 ?? ?? ?? ?? 31 C0 8D B4 26 00 00 00 00 8B 5D ?? 8B 75 ?? 8B 7D ?? 89 EC"), - suspended_procs => universal_signature!(1, "A3 ?? ?? ?? ?? 8D 14 ?? 73 ?? 8D 74 26 00 83 C0 01 8B 14 ?? 39 C3 89 54 ?? ??"), - suspended_procs_buffer => universal_signature!(2, "89 35 ?? ?? ?? ?? C7 04 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? 8B 45 ?? 83 C0 08"), - to_string => version_dependent_signature!( - 1602.. => "55 89 E5 83 ?? ?? 89 5D F4 8D ?? ?? 89 75 F8 89 7D FC 80 ?? ?? ?? ?? ?? B8", - 1560..1602 => (call, "E8 ?? ?? ?? ?? 89 04 24 E8 ?? ?? ?? ?? 8B 00 8D 4D ?? 89 0C 24"), - 1543..1560 => "55 89 E5 83 EC 68 A1 ?? ?? ?? ?? 8B 15 ?? ?? ?? ?? 8B 0D ?? ?? ?? ?? 89 5D ??", - ..1543 => "55 89 E5 83 EC 58 89 5D ?? 8B 5D ?? 89 75 ?? 8B 75 ?? 89 7D ?? 80 FB 54" - ), - current_execution_context => universal_signature!(1, "A1 ?? ?? ?? ?? C7 44 24 ?? 00 00 00 00 C7 44 24 ?? 00 00 00 00 89 74 24"), - variable_names => version_dependent_signature!( - 1543.. => (1, "A1 ?? ?? ?? ?? 8B 13 8B 39 8B 75 ?? 8B 14 ?? 89 7D ?? 8B 3C ?? 83 EE 02"), - ..1543 => (2, "8B 35 ?? ?? ?? ?? 89 5D ?? 0F B7 08 89 75 ?? 66 C7 45 ?? 00 00 89 7D ??") - ) -} pub static PIN_DLL: AtomicBool = AtomicBool::new(true); // This strange section of code retrieves our DLL using the init function's @@ -235,11 +80,6 @@ byond_ffi_fn! { auxtools_init(_input) { return Some("SUCCESS".to_owned()) } - let byondcore = match sigscan::Scanner::for_module(BYONDCORE) { - Some(v) => v, - None => return Some("FAILED (Couldn't create scanner for byondcore.dll)".to_owned()) - }; - let mut did_full = false; let mut did_partial = false; @@ -249,52 +89,8 @@ byond_ffi_fn! { auxtools_init(_input) { return Some(format!("FAILED ({})", e)); } - find_signatures! { byondcore, - (current_execution_context as *mut *mut raw_types::procs::ExecutionContext), - (suspended_procs as *mut raw_types::procs::SuspendedProcs), - (suspended_procs_buffer as *mut raw_types::procs::SuspendedProcsBuffer), - call_proc_by_id, - call_datum_proc_by_name, - get_proc_array_entry, - get_string_id, - get_variable, - set_variable, - get_string_table_entry, - inc_ref_count, - dec_ref_count, - get_assoc_element, - set_assoc_element, - create_list, - append_to_list, - remove_from_list, - get_length, - get_misc_by_id, - to_string, - runtime - } - - unsafe { - raw_types::funcs::CURRENT_EXECUTION_CONTEXT = current_execution_context; - raw_types::funcs::SUSPENDED_PROCS = suspended_procs; - raw_types::funcs::SUSPENDED_PROCS_BUFFER = suspended_procs_buffer; - raw_types::funcs::call_proc_by_id_byond = call_proc_by_id; - raw_types::funcs::call_datum_proc_by_name_byond = call_datum_proc_by_name; - raw_types::funcs::get_proc_array_entry_byond = get_proc_array_entry; - raw_types::funcs::get_string_id_byond = get_string_id; - raw_types::funcs::get_variable_byond = get_variable; - raw_types::funcs::set_variable_byond = set_variable; - raw_types::funcs::get_string_table_entry_byond = get_string_table_entry; - raw_types::funcs::inc_ref_count_byond = inc_ref_count; - raw_types::funcs::dec_ref_count_byond = dec_ref_count; - raw_types::funcs::get_assoc_element_byond = get_assoc_element; - raw_types::funcs::set_assoc_element_byond = set_assoc_element; - raw_types::funcs::create_list_byond = create_list; - raw_types::funcs::append_to_list_byond = append_to_list; - raw_types::funcs::remove_from_list_byond = remove_from_list; - raw_types::funcs::get_length_byond = get_length; - raw_types::funcs::get_misc_by_id_byond = get_misc_by_id; - raw_types::funcs::to_string_byond = to_string; - raw_types::funcs::runtime_byond = runtime; + if let Err(e) = symbols::resolve_full() { + return Some(format!("FAILED ({})", e)); } if pin_dll().is_err() { @@ -313,10 +109,8 @@ byond_ffi_fn! { auxtools_init(_input) { did_partial = true; // This is a heap ptr so fetch it on partial loads - find_signature! { byondcore, variable_names as *mut VariableNameIdTable } - - unsafe { - raw_types::funcs::VARIABLE_NAMES = variable_names; + if let Err(e) = symbols::resolve_partial() { + return Some(format!("FAILED ({})", e)); } proc::populate_procs(); @@ -415,22 +209,12 @@ byond_ffi_fn! { auxtools_full_shutdown(_input) { } } byond_ffi_fn! { auxtools_check_signatures(_input) { - let byondcore = match sigscan::Scanner::for_module(BYONDCORE) { - Some(v) => v, - None => return Some("FAILED (Couldn't create scanner for byondcore.dll)".to_owned()) - }; if let Err(e) = version::init() { return Some(format!("FAILED ({})", e)); } - let mut missing = Vec::<&'static str>::new(); - for (name, found) in SIGNATURES0.check_all(&byondcore) { - if !found { - missing.push(name); - } - } - if missing.is_empty() { - Some("SUCCESS".to_owned()) - } else { - Some(format!("MISSING: {}", missing.join(", "))) + match symbols::unresolved() { + Err(e) => Some(format!("FAILED ({})", e)), + Ok(missing) if missing.is_empty() => Some("SUCCESS".to_owned()), + Ok(missing) => Some(format!("MISSING: {}", missing.join(", "))) } } } diff --git a/auxtools/src/raw_types/funcs.cpp b/auxtools/src/raw_types/funcs.cpp index 1b225092..f2200090 100644 --- a/auxtools/src/raw_types/funcs.cpp +++ b/auxtools/src/raw_types/funcs.cpp @@ -52,9 +52,26 @@ struct RestoreJmpBuf #endif +// The REGPARM markers are real on Linux and expand to nothing on Windows, so a +// wrong one here is a Linux-only bug that no Windows test can catch. These were +// read off libbyond.so's function prologues on 516.1669 and 516.1687. +// +// How to check one: does the function read `eax` or `edx` before writing it in +// its first handful of instructions? If so it takes arguments in registers and +// needs a marker. If it opens by reading `[esp+arg_N]` it is plain cdecl and must +// not have one. The export table can't tell you, none of these are in it. +// +// Two things make this easy to get wrong: +// +// * A Value-returning regparm function spends `eax` on the hidden return +// buffer, so its first real argument starts at `edx`. Count the buffer. +// * The convention is per-function and per-build. `remove_from_list` changed +// from registers to the stack somewhere in (1669, 1685] with no change to +// its signature, while its identically-shaped sibling `append_to_list` did +// not move. Don't infer one row from another. extern "C" { - DEFINE_byond_REGPARM3(call_proc_by_id, Value, (Value, uint32_t, uint32_t, uint32_t, Value, const Value *, uint32_t, uint32_t, uint32_t)); + DEFINE_byond(call_proc_by_id, Value, (Value, uint32_t, uint32_t, uint32_t, Value, const Value *, uint32_t, uint32_t, uint32_t)); DEFINE_byond(call_datum_proc_by_name, Value, (Value, uint32_t, uint32_t, Value, const Value *, uint32_t, uint32_t, uint32_t)); DEFINE_byond(get_proc_array_entry, void *, (uint32_t)); DEFINE_byond_REGPARM3(get_string_id, uint32_t, (const char *, uint8_t, uint8_t, uint8_t)); @@ -64,16 +81,53 @@ extern "C" DEFINE_byond(inc_ref_count, void, (Value)); DEFINE_byond(dec_ref_count, void, (Value)); DEFINE_byond_REGPARM3(get_list_by_id, void *, (uint32_t)); - DEFINE_byond_REGPARM3(get_assoc_element, Value, (Value, Value)); - DEFINE_byond_REGPARM3(set_assoc_element, void, (Value, Value, Value)); + DEFINE_byond(get_assoc_element, Value, (Value, Value)); + DEFINE_byond(set_assoc_element, void, (Value, Value, Value)); DEFINE_byond(create_list, uint32_t, (uint32_t)); DEFINE_byond_REGPARM2(append_to_list, void, (Value, Value)); - DEFINE_byond_REGPARM2(remove_from_list, void, (Value, Value)); + // Correct from 516.1685 on, and wrong below it. This is the one function known + // to have changed convention mid-range, so one declaration can't cover both + // eras. Running this on an older Linux build means putting REGPARM2 back, and + // re-reading the prologue first, because the exact build it flipped at was + // never bisected. + DEFINE_byond(remove_from_list, bool, (Value, Value)); + // The one function here whose *return* differs by platform, not just its + // argument placement. Windows hands the count straight back in `eax`. Linux + // returns a DM Value through a caller-supplied buffer, so the count has to be + // unpacked out of it. See the `get_length` wrapper below. +#ifdef _WIN32 DEFINE_byond(get_length, uint32_t, (Value)); +#else + DEFINE_byond(get_length, Value, (Value)); +#endif DEFINE_byond(get_misc_by_id, void *, (uint32_t)); DEFINE_byond(to_string, uint32_t, (Value)); } +#ifndef _WIN32 +// Unpack the DM Value Linux's `get_length` returns into the plain count every +// caller here wants. Tag 0x2A is Number and its data half is an `f32`. Anything +// else means the argument had no length, which BYOND itself reports as 0. +// +// The tag is masked to a byte on purpose: BYOND leaves the three padding bytes +// above a tag indeterminate, so comparing the whole dword answers wrong at +// random. +static uint32_t length_value_to_count(Value length) +{ + if ((length.type & 0xFF) != 0x2A) + { + return 0; + } + float count; + __builtin_memcpy(&count, &length.value, sizeof(count)); + if (!(count > 0.0f)) + { + return 0; + } + return static_cast(count); +} +#endif + extern "C" uint8_t call_proc_by_id( Value *out, Value usr, @@ -110,7 +164,13 @@ extern "C" uint8_t call_datum_proc_by_name( uint32_t unk_0, uint32_t unk_1) { - RuntimeContext ctx(false); + // Intercepts, unlike `call_proc_by_id` above. The errors this function raises + // itself (an unknown proc name, a null receiver) happen before any DM code + // runs, and with `false` BYOND's own exception flew straight past the catch and + // killed the process at the first Rust frame. The proc it ends up calling still + // reports its own errors normally: `call_proc_by_id_hook_trampoline` pushes + // `false` around every DM call. + RuntimeContext ctx(true); BYOND_TRY { @@ -345,7 +405,11 @@ extern "C" uint8_t get_length(uint32_t *out, Value value) BYOND_TRY { clean(value); +#ifdef _WIN32 *out = get_length_byond(value); +#else + *out = length_value_to_count(get_length_byond(value)); +#endif return 1; } BYOND_CATCH diff --git a/auxtools/src/raw_types/lists.rs b/auxtools/src/raw_types/lists.rs index 38b0c875..e11fa17b 100644 --- a/auxtools/src/raw_types/lists.rs +++ b/auxtools/src/raw_types/lists.rs @@ -4,20 +4,23 @@ use super::values; #[derive(Copy, Clone, Debug)] pub struct ListId(pub u32); -#[repr(C)] -#[allow(unused)] -enum Color { - Red = 0, - Black = 1 -} - +/// One key->value pair of an associative list, as a red-black tree node. +/// +/// 32 bytes. Matches BYOND's own tree lookup (516.1669), which reads the key at +/// `+0` and the children at `+16` and `+20`. This used to declare `color` at +/// `+16`, which put both child pointers 4 bytes past where BYOND writes them. +/// Nothing read the children, so it never showed. #[repr(C)] pub struct AssociativeListEntry { - key: values::Value, - value: values::Value, - color: Color, - left: *mut AssociativeListEntry, - right: *mut AssociativeListEntry + /// Non-owning; the same ref also occupies a `vector_part` slot. + pub key: values::Value, + /// Owning; released when the tree is torn down. + pub value: values::Value, + pub left: *mut AssociativeListEntry, + pub right: *mut AssociativeListEntry, + pub parent: *mut AssociativeListEntry, + /// 0 = red, 1 = black. Padded to 4. + pub color: u32 } #[repr(C)] diff --git a/auxtools/src/raw_types/procs.rs b/auxtools/src/raw_types/procs.rs index 6e915518..ed3fba67 100644 --- a/auxtools/src/raw_types/procs.rs +++ b/auxtools/src/raw_types/procs.rs @@ -76,8 +76,11 @@ struct ProcInstanceInner { pub time_to_resume: u32 } +// Linux is 148 bytes against Windows' 152: from `cache` onward the fields sit 4 +// bytes earlier, which is the same shape Windows had before 1668. So Linux +// always takes the Pre1668 variant. #[versioned( - Pre1668 if crate::version::BYOND_VERSION_MAJOR <= 515 || (crate::version::BYOND_VERSION_MAJOR == 516 && crate::version::BYOND_VERSION_MINOR <= 1667), + Pre1668 if cfg!(unix) || crate::version::BYOND_VERSION_MAJOR <= 515 || (crate::version::BYOND_VERSION_MAJOR == 516 && crate::version::BYOND_VERSION_MINOR <= 1667), Post1668, )] #[repr(C)] @@ -122,6 +125,7 @@ pub struct SuspendedProcsBuffer { pub buffer: *mut *mut ProcInstance } +#[cfg(windows)] #[repr(C)] pub struct SuspendedProcs { pub front: usize, @@ -129,6 +133,15 @@ pub struct SuspendedProcs { pub capacity: usize } +// On Linux the compiler lays the two index globals out the other way round, +// `back` first. Whatever follows them isn't read. +#[cfg(unix)] +#[repr(C)] +pub struct SuspendedProcs { + pub back: usize, + pub front: usize +} + #[cfg(test)] mod layout_tests { use std::mem::{offset_of, size_of}; @@ -136,7 +149,8 @@ mod layout_tests { use super::{misc, strings, values}; // Reference structs: verbatim copies of the original hand-written variants, - // kept here so that any accidental layout change in the macro output is caught. + // kept here so that any accidental layout change in the macro output is + // caught. #[repr(C)] #[derive(Copy, Clone)] diff --git a/auxtools/src/raw_types/values.rs b/auxtools/src/raw_types/values.rs index 32837cc5..f66da363 100644 --- a/auxtools/src/raw_types/values.rs +++ b/auxtools/src/raw_types/values.rs @@ -43,18 +43,33 @@ value_tags! { // Lists List = 0x0F, ArgList = 0x10, + MobVerbs = 0x11, + ObjVerbs = 0x12, + TurfVerbs = 0x13, + AreaVerbs = 0x14, + ClientVerbs = 0x15, + SaveFileDir = 0x16, MobContents = 0x17, TurfContents = 0x18, AreaContents = 0x19, WorldContents = 0x1A, + MobGroup = 0x1B, ObjContents = 0x1C, DatumTypepath = 0x20, Datum = 0x21, SaveFile = 0x23, + SaveFileTypepath = 0x24, ProcRef = 0x26, File = 0x27, + // what `var/list/L = new()` pushes as its type. islist() says no to it + ListTypepath = 0x28, + // a typepath plus var overrides, applied after `new`. extools' name; the + // binary never names it + Prefab = 0x29, Number = 0x2A, + // what `F["key"]` evaluates to: a savefile id plus the key's string id + SaveFileEntry = 0x2B, MobVars = 0x2C, ObjVars = 0x2D, TurfVars = 0x2E, @@ -70,10 +85,34 @@ value_tags! { AreaOverlays = 0x38, AreaUnderlays = 0x39, Appearance = 0x3A, + ClientTypepath = 0x3B, Pointer = 0x3C, + // internal: the recorder `optimize_obj_type_init_procs` writes var inits + // into. DM code never sees it + InitVarRecorder = 0x3D, + // internal: a name constant that only DM-compiler code builds + CompilerNameConstant = 0x3E, + // also what an appearance's `type` comes back as + ImageTypepath = 0x3F, ImageOverlays = 0x40, ImageUnderlays = 0x41, ImageVars = 0x42, + ImageVerbs = 0x43, + ImageContents = 0x44, + // What `load_ext()` hands back: a reference to a resolved function in an + // external library. It is really any native-object handle: /icon and /regex + // objects are this tag too, told apart by a kind at `+8`. + ExtFunc = 0x45, + ClientImages = 0x46, + ClientScreen = 0x47, + // BYOND's own inc/dec_ref_count do nothing with this one + SaveFileVars = 0x48, + // the vars of a /list or /alist. The data is a string id, so it is + // refcounted like a string + ListVars = 0x49, + // internal, and no DM-visible name anywhere: an entry in a per-client + // resource set + Unnamed4A = 0x4A, TurfVisContents = 0x4B, ObjVisContents = 0x4C, MobVisContents = 0x4D, @@ -87,7 +126,9 @@ value_tags! { Alist = 0x55, PixLoc = 0x56, Vector = 0x57, - Callee = 0x58 + Callee = 0x58, + // the typepath of the 516 builtins: /alist, /pixloc, /vector, /callee + BuiltinTypepath = 0x59 } impl fmt::Display for Value { diff --git a/auxtools/src/sigscan.rs b/auxtools/src/sigscan.rs deleted file mode 100644 index c74182b1..00000000 --- a/auxtools/src/sigscan.rs +++ /dev/null @@ -1,211 +0,0 @@ -#[cfg(unix)] -mod linux; -#[cfg(windows)] -mod windows; - -use std::ops::{Bound, RangeBounds}; - -pub use auxtools_impl::convert_signature; -#[cfg(unix)] -pub use linux::Scanner; -pub use once_cell; -#[cfg(windows)] -pub use windows::Scanner; - -#[macro_export] -macro_rules! signature { - ($sig:tt) => { - $crate::sigscan::convert_signature!($sig) - }; -} - -#[macro_export] -macro_rules! count { - () => (0usize); - ( $x:tt $($xs:tt)* ) => (1_usize + count!($($xs)*)); -} - -#[macro_export] -macro_rules! signatures { - ( $( $name:ident => $sig:expr ),*) => { - struct Signatures { - $( pub $name: $crate::sigscan::SignatureMap, )* - } - - impl Signatures { - #[allow(dead_code)] - pub fn check_all(&self, scanner: &$crate::sigscan::Scanner) -> [(&'static str, bool); count!($($name)*)] { - let version = $crate::version::get().1; - [$( - (stringify!($name), self.$name.find(scanner, version).is_some()), - )*] - } - } - - static SIGNATURES0: $crate::sigscan::once_cell::sync::Lazy = $crate::sigscan::once_cell::sync::Lazy::new(|| Signatures { - $( $name: $sig, )* - }); - }; -} - -#[macro_export] -macro_rules! signature_struct { - (call, $sig:tt) => { - $crate::sigscan::Signature { - treatment: $crate::sigscan::SignatureTreatment::OffsetByCall, - bytes: signature!($sig) - } - }; - ($offset:literal, $sig:tt) => { - $crate::sigscan::Signature { - treatment: $crate::sigscan::SignatureTreatment::OffsetByInt($offset), - bytes: signature!($sig) - } - }; - (($spec:tt, $sig:tt)) => { - signature_struct!($spec, $sig) - }; - ($sig:tt) => { - $crate::sigscan::Signature { - treatment: $crate::sigscan::SignatureTreatment::NoOffset, - bytes: signature!($sig) - } - }; -} - -#[macro_export] -macro_rules! version_dependent_signature { - ( $($range:expr => $sig:tt),* ) => { - $crate::sigscan::SignatureMap::VersionDependent(vec![ - $(((std::ops::RangeBounds::start_bound(&$range), std::ops::RangeBounds::end_bound(&$range)), signature_struct!($sig)),)* - ]) - }; -} - -#[macro_export] -macro_rules! universal_signature { - (call, $sig:tt) => { - $crate::sigscan::SignatureMap::AllVersions(signature_struct!(call, $sig)) - }; - ($offset:literal, $sig:tt) => { - $crate::sigscan::SignatureMap::AllVersions(signature_struct!($offset, $sig)) - }; - ($sig:tt) => { - $crate::sigscan::SignatureMap::AllVersions(signature_struct!($sig)) - }; -} - -#[macro_export] -macro_rules! find_signature_inner { - ($scanner:ident, $name:ident, $type:ty) => { - let $name: $type; - if let Some(ptr) = SIGNATURES0.$name.find(&$scanner, $crate::version::get().1) { - $name = ptr as $type; - } else { - return Some(format!("FAILED (Couldn't find {})", stringify!($name))); - } - }; -} - -#[macro_export] -macro_rules! find_signature_inner_result { - ($scanner:ident, $name:ident, $type:ty) => { - let $name: $type; - if let Some(ptr) = SIGNATURES0.$name.find(&$scanner, $crate::version::get().1) { - $name = ptr as $type; - } else { - return Err(format!("FAILED (Couldn't find {})", stringify!($name))); - } - }; -} - -#[macro_export] -macro_rules! find_signature { - ($scanner:ident, $name:ident as $type:ty) => { - find_signature_inner!($scanner, $name, $type); - }; - - ($scanner:ident,($name:ident as $type:ty)) => { - find_signature_inner!($scanner, $name, $type); - }; - - ($scanner:ident, $name:ident) => { - find_signature_inner!($scanner, $name, *const c_void); - }; -} - -#[macro_export] -macro_rules! find_signature_result { - ($scanner:ident, $name:ident as $type:ty) => { - find_signature_inner_result!($scanner, $name, $type); - }; - - ($scanner:ident,($name:ident as $type:ty)) => { - find_signature_inner_result!($scanner, $name, $type); - }; - - ($scanner:ident, $name:ident) => { - find_signature_inner_result!($scanner, $name, *const c_void); - }; -} - -#[macro_export] -macro_rules! find_signatures { - ($scanner:ident, $($sig:tt),* ) => { - $( - find_signature!($scanner, $sig); - )* - }; -} - -#[macro_export] -macro_rules! find_signatures_result { - ($scanner:ident, $($sig:tt),* ) => { - $( - find_signature_result!($scanner, $sig); - )* - }; -} - -pub enum SignatureTreatment { - NoOffset, - OffsetByInt(isize), - OffsetByCall -} - -pub struct Signature { - pub treatment: SignatureTreatment, - pub bytes: &'static [Option] -} - -impl Signature { - pub fn find(&self, scanner: &Scanner) -> Option<*const std::ffi::c_void> { - scanner.find(self.bytes).map(|address| unsafe { - match self.treatment { - SignatureTreatment::NoOffset | SignatureTreatment::OffsetByInt(0) => address as *const std::ffi::c_void, - SignatureTreatment::OffsetByInt(i) => (address.offset(i) as *const *const std::ffi::c_void).read_unaligned(), - SignatureTreatment::OffsetByCall => { - let offset = (address.offset(1) as *const isize).read_unaligned(); - address.offset(5).offset(offset) as *const () as *const std::ffi::c_void - } - } - }) - } -} - -pub enum SignatureMap { - AllVersions(Signature), - VersionDependent(Vec<((Bound<&'static u32>, Bound<&'static u32>), Signature)>) -} - -impl SignatureMap { - pub fn find(&self, scanner: &Scanner, version: u32) -> Option<*const std::ffi::c_void> { - match self { - Self::AllVersions(signature) => signature.find(scanner), - Self::VersionDependent(map) => map - .iter() - .find(|(version_range, _)| version_range.contains(&version)) - .and_then(|(_, signature)| signature.find(scanner)) - } - } -} diff --git a/auxtools/src/sigscan/linux.rs b/auxtools/src/sigscan/linux.rs deleted file mode 100644 index 3446c0ec..00000000 --- a/auxtools/src/sigscan/linux.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::{ - ffi::{c_void, CStr, CString}, - os::raw::{c_char, c_int} -}; - -use libc::{dl_iterate_phdr, dl_phdr_info, Elf32_Phdr, PT_LOAD}; - -#[repr(C)] -struct CallbackData { - module_name_ptr: *const c_char, - memory_start: usize, - memory_len: usize, - memory_area: Option<&'static [u8]> -} - -pub struct Scanner { - module_name: String -} - -extern "C" fn dl_phdr_callback(info: *mut dl_phdr_info, _size: usize, data: *mut c_void) -> c_int { - let info = unsafe { *info }; - let module_name = unsafe { CStr::from_ptr(info.dlpi_name) }.to_str().unwrap(); - let cb_data = unsafe { &mut *(data as *mut CallbackData) }; - let target_module_name = unsafe { CStr::from_ptr(cb_data.module_name_ptr as *mut c_char) }.to_str().unwrap(); - if !module_name.ends_with(target_module_name) { - return 0; - } - - let headers: &'static [Elf32_Phdr] = unsafe { std::slice::from_raw_parts(info.dlpi_phdr, info.dlpi_phnum as usize) }; - let elf_header = headers.iter().filter(|p| p.p_type == PT_LOAD).next().unwrap(); - - let start = (info.dlpi_addr + elf_header.p_vaddr) as usize; - let end = start + elf_header.p_memsz as usize; - let len = end - start; - - cb_data.memory_start = start; - cb_data.memory_len = len; - cb_data.memory_area = Some(unsafe { std::slice::from_raw_parts(start as *const u8, len) }); - 0 -} - -impl Scanner { - pub fn for_module(name: &str) -> Option { - Some(Scanner { - module_name: name.to_string() - }) - } - - pub fn find(&self, signature: &[Option]) -> Option<*mut u8> { - let module_name = CString::new(self.module_name.clone()).unwrap(); - let module_name_ptr = module_name.as_ptr(); - let mut data = CallbackData { - module_name_ptr, - memory_start: 0, - memory_len: 0, - memory_area: None - }; - unsafe { dl_iterate_phdr(Some(dl_phdr_callback), &mut data as *mut CallbackData as *mut c_void) }; - - let mut data_current = data.memory_start as *mut u8; - let data_end = (data.memory_start + data.memory_len) as *mut u8; - - if data_current.is_null() || data_end == data_current { - // There's no more bytes to scan or the module wasn't found. - return None; - } - - let mut signature_offset = 0; - let mut result: Option<*mut u8> = None; - - unsafe { - while data_current <= data_end { - if signature[signature_offset] == None || signature[signature_offset] == Some(*data_current) { - if signature.len() <= signature_offset + 1 { - if result.is_some() { - // Found two matches. - return None; - } - result = Some(data_current.offset(-(signature_offset as isize))); - data_current = data_current.offset(-(signature_offset as isize)); - signature_offset = 0; - } else { - signature_offset += 1; - } - } else { - data_current = data_current.offset(-(signature_offset as isize)); - signature_offset = 0; - } - - data_current = data_current.offset(1); - } - } - - result - } -} - -#[cfg(test)] -mod tests {} diff --git a/auxtools/src/sigscan/windows.rs b/auxtools/src/sigscan/windows.rs deleted file mode 100644 index 49ba09ed..00000000 --- a/auxtools/src/sigscan/windows.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::{mem, ptr}; - -use winapi::{ - shared::minwindef, - um::{libloaderapi, processthreadsapi, psapi} -}; - -pub struct Scanner { - _module: minwindef::HMODULE, - data_begin: *mut u8, - data_end: *mut u8 -} - -impl Scanner { - pub fn for_module(name: &str) -> Option { - let mut module: minwindef::HMODULE = ptr::null_mut(); - let data_begin: *mut u8; - let data_end: *mut u8; - - // Construct a null-terminated UTF-16 string to pass to the Windows API - let name_winapi: Vec = name.encode_utf16().chain(std::iter::once(0)).collect(); - - unsafe { - if libloaderapi::GetModuleHandleExW(0, name_winapi.as_ptr(), &mut module) == 0 { - return None; - } - - let mut module_info_wrapper = mem::MaybeUninit::::zeroed(); - if psapi::GetModuleInformation( - processthreadsapi::GetCurrentProcess(), - module, - module_info_wrapper.as_mut_ptr(), - mem::size_of::() as u32 - ) == 0 - { - libloaderapi::FreeLibrary(module); - return None; - } - - let module_info = module_info_wrapper.assume_init(); - data_begin = module_info.lpBaseOfDll as *mut u8; - data_end = data_begin.offset(module_info.SizeOfImage as isize).offset(-1); - } - - Some(Scanner { - _module: module, - data_begin, - data_end - }) - } - - pub fn find(&self, signature: &[Option]) -> Option<*mut u8> { - let mut data_current = self.data_begin; - let data_end = self.data_end; - let mut signature_offset = 0; - let mut result: Option<*mut u8> = None; - - unsafe { - while data_current <= data_end { - if signature[signature_offset].is_none() || signature[signature_offset] == Some(*data_current) { - if signature.len() <= signature_offset + 1 { - if result.is_some() { - // Found two matches. - return None; - } - result = Some(data_current.offset(-(signature_offset as isize))); - data_current = data_current.offset(-(signature_offset as isize)); - signature_offset = 0; - } else { - signature_offset += 1; - } - } else { - data_current = data_current.offset(-(signature_offset as isize)); - signature_offset = 0; - } - - data_current = data_current.offset(1); - } - } - - result - } -} - -impl Drop for Scanner { - fn drop(&mut self) { - // TODO: WTf this started throwing?! - // unsafe { - // libloaderapi::FreeLibrary(self.module); - // } - } -} - -#[cfg(test)] -mod tests {} diff --git a/auxtools/src/symbols.rs b/auxtools/src/symbols.rs new file mode 100644 index 00000000..ac8f07f5 --- /dev/null +++ b/auxtools/src/symbols.rs @@ -0,0 +1,119 @@ +//! Finds the BYOND functions and globals auxtools needs, through `byond-scan`. +//! +//! Every miss is an error that names the lookup. Nothing here hands back a null +//! pointer, because the C++ shim calls these functions without checking and +//! `debug.rs` reads the globals the same way. + +#[cfg(unix)] +mod linux; +#[cfg(windows)] +mod windows; + +use std::ffi::c_void; + +use byond_scan::{resolve_recipe, Module, Recipe}; +#[cfg(unix)] +use linux::RECIPES; +#[cfg(windows)] +use windows::RECIPES; + +use crate::raw_types::{funcs, procs, variables::VariableNameIdTable}; + +/// The BYOND major version the patterns were checked against. The same build +/// number under another major is a different binary. +const SUPPORTED_VERSION: u32 = 516; + +fn module_and_build() -> Result<(Module, (u32, u32)), String> { + let module = Module::current().map_err(|err| format!("Couldn't locate BYOND core ({err})"))?; + let build = byond_scan::version().map_err(|err| format!("Couldn't read BYOND version ({err})"))?; + Ok((module, build)) +} + +fn recipe(name: &str) -> Result<&'static Recipe, String> { + RECIPES + .iter() + .find(|recipe| recipe.name == name) + .ok_or_else(|| format!("Couldn't find {name} (no such pattern)")) +} + +fn resolve(module: &Module, build: (u32, u32), name: &str) -> Result { + resolve_checked(module, build, recipe(name)?) +} + +fn resolve_checked(module: &Module, (version, build): (u32, u32), recipe: &Recipe) -> Result { + let name = recipe.name; + + // Older than anything checked is a hard stop. A newer build is still tried, + // and a pattern that has stopped matching fails with its own error. + if recipe.versions.applicability(version, build, SUPPORTED_VERSION).is_none() { + return Err(format!( + "Couldn't find {name} (BYOND {version}.{build} is outside the checked range {}..={})", + recipe.versions.min, recipe.versions.max + )); + } + + resolve_recipe(module, recipe, |_| None).map_err(|err| format!("Couldn't find {name} ({err})")) +} + +/// Resolves a recipe of your own with the same rules auxtools applies to its +/// own: builds older than the recipe's range are refused, newer ones are tried. +pub fn find_recipe(recipe: &Recipe) -> Result { + let (module, build) = module_and_build()?; + resolve_checked(&module, build, recipe) +} + +/// Looks up every function and global except `variable_names`, and hands them +/// to the C++ shim and `raw_types::funcs`. +pub(crate) fn resolve_full() -> Result<(), String> { + let (module, build) = module_and_build()?; + let address = |name| resolve(&module, build, name); + let function = |name| address(name).map(|address| address as *const c_void); + + unsafe { + funcs::CURRENT_EXECUTION_CONTEXT = address("current_execution_context")? as *mut *mut procs::ExecutionContext; + funcs::SUSPENDED_PROCS = address("suspended_procs")? as *mut procs::SuspendedProcs; + funcs::SUSPENDED_PROCS_BUFFER = address("suspended_procs_buffer")? as *mut procs::SuspendedProcsBuffer; + funcs::call_proc_by_id_byond = function("call_proc_by_id")?; + funcs::call_datum_proc_by_name_byond = function("call_datum_proc_by_name")?; + funcs::get_proc_array_entry_byond = function("get_proc_array_entry")?; + funcs::get_string_id_byond = function("get_string_id")?; + funcs::get_variable_byond = function("get_variable")?; + funcs::set_variable_byond = function("set_variable")?; + funcs::get_string_table_entry_byond = function("get_string_table_entry")?; + funcs::inc_ref_count_byond = function("inc_ref_count")?; + funcs::dec_ref_count_byond = function("dec_ref_count")?; + funcs::get_assoc_element_byond = function("get_assoc_element")?; + funcs::set_assoc_element_byond = function("set_assoc_element")?; + funcs::create_list_byond = function("create_list")?; + funcs::append_to_list_byond = function("append_to_list")?; + funcs::remove_from_list_byond = function("remove_from_list")?; + funcs::get_length_byond = function("get_length")?; + funcs::get_misc_by_id_byond = function("get_misc_by_id")?; + funcs::to_string_byond = function("to_string")?; + funcs::runtime_byond = function("runtime")?; + } + + Ok(()) +} + +/// Looks up `variable_names`. BYOND reallocates it for every world, so this +/// runs on every partial init and not only the first. +pub(crate) fn resolve_partial() -> Result<(), String> { + let (module, build) = module_and_build()?; + let address = resolve(&module, build, "variable_names")?; + unsafe { + funcs::VARIABLE_NAMES = address as *const VariableNameIdTable; + } + Ok(()) +} + +/// The names that fail to resolve on the running build, for +/// `auxtools_check_signatures`. +pub(crate) fn unresolved() -> Result, String> { + let (module, build) = module_and_build()?; + Ok(RECIPES + .iter() + .map(|recipe| recipe.name) + .filter(|name| resolve(&module, build, name).is_err()) + .collect()) +} diff --git a/auxtools/src/symbols/linux.rs b/auxtools/src/symbols/linux.rs new file mode 100644 index 00000000..8049c225 --- /dev/null +++ b/auxtools/src/symbols/linux.rs @@ -0,0 +1,270 @@ +//! Where auxtools finds its BYOND functions and globals in `libbyond.so`. +//! +//! Copied from `byond_catalog` in the byond-re repo, where `verify_recipes` +//! checks each pattern against every local build. Keep the two in step. + +use byond_scan::{Anchor, Extract, OperandSelect, Recipe, SignatureTreatment, VersionRange}; + +// Checked on every build in this range. Older builds are refused by the +// resolver, newer ones are tried and can still fail. +const SUPPORTED: VersionRange = VersionRange { min: 1659, max: 1688 }; + +// One loader mask finds the variable-name table. Operands that don't use a +// register are count=0, global_values=1 and variable_names=2. +const GLOBAL_VARS_SIG: &str = "89 1D ?? ?? ?? ?? 57 E8 ?? ?? ?? ?? 83 C4 10 85 FF 74 08 85 C0 0F 84 ?? ?? ?? ?? 83 EC 0C C1 E3 02 A3 ?? ?? ?? ?? 53 \ + E8 ?? ?? ?? ?? 83 C4 10 85 DB 74 08 85 C0 0F 84 ?? ?? ?? ?? A3 ?? ?? ?? ??"; + +// The prefix tells this enqueue apart from the dequeue clones. Operands are +// back=0, front=1, buffer=2. The two index globals sit the other way round from +// Windows, so back is 4 bytes below front. +const SUSPENDED_SIG: &str = "55 57 89 C7 56 53 83 EC ?? 8B 0D ?? ?? ?? ?? 80 48 04 04 8B A8 8C 00 00 00 A1 ?? ?? ?? ?? 89 4C 24 ?? 8B 35 ?? ?? ?? ??"; + +// `runtime` opens with a load of the current execution context, so the same +// mask finds both. +const RUNTIME_SIG: &str = + "55 89 E5 56 53 83 EC ?? 8B 15 ?? ?? ?? ?? 85 D2 0F 84 ?? ?? ?? ?? 0F B6 42 6F 3C 01 76 ?? 31 C9 84 C0 C7 42 10 ?? ?? ?? ?? 0F 94 C3"; + +const PROCDEF_ACCESSOR_SIG: &str = "8B 44 24 04 39 05 ?? ?? ?? ?? 76 ?? 6B C0 ?? 03 05 ?? ?? ?? ?? C3"; +const STRING_ACCESSOR_SIG: &str = "56 53 83 EC 14 8B 44 24 20 39 05 ?? ?? ?? ?? 76 ?? 8B 15 ?? ?? ?? ?? 8B 04 82"; + +pub(crate) const RECIPES: &[Recipe] = &[ + Recipe { + name: "get_proc_array_entry", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, PROCDEF_ACCESSOR_SIG), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_string_table_entry", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, STRING_ACCESSOR_SIG), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_string_id", + versions: SUPPORTED, + // frameless mkstr, pinned by the comparison against 0x01 + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 57 56 89 CE 53 89 D3 83 EC ?? 8B 54 24 ?? 88 54 24 ?? 85 C0 0F 84 ?? ?? ?? ?? 89 44 24 ?? 84 DB 0F 88 ?? ?? ?? ?? 88 5C 24 ?? 80 FB \ + 01 0F 84 ?? ?? ?? ?? 89 F0 84 C0" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "call_proc_by_id", + versions: SUPPORTED, + // the frame that builds a call record and drops into the interpreter. Every + // `mov reg,[esp+disp]` argument load has its displacement wildcarded because + // they all move with the frame. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 31 C0 57 56 53 81 EC ?? ?? ?? ?? F3 0F 7E 8C 24 ?? ?? ?? ?? 8B 8C 24 ?? ?? ?? ?? 8A 84 24 ?? ?? ?? ?? 8B 9C 24 ?? ?? ?? ?? F3 0F 7E \ + 84 24" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "call_datum_proc_by_name", + versions: SUPPORTED, + // positive `ebp` displacements are incoming arguments and stay pinned. The + // negative ones are locals and move with the frame. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 31 C9 89 E5 57 56 8D 45 ?? 53 83 EC ?? F3 0F 7E 45 0C 8B 5D 14 C6 45 ?? 00 8B 55 18 8B 75 08 6A 00 66 0F D6 45 ?? 0F B6 FB 66 0F 7E \ + 45 ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_variable", + versions: SUPPORTED, + // `3C 58` is `cmp al, 58h`, the tag bound of the 89-case switch, and it is + // what makes this unique + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 57 56 53 83 EC ?? F3 0F 7E 4D 0C 8B 75 08 8B 5D 14 66 0F 6F C1 66 0F 7E CA 66 0F 73 D0 20 0F B6 C2 66 0F 7E 45 ?? 3C 58 77 ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "set_variable", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 57 56 53 83 EC ?? F3 0F 7E 55 08 8B 7D 10 66 0F 6F C2 66 0F 7E D2 66 0F 73 D0 20 89 D3 66 0F 7E C6 F3 0F 7E 45 14 89 75 ?? 66 \ + 0F 6F C8" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "inc_ref_count", + versions: SUPPORTED, + // `80 F9 4A` is `cmp cl, 4Ah`, the tag table bound. The `66 0F 7E C0` before + // it is the only thing telling this apart from dec_ref_count in the first 48 + // bytes. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 56 53 83 EC ?? F3 0F 7E 4D 08 66 0F 6F C1 66 0F 7E C9 66 0F 73 D0 20 66 0F 7E C0 80 F9 4A 77 ?? 0F B6 C9 FF 24 8D ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "dec_ref_count", + versions: SUPPORTED, + // the fifth call out of the exported ByondValue_DecRef. A shifted call chain + // fails silently, so byond-re checks this index with a direct mask as well. + anchor: Anchor::Export(c"ByondValue_DecRef"), + hops: &[4], + extract: Extract::Entry + }, + Recipe { + name: "get_assoc_element", + versions: SUPPORTED, + // splits from set_assoc_element at +0x0E: `8B 7D` here against the setter's + // `66 0F 6F`. Everything before that is identical or wildcarded. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 57 56 53 83 EC ?? F3 0F 7E 4D 0C 8B 7D 08 66 0F 6F C1 66 0F 7E C8 66 0F 7E 4D ?? 66 0F 73 D0 20 66 0F 7E 45 ?? 3C 3C 0F 84 ?? \ + ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "set_assoc_element", + versions: SUPPORTED, + // the prologue changes after 1659, so follow this caller. It is unique across + // the range, and its alignment padding is wildcarded. + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 C4 20 E9 ?? ?? ?? ?? ?? ?? ?? ?? 31 C9 EB ?? D9 43 04 BA 01 00 00 00" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "create_list", + versions: SUPPORTED, + // `81 FB FF FF 00 00` is `cmp ebx, 0FFFFh`, the failure sentinel. It is what + // tells this apart from the `new` dispatcher, which opens the same way. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 57 56 53 83 EC ?? A1 ?? ?? ?? ?? 8B 7C 24 ?? 85 C0 0F 84 ?? ?? ?? ?? 8B 15 ?? ?? ?? ?? 83 E8 01 A3 ?? ?? ?? ?? 8B 1C 82 81 FB FF FF \ + 00 00" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "append_to_list", + versions: SUPPORTED, + // the Value arrives in registers, so the head is a run of movd/punpckldq + // instead of stack loads. It opens with `56` where remove_from_list opens + // with `55`. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "56 66 0F 6E CA 66 0F 6E C0 53 66 0F 62 C1 66 0F 7E C6 66 0F 6F D0 66 0F 73 D0 20 89 F1 83 EC ?? 66 0F 7E C0 F3 0F 7E 4C 24 ?? 66 0F 6F \ + C1" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "remove_from_list", + versions: SUPPORTED, + // the prologue mask only covers up to 1669 and the head changes at 1675, so + // follow this caller, which is unique on every build. Its `ebp` + // displacements are wildcarded. + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 85 ?? ?? ?? ?? 01 8B 8D ?? ?? ?? ?? 83 C4 10 83 85 ?? ?? ?? ?? 08 39" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_length", + versions: SUPPORTED, + // the tag-switch gate of the real length helper. An older mask resolved + // value_is_list instead. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 57 56 53 83 EC ?? F3 0F 7E 4D 0C 8B 5D 08 66 0F 7E CA 83 EA 0C 80 FA 4B 77 ?? 66 0F 6F C1 0F B6 D2 66 0F 73 D0 20 66 0F 7E C0 \ + FF 24 95 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_misc_by_id", + versions: SUPPORTED, + // the plain accessor body matches 17 times, so follow a unique caller + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 89 85 ?? ?? ?? ?? 83 C4 10 31 DB 85 C0 74 03 0F B7 18 8B 85 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "to_string", + versions: SUPPORTED, + // the 89-case interning switch. Stops before unstable alignment padding. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 57 56 53 83 EC ?? 0F B6 45 08 3C 45 77 ?? 3C 27 77 ?? 8D 50 FF 80 FA 26 77 ?? FF 24 85 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "runtime", + versions: SUPPORTED, + // throws through __cxa_throw here, where Windows uses _CxxThrowException + anchor: Anchor::Signature(SignatureTreatment::NoOffset, RUNTIME_SIG), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "current_execution_context", + versions: SUPPORTED, + // `runtime` opens with a load of this global, and nothing touches memory + // before that, so it is operand 0 + anchor: Anchor::Signature(SignatureTreatment::NoOffset, RUNTIME_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + // BYOND reallocates this per world load, so it is looked up again on every + // partial init. + Recipe { + name: "variable_names", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, GLOBAL_VARS_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(2)) + }, + // the start of the sleeping-proc queue: back, then front + Recipe { + name: "suspended_procs", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, SUSPENDED_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + Recipe { + name: "suspended_procs_buffer", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, SUSPENDED_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(2)) + } +]; diff --git a/auxtools/src/symbols/windows.rs b/auxtools/src/symbols/windows.rs new file mode 100644 index 00000000..59e360e4 --- /dev/null +++ b/auxtools/src/symbols/windows.rs @@ -0,0 +1,251 @@ +//! Where auxtools finds its BYOND functions and globals in `byondcore.dll`. +//! +//! Copied from `byond_catalog` in the byond-re repo, where `verify_recipes` +//! checks each pattern against every local build. Keep the two in step. + +use byond_scan::{Anchor, Extract, OperandSelect, Recipe, SignatureTreatment, VersionRange}; + +// Checked on every build in this range. Older builds are refused by the +// resolver, newer ones are tried and can still fail. +const SUPPORTED: VersionRange = VersionRange { min: 1659, max: 1688 }; + +// One loader mask finds the variable-name table. Operands that don't use a +// register are count=0, global_values=1, the sizing lea=3 and variable_names=4. +// The lea is why variable_names is not index 3. +const GLOBAL_VARS_SIG: &str = "A3 ?? ?? ?? ?? 56 FF D3 83 C4 04 85 F6 74 08 85 C0 0F 84 4B 01 00 00 A3 ?? ?? ?? ?? A1 ?? ?? ?? ?? 8D 34 85 00 00 00 \ + 00 56 FF D3 83 C4 04 85 F6 74 08 85 C0 0F 84 2D 01 00 00 33 DB A3 ?? ?? ?? ?? 39 1D"; + +// The prologue tells this queue access apart from its dequeue clone. Operands +// are front=0, back=1, buffer=2. +const SUSPENDED_SIG: &str = + "55 8B EC 56 8B 75 08 56 80 4E 04 04 E8 ?? ?? ?? ?? 8B 0D ?? ?? ?? ?? 83 C4 04 3B 0D ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? 8B 04 88"; + +// The procdef accessor has a 44-byte stride, which sets it apart from the other +// table accessors. +const PROCDEF_ACCESSOR_SIG: &str = "55 8B EC 8B 45 08 3B 05 ?? ?? ?? ?? 72 04 33 C0 5D C3 6B C0 2C 03 05"; +// The branch distance varies, so it is wildcarded and the mask keeps a longer +// tail. +const STRING_ACCESSOR_SIG: &str = "55 8B EC 8B 4D 08 3B 0D ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? 8B 04 88 85 C0 0F 85"; + +pub(crate) const RECIPES: &[Recipe] = &[ + Recipe { + name: "get_proc_array_entry", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, PROCDEF_ACCESSOR_SIG), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_string_table_entry", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, STRING_ACCESSOR_SIG), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_string_id", + versions: SUPPORTED, + // mkstr, where string interning goes in + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 8B 45 ?? 83 EC ?? 53 56 8B 35 ?? ?? ?? ?? 57 85 C0 75 ?? 68 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "call_proc_by_id", + versions: SUPPORTED, + // the frame size is wildcarded because the compiler picks it + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 81 EC ?? ?? ?? ?? A1 ?? ?? ?? ?? 33 C5 89 45 ?? 8B 55 ?? 8B 45 ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "call_datum_proc_by_name", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC ?? 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 75 1C 8D 45 \ + F3 8B 7D 18 8B 5D 10 6A 00" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_variable", + versions: SUPPORTED, + // the 0x0C frame size has to stay pinned: without it the mask matches one of + // several common SEH prologues. A changed frame should fail as NotFound. + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 0C 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 8B 75 \ + ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "set_variable", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC 24 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 4D ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "inc_ref_count", + versions: SUPPORTED, + // no prologue worth masking, so anchor on a call site and follow it + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? FF ?? ?? FF ?? ?? E8 ?? ?? ?? ?? 8D ?? ?? 56 E8" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "dec_ref_count", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 C4 0C 81 FF FF FF 00 00 74 ?? 85 FF 74" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_assoc_element", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 51 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 8B 5D 08 80" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "set_assoc_element", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 ?? ?? ?? ?? 50 83 EC ?? 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 ?? 64 A3 ?? ?? ?? ?? 8B 5D ?? 80 FB \ + 0F" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "create_list", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 8B 0D ?? ?? ?? ?? 56 85 C9 74 1B A1 ?? ?? ?? ?? 49 89 0D ?? ?? ?? ?? 8B 34 88 81 FE FF FF 00 00" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "append_to_list", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 8B 4D ?? 0F B6 C1 48 56 57 83 F8 54 0F 87 ?? ?? ?? ?? 0F B6 80 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "remove_from_list", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 83 EC 08 53 8B 5D ?? 0F B6 C3 48 56 57 83 F8 54 0F 87 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_length", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 8B 4D ?? 83 EC ?? 0F B6 C1 48 53 56 57 83 F8 ?? 0F 87 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "get_misc_by_id", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 C4 04 85 C0 74 08 0F B7 38 8B 70 08 EB 04 33 FF 33 F6 0F B7 C7 50 89 45 F8 E8 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "to_string", + versions: SUPPORTED, + // the prologue changes at 1686 and any shared prefix is ambiguous, so follow + // this caller instead. It is unique across the whole range. + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 C4 08 8B F0 EB ?? 80 FB 2A 75 ?? 66 83 7D F8" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "runtime", + versions: SUPPORTED, + anchor: Anchor::Signature( + SignatureTreatment::OffsetByCall, + "E8 ?? ?? ?? ?? 83 C4 04 8B 85 ?? ?? ?? ?? 0F B6 C0 51 66 0F 6E C0 0F 5B C0" + ), + hops: &[], + extract: Extract::Entry + }, + Recipe { + name: "current_execution_context", + versions: SUPPORTED, + // the `A1` load from an absolute address has no base register, which makes + // it operand 0 + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "A1 ?? ?? ?? ?? 56 53 6A 00 8B 00 57 6A ?? 89 4D ?? FF 70 ?? 8B 4D ?? FF" + ), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + // BYOND reallocates this per world load, so it is looked up again on every + // partial init. + Recipe { + name: "variable_names", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, GLOBAL_VARS_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(4)) + }, + // the start of the sleeping-proc queue: front, then back, then a capacity + Recipe { + name: "suspended_procs", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, SUSPENDED_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + Recipe { + name: "suspended_procs_buffer", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, SUSPENDED_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(2)) + } +]; diff --git a/auxtools/src/version.rs b/auxtools/src/version.rs index 70ceff66..8a845c36 100644 --- a/auxtools/src/version.rs +++ b/auxtools/src/version.rs @@ -1,82 +1,12 @@ -use super::*; -use std::{ffi::CString, os::raw::c_char}; - pub static mut BYOND_VERSION_MAJOR: u32 = 0; pub static mut BYOND_VERSION_MINOR: u32 = 0; -#[cfg(windows)] -static GET_BYOND_VERSION_SYMBOL: &[u8] = b"?GetByondVersion@ByondLib@@QAEJXZ\0"; - -#[cfg(windows)] -static GET_BYOND_BUILD_SYMBOL: &[u8] = b"?GetByondBuild@ByondLib@@QAEJXZ\0"; - -#[cfg(unix)] -static GET_BYOND_VERSION_SYMBOL: &[u8] = b"_ZN8ByondLib15GetByondVersionEv\0"; - -#[cfg(unix)] -static GET_BYOND_BUILD_SYMBOL: &[u8] = b"_ZN8ByondLib13GetByondBuildEv\0"; - pub fn init() -> Result<(), String> { - let get_byond_version: extern "C" fn() -> u32; - let get_byond_build: extern "C" fn() -> u32; - - #[cfg(windows)] - { - use winapi::um::libloaderapi; - - unsafe { - let mut module = std::ptr::null_mut(); - let core_str = CString::new(BYONDCORE).unwrap(); - if libloaderapi::GetModuleHandleExA(0, core_str.as_ptr(), &mut module) == 0 { - return Err("Couldn't get module handle for BYONDCORE".into()); - } - - let symbol = libloaderapi::GetProcAddress(module, GET_BYOND_VERSION_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find get_byond_version in BYONDCORE".into()); - } - - get_byond_version = std::mem::transmute(symbol); - - let symbol = libloaderapi::GetProcAddress(module, GET_BYOND_BUILD_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find get_byond_build in BYONDCORE".into()); - } - - get_byond_build = std::mem::transmute(symbol); - } - } - - #[cfg(unix)] - { - use libc::{dlopen, dlsym, RTLD_LAZY}; - - unsafe { - let byond_core_str = CString::new(BYONDCORE).unwrap(); - let module = dlopen(byond_core_str.as_ptr(), RTLD_LAZY); - if module.is_null() { - return Err("Couldn't get module handle for BYONDCORE".into()); - } - - let symbol = dlsym(module, GET_BYOND_VERSION_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find get_byond_version in BYONDCORE".into()); - } - - get_byond_version = std::mem::transmute(symbol); - - let symbol = dlsym(module, GET_BYOND_BUILD_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find get_byond_build in BYONDCORE".into()); - } - - get_byond_build = std::mem::transmute(symbol); - } - } + let (major, minor) = byond_scan::version().map_err(|err| format!("Couldn't read the BYOND version ({err})"))?; unsafe { - BYOND_VERSION_MAJOR = get_byond_version(); - BYOND_VERSION_MINOR = get_byond_build(); + BYOND_VERSION_MAJOR = major; + BYOND_VERSION_MINOR = minor; } Ok(()) diff --git a/debug_server/Cargo.toml b/debug_server/Cargo.toml index fff1b54f..17cf6074 100644 --- a/debug_server/Cargo.toml +++ b/debug_server/Cargo.toml @@ -23,10 +23,7 @@ region = "3" retour = { workspace = true } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["winuser", "libloaderapi", "errhandlingapi"] } - -[target.'cfg(unix)'.dependencies] -libc = "0.2" +winapi = { version = "0.3", features = ["libloaderapi", "errhandlingapi", "processthreadsapi"] } [lints] workspace = true diff --git a/debug_server/src/ckey_override.rs b/debug_server/src/ckey_override.rs index a21fcf3b..85b0a409 100644 --- a/debug_server/src/ckey_override.rs +++ b/debug_server/src/ckey_override.rs @@ -1,23 +1,35 @@ use std::{ffi::CString, os::raw::c_char}; +#[cfg(windows)] +use auxtools::byond_scan::{Anchor, Extract, Recipe, SignatureTreatment, VersionRange}; use auxtools::*; use region::Protection; static mut STRING_PTR: *mut *const c_char = std::ptr::null_mut(); +// The `push` of the "Guest-%u" format string. We want the address of its 4-byte +// immediate, one byte past the opcode, so we can swap the string pointer out. +// Copied from `byond_catalog` in the byond-re repo, which checks it against +// every local build. +#[cfg(windows)] +const GUEST_NAME_FORMAT: Recipe = Recipe { + name: "guest_name_format", + versions: VersionRange { min: 1659, max: 1688 }, + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "68 ?? ?? ?? ?? 50 E8 ?? ?? ?? ?? 83 C4 0C 8D 8D ?? ?? ?? ?? E8 ?? ?? ?? ?? 8B 85 ?? ?? ?? ??" + ), + hops: &[], + extract: Extract::Offset(1) +}; + #[init(full)] fn ckey_override_init() -> Result<(), String> { + // This feature soft-fails #[cfg(windows)] - { - let byondcore = sigscan::Scanner::for_module(BYONDCORE).unwrap(); - - // This feature soft-fails - if let Some(ptr) = byondcore.find(signature!( - "68 ?? ?? ?? ?? 50 E8 ?? ?? ?? ?? 83 C4 0C 8D 8D ?? ?? ?? ?? E8 ?? ?? ?? ?? 8B 85 ?? ?? ?? ??" - )) { - unsafe { - STRING_PTR = ptr.add(1) as *mut *const c_char; - } + if let Ok(address) = find_recipe(&GUEST_NAME_FORMAT) { + unsafe { + STRING_PTR = address as *mut *const c_char; } } diff --git a/debug_server/src/stddef.rs b/debug_server/src/stddef.rs index 1cc95143..72a70ac8 100644 --- a/debug_server/src/stddef.rs +++ b/debug_server/src/stddef.rs @@ -1,61 +1,25 @@ use std::{ - ffi::{c_void, CStr, CString}, + ffi::{c_void, CStr}, os::raw::c_char }; -use auxtools::*; +use auxtools::{byond_scan::find_export, *}; #[cfg(windows)] -static STDDEF_FN_SYMBOL: &[u8] = b"?StdDefDM@DungBuilder@@QAEPADXZ\0"; +const STDDEF_FN_SYMBOL: &CStr = c"?StdDefDM@DungBuilder@@QAEPADXZ"; #[cfg(unix)] -static STDDEF_FN_SYMBOL: &[u8] = b"_ZN11DungBuilder8StdDefDMEv\0"; +const STDDEF_FN_SYMBOL: &CStr = c"_ZN11DungBuilder8StdDefDMEv"; static mut STDDEF: Option<&'static str> = None; #[init(full)] fn stddef_init() -> Result<(), String> { - let stddef_fn: extern "C" fn(*const c_void) -> *const c_char; - - #[cfg(windows)] - { - use winapi::um::libloaderapi; - - unsafe { - let mut module = std::ptr::null_mut(); - let core_str = CString::new(BYONDCORE).unwrap(); - if libloaderapi::GetModuleHandleExA(0, core_str.as_ptr(), &mut module) == 0 { - return Err("Couldn't get module handle for BYONDCORE".into()); - } - - let symbol = libloaderapi::GetProcAddress(module, STDDEF_FN_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find STDDEF_FN in BYONDCORE".into()); - } - - stddef_fn = std::mem::transmute(symbol); - } - } - - #[cfg(unix)] - { - use libc::{dlopen, dlsym, RTLD_LAZY}; - - unsafe { - let byond_core_str = CString::new(BYONDCORE).unwrap(); - let module = dlopen(byond_core_str.as_ptr(), RTLD_LAZY); - if module.is_null() { - return Err("Couldn't get module handle for BYONDCORE".into()); - } - - let symbol = dlsym(module, STDDEF_FN_SYMBOL.as_ptr() as *const c_char); - if symbol.is_null() { - return Err("Couldn't find STDDEF_FN in BYONDCORE".into()); - } - - stddef_fn = std::mem::transmute(symbol); - } + let symbol = find_export(STDDEF_FN_SYMBOL); + if symbol.is_null() { + return Err("Couldn't find STDDEF_FN in BYONDCORE".into()); } + let stddef_fn: extern "C" fn(*const c_void) -> *const c_char = unsafe { std::mem::transmute(symbol) }; unsafe { match CStr::from_ptr(stddef_fn(std::ptr::null())).to_str() { diff --git a/instruction_hooking/Cargo.toml b/instruction_hooking/Cargo.toml index aa227199..eb442608 100644 --- a/instruction_hooking/Cargo.toml +++ b/instruction_hooking/Cargo.toml @@ -15,14 +15,6 @@ cc = "1.0" auxtools = { path = "../auxtools" } dmasm = { workspace = true } retour = { workspace = true } -symbolic-common = "12" -symbolic-demangle = { version = "12", default-features = false } - -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["winuser", "libloaderapi", "errhandlingapi"] } - -[target.'cfg(unix)'.dependencies] -libc = "0.2" [lints] workspace = true diff --git a/instruction_hooking/src/execute_instruction_hook.unix.S b/instruction_hooking/src/execute_instruction_hook.unix.S index 2172553b..2916fea7 100644 --- a/instruction_hooking/src/execute_instruction_hook.unix.S +++ b/instruction_hooking/src/execute_instruction_hook.unix.S @@ -4,15 +4,16 @@ .extern execute_instruction_original .extern handle_instruction -// EDI = [CURRENT_EXECUTION_CONTEXT] +// ESI = [CURRENT_EXECUTION_CONTEXT] execute_instruction_hook: // Give rust a chance to handle the instruction. Leaves [CURRENT_EXECUTION_CONTEXT] in EAX. + // The 16 bytes pushed here keep ESP 16-byte aligned at the call. SUB ESP, 0x04 PUSH ECX PUSH EDX - PUSH EDI + PUSH ESI call handle_instruction - MOV EDI, EAX + MOV ESI, EAX ADD ESP, 0x04 POP EDX POP ECX diff --git a/instruction_hooking/src/lib.rs b/instruction_hooking/src/lib.rs index e60f1449..cbd0b4dd 100644 --- a/instruction_hooking/src/lib.rs +++ b/instruction_hooking/src/lib.rs @@ -1,25 +1,31 @@ pub mod disassemble_env; -use auxtools::*; +use auxtools::{ + byond_scan::{Anchor, Extract, Recipe, SignatureTreatment, VersionRange}, + * +}; use retour::RawDetour; use std::{any::Any, cell::UnsafeCell, ffi::c_void}; +// Where the interpreter reads the next opcode, inside exec_proc. Copied from +// `byond_catalog` in the byond-re repo, which checks it against every local +// build. +// +// The running proc's frame sits in EDI here and in ESI on Linux, so each +// platform has its own assembly in execute_instruction_hook.*. #[cfg(windows)] -signatures! { - execute_instruction => version_dependent_signature!( - 1616.. => "0F B7 47 ?? 8B 4F ?? 8B F0 8B 14 ?? 89 95 ?? ?? ?? ?? 81 FA ?? 01 00 00", - 1590..1616 => "0F B7 48 ?? 8B ?? ?? 8B F1 8B ?? ?? 81 ?? ?? ?? 00 00 0F 87 ?? ?? ?? ??", - ..1590 => "0F B7 48 ?? 8B 78 ?? 8B F1 8B 14 ?? 81 FA ?? ?? 00 00 0F 87 ?? ?? ?? ??" - ) -} - +const DISPATCH_MASK: &str = "0F B7 47 ?? 8B 4F ?? 8B F0 8B 14 ?? 89 95 ?? ?? ?? ?? 81 FA ?? 01 00 00"; #[cfg(unix)] -signatures! { - execute_instruction => version_dependent_signature!( - 1616.. => "0F B7 C0 8D 14 ?? 8B 02 8B 52 ?? 8B 4E ?? 8B 5E ?? 89 46 ?? 89 56 ?? 89 0C 24", - ..1616 => "0F B7 47 ?? 8B 57 ?? 0F B7 D8 8B 0C ?? 81 F9 ?? ?? 00 00 77 ?? FF 24 8D ?? ?? ?? ??" - ) -} +const DISPATCH_MASK: &str = "0F B7 46 ?? 8B 56 ?? 66 89 85 ?? ?? ?? ?? 89 85 ?? ?? ?? ?? C1 E0 02 89 85 ?? ?? ?? ?? 01 D0 8B 18 81 FB ?? 01 00 00 \ + 0F 87 ?? ?? ?? ?? FF 24 9D ?? ?? ?? ??"; + +const EXECUTE_INSTRUCTION: Recipe = Recipe { + name: "execute_instruction", + versions: VersionRange { min: 1659, max: 1688 }, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, DISPATCH_MASK), + hops: &[], + extract: Extract::Entry +}; // stackoverflow copypasta https://old.reddit.com/r/rust/comments/kkap4e/how_to_cast_a_boxdyn_mytrait_to_an_actual_struct/ pub trait InstructionHookToAny: 'static { @@ -42,8 +48,8 @@ extern "C" { // Trampoline to the original un-hooked BYOND execute_instruction code static mut execute_instruction_original: *const c_void; - // Our version of execute_instruction. It hasn't got a calling convention rust - // knows about, so don't call it. + // Our version of execute_instruction. It hasn't got a calling convention + // rust knows about, so don't call it. fn execute_instruction_hook(); // The 514 version of the instruction hook. @@ -53,11 +59,7 @@ extern "C" { #[init(full)] fn instruction_hooking_init() -> Result<(), String> { - let byondcore = sigscan::Scanner::for_module(BYONDCORE).unwrap(); - - find_signatures_result! { byondcore, - execute_instruction - } + let execute_instruction = find_recipe(&EXECUTE_INSTRUCTION)?; #[cfg(windows)] let versioned_hook = if auxtools::version::get().0 == 514 { From fed24b916b6a0c579971cd7ab2b8d2e81d1d9452 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sat, 3 Oct 2026 22:29:37 -0400 Subject: [PATCH 02/20] address review --- README.md | 2 +- auxtools/src/list.rs | 47 +++++++----------------------- auxtools/src/raw_types/funcs.cpp | 47 ++++++++++++++++++++++++------ auxtools/src/raw_types/funcs.rs | 3 ++ auxtools/src/symbols.rs | 5 ++++ auxtools/src/symbols/linux.rs | 27 ++++++++++++++++- auxtools/src/symbols/windows.rs | 15 ++++++++++ tests/auxtest/src/lists.rs | 16 ++++++++++ tests/auxtest_host/auxtest_host.dm | 26 +++++++++++++++++ tests/test_runner/src/paths.rs | 2 +- 10 files changed, 141 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 59cfcd45..5bba49c1 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Debug Server - Working with SpaceManiac's [SpacemanDMM](https://github.com/Space ## Supported BYOND versions -516.1659 through 516.1688, on Windows and Linux. Those are the builds the patterns that find BYOND's functions were checked against. A newer build is still tried, and fails at init with the name of whatever stopped matching. An older build, or another major version, is refused at init with an error that names the build. +516.1659 through 516.1688 on Windows, and 516.1664 through 516.1688 on Linux. Those are the builds the patterns that find BYOND's functions were checked against. A newer build is still tried, and fails at init with the name of whatever stopped matching. An older build, or another major version, is refused at init with an error that names the build. The patterns are run by [byond-scan](https://github.com/Absolucy/byond-scan) and live in `auxtools/src/symbols/`. They are copies of the ones in the byond-re repo's `byond_catalog`, which checks each against every local build, so a change to one has to land in both. diff --git a/auxtools/src/list.rs b/auxtools/src/list.rs index 9fdb3066..7ac1fbe3 100644 --- a/auxtools/src/list.rs +++ b/auxtools/src/list.rs @@ -97,43 +97,16 @@ impl List { self.len() == 0 } - pub const fn is_list(value: &Value) -> bool { - matches!( - value.raw.tag, - raw_types::values::ValueTag::List - | raw_types::values::ValueTag::ArgList - | raw_types::values::ValueTag::MobContents - | raw_types::values::ValueTag::TurfContents - | raw_types::values::ValueTag::AreaContents - | raw_types::values::ValueTag::WorldContents - | raw_types::values::ValueTag::ObjContents - | raw_types::values::ValueTag::MobVars - | raw_types::values::ValueTag::ObjVars - | raw_types::values::ValueTag::TurfVars - | raw_types::values::ValueTag::AreaVars - | raw_types::values::ValueTag::ClientVars - | raw_types::values::ValueTag::Vars - | raw_types::values::ValueTag::MobOverlays - | raw_types::values::ValueTag::MobUnderlays - | raw_types::values::ValueTag::ObjOverlays - | raw_types::values::ValueTag::ObjUnderlays - | raw_types::values::ValueTag::TurfOverlays - | raw_types::values::ValueTag::TurfUnderlays - | raw_types::values::ValueTag::AreaOverlays - | raw_types::values::ValueTag::AreaUnderlays - | raw_types::values::ValueTag::ImageOverlays - | raw_types::values::ValueTag::ImageUnderlays - | raw_types::values::ValueTag::ImageVars - | raw_types::values::ValueTag::TurfVisContents - | raw_types::values::ValueTag::ObjVisContents - | raw_types::values::ValueTag::MobVisContents - | raw_types::values::ValueTag::ImageVisContents - | raw_types::values::ValueTag::TurfVisLocs - | raw_types::values::ValueTag::ObjVisLocs - | raw_types::values::ValueTag::MobVisLocs - | raw_types::values::ValueTag::WorldVars - | raw_types::values::ValueTag::GlobalVars - ) + /// Whether DM's `islist()` says yes to this value. + /// + /// This asks BYOND instead of matching on the tag, because a tag can't + /// answer it: a `filters` list and a single filter share one. + pub fn is_list(value: &Value) -> bool { + let mut is_list: u8 = 0; + unsafe { + assert_eq!(raw_types::funcs::value_is_list(&mut is_list, value.raw), 1); + } + is_list != 0 } } diff --git a/auxtools/src/raw_types/funcs.cpp b/auxtools/src/raw_types/funcs.cpp index f2200090..90b8de89 100644 --- a/auxtools/src/raw_types/funcs.cpp +++ b/auxtools/src/raw_types/funcs.cpp @@ -66,9 +66,9 @@ struct RestoreJmpBuf // * A Value-returning regparm function spends `eax` on the hidden return // buffer, so its first real argument starts at `edx`. Count the buffer. // * The convention is per-function and per-build. `remove_from_list` changed -// from registers to the stack somewhere in (1669, 1685] with no change to -// its signature, while its identically-shaped sibling `append_to_list` did -// not move. Don't infer one row from another. +// from registers to the stack at 1674 with no change to its signature, +// while its identically-shaped sibling `append_to_list` did not move. Don't +// infer one row from another. extern "C" { DEFINE_byond(call_proc_by_id, Value, (Value, uint32_t, uint32_t, uint32_t, Value, const Value *, uint32_t, uint32_t, uint32_t)); @@ -85,12 +85,14 @@ extern "C" DEFINE_byond(set_assoc_element, void, (Value, Value, Value)); DEFINE_byond(create_list, uint32_t, (uint32_t)); DEFINE_byond_REGPARM2(append_to_list, void, (Value, Value)); - // Correct from 516.1685 on, and wrong below it. This is the one function known - // to have changed convention mid-range, so one declaration can't cover both - // eras. Running this on an older Linux build means putting REGPARM2 back, and - // re-reading the prologue first, because the exact build it flipped at was - // never bisected. + // The one function known to have changed convention mid-range, so it gets + // both declarations. Linux builds up to 516.1673 take the list in `eax:edx`, + // and 516.1674 on take everything on the stack. Rust sets the flag from the + // running build, and the `remove_from_list` wrapper below picks the matching + // pointer type. DEFINE_byond(remove_from_list, bool, (Value, Value)); + using Fnremove_from_list_regparm_byond = bool(LINUX_REGPARM2 *)(Value, Value); + bool remove_from_list_in_registers = false; // The one function here whose *return* differs by platform, not just its // argument placement. Windows hands the count straight back in `eax`. Linux // returns a DM Value through a caller-supplied buffer, so the count has to be @@ -100,6 +102,9 @@ extern "C" #else DEFINE_byond(get_length, Value, (Value)); #endif + // BYOND's own `islist()`. The last argument says whether a pointer to a list + // counts as a list. + DEFINE_byond(value_is_list, bool, (Value, uint8_t)); DEFINE_byond(get_misc_by_id, void *, (uint32_t)); DEFINE_byond(to_string, uint32_t, (Value)); } @@ -389,7 +394,14 @@ extern "C" uint8_t remove_from_list(Value list, Value value) { clean(list); clean(value); - remove_from_list_byond(list, value); + if (remove_from_list_in_registers) + { + reinterpret_cast(remove_from_list_byond)(list, value); + } + else + { + remove_from_list_byond(list, value); + } return 1; } BYOND_CATCH @@ -418,6 +430,23 @@ extern "C" uint8_t get_length(uint32_t *out, Value value) } } +extern "C" uint8_t value_is_list(uint8_t *out, Value value) +{ + RuntimeContext ctx(true); + + BYOND_TRY + { + clean(value); + // 0 is what the `islist()` opcode passes, so a pointer to a list is not a list + *out = value_is_list_byond(value, 0); + return 1; + } + BYOND_CATCH + { + return 0; + } +} + extern "C" uint8_t get_misc_by_id(void **out, uint32_t index) { RuntimeContext ctx(true); diff --git a/auxtools/src/raw_types/funcs.rs b/auxtools/src/raw_types/funcs.rs index 6f1d58e5..55bf21ca 100644 --- a/auxtools/src/raw_types/funcs.rs +++ b/auxtools/src/raw_types/funcs.rs @@ -27,7 +27,9 @@ extern "C" { pub static mut create_list_byond: *const c_void; pub static mut append_to_list_byond: *const c_void; pub static mut remove_from_list_byond: *const c_void; + pub static mut remove_from_list_in_registers: bool; pub static mut get_length_byond: *const c_void; + pub static mut value_is_list_byond: *const c_void; pub static mut get_misc_by_id_byond: *const c_void; pub static mut to_string_byond: *const c_void; pub static mut runtime_byond: *const c_void; @@ -71,6 +73,7 @@ extern "C" { pub fn append_to_list(list: values::Value, value: values::Value) -> u8; pub fn remove_from_list(list: values::Value, value: values::Value) -> u8; pub fn get_length(out: *mut u32, value: values::Value) -> u8; + pub fn value_is_list(out: *mut u8, value: values::Value) -> u8; pub fn get_misc_by_id(out: *mut *mut c_void, index: misc::MiscId) -> u8; pub fn to_string(out: *mut strings::StringId, value: values::Value) -> u8; } diff --git a/auxtools/src/symbols.rs b/auxtools/src/symbols.rs index ac8f07f5..3bff2dd2 100644 --- a/auxtools/src/symbols.rs +++ b/auxtools/src/symbols.rs @@ -87,7 +87,12 @@ pub(crate) fn resolve_full() -> Result<(), String> { funcs::create_list_byond = function("create_list")?; funcs::append_to_list_byond = function("append_to_list")?; funcs::remove_from_list_byond = function("remove_from_list")?; + #[cfg(unix)] + { + funcs::remove_from_list_in_registers = build.1 < linux::REMOVE_FROM_LIST_STACK_BUILD; + } funcs::get_length_byond = function("get_length")?; + funcs::value_is_list_byond = function("value_is_list")?; funcs::get_misc_by_id_byond = function("get_misc_by_id")?; funcs::to_string_byond = function("to_string")?; funcs::runtime_byond = function("runtime")?; diff --git a/auxtools/src/symbols/linux.rs b/auxtools/src/symbols/linux.rs index 8049c225..cd4a7a29 100644 --- a/auxtools/src/symbols/linux.rs +++ b/auxtools/src/symbols/linux.rs @@ -7,7 +7,17 @@ use byond_scan::{Anchor, Extract, OperandSelect, Recipe, SignatureTreatment, Ver // Checked on every build in this range. Older builds are refused by the // resolver, newer ones are tried and can still fail. -const SUPPORTED: VersionRange = VersionRange { min: 1659, max: 1688 }; +// +// The floor is 1664 and not Windows' 1659 because of proc hooks. On 1659 GCC +// inlined `call_proc_by_id` into the interpreter, so a DM proc call never enters +// the function the hook patches and no `#[hook]` ever fires. 1660 to 1663 were +// never checked. +const SUPPORTED: VersionRange = VersionRange { min: 1664, max: 1688 }; + +// The first build where `remove_from_list` takes its arguments on the stack. +// Every build before it wants the list in `eax:edx`. Read off the prologue of +// every build from 1669 to 1675. +pub(crate) const REMOVE_FROM_LIST_STACK_BUILD: u32 = 1674; // One loader mask finds the variable-name table. Operands that don't use a // register are count=0, global_values=1 and variable_names=2. @@ -204,6 +214,21 @@ pub(crate) const RECIPES: &[Recipe] = &[ hops: &[], extract: Extract::Entry }, + Recipe { + name: "value_is_list", + versions: SUPPORTED, + // this is the function the `islist()` opcode calls. The exported + // `ByondValue_IsList` is not used because on Windows 1659 it says no to a + // `filters` list, and both platforms should ask the same function. + // `80 FA 55` is `cmp dl, 55h`, the highest tag it accepts + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 89 E5 56 53 83 EC ?? F3 0F 7E 4D 08 8B 75 10 66 0F 7E CA 80 FA 55 0F 87 ?? ?? ?? ?? 80 FA 3B 76 ?? 8D 4A C4 B8 01 00 00 00 D3 E0 89 \ + C1" + ), + hops: &[], + extract: Extract::Entry + }, Recipe { name: "get_misc_by_id", versions: SUPPORTED, diff --git a/auxtools/src/symbols/windows.rs b/auxtools/src/symbols/windows.rs index 59e360e4..c2bfdfc9 100644 --- a/auxtools/src/symbols/windows.rs +++ b/auxtools/src/symbols/windows.rs @@ -180,6 +180,21 @@ pub(crate) const RECIPES: &[Recipe] = &[ hops: &[], extract: Extract::Entry }, + Recipe { + name: "value_is_list", + versions: SUPPORTED, + // this is the function the `islist()` opcode calls. The exported + // `ByondValue_IsList` is not used because on 1659 it says no to a `filters` + // list. The prologue is a common one, and the tag-range check at the end + // (`83 C0 F1 83 F8 46`) is what makes it unique + anchor: Anchor::Signature( + SignatureTreatment::NoOffset, + "55 8B EC 6A FF 68 ?? ?? ?? ?? 64 A1 00 00 00 00 50 83 EC ?? 53 56 57 A1 ?? ?? ?? ?? 33 C5 50 8D 45 F4 64 A3 00 00 00 00 89 65 ?? 8B 45 \ + 08 0F B6 C0 83 C0 F1 83 F8 46" + ), + hops: &[], + extract: Extract::Entry + }, Recipe { name: "get_misc_by_id", versions: SUPPORTED, diff --git a/tests/auxtest/src/lists.rs b/tests/auxtest/src/lists.rs index 4160a735..f48ae0f8 100644 --- a/tests/auxtest/src/lists.rs +++ b/tests/auxtest/src/lists.rs @@ -52,5 +52,21 @@ fn test_lists() { } } + // is_list should agree with DM's own islist() on everything, and len() with + // length() on everything that is a list + let samples = Proc::find("/proc/auxtest_islist_samples").unwrap().call(&[])?.as_list()?; + let dm_islist = Proc::find("/proc/auxtest_islist").unwrap(); + let dm_length = Proc::find("/proc/auxtest_length").unwrap(); + for n in 1..=samples.len() { + let sample = samples.get(n)?; + let expected = dm_islist.call(&[&sample])?.as_number()? != 0.0; + if List::is_list(&sample) != expected { + return Err(runtime!("test_lists: is_list disagrees with islist() on sample {}", n)); + } + if expected && sample.as_list()?.len() as f32 != dm_length.call(&[&sample])?.as_number()? { + return Err(runtime!("test_lists: len disagrees with length() on sample {}", n)); + } + } + Ok(Value::from(true)) } diff --git a/tests/auxtest_host/auxtest_host.dm b/tests/auxtest_host/auxtest_host.dm index e1b90976..2b4fd8ad 100644 --- a/tests/auxtest_host/auxtest_host.dm +++ b/tests/auxtest_host/auxtest_host.dm @@ -30,6 +30,32 @@ var/datum/weak_test_datum /proc/create_datum_for_weak() weak_test_datum = new +/mob/verb/auxtest_verb() + return + +// A spread of values for `List::is_list` to agree with `islist()` on: plain +// lists, the special lists that hang off an object, and things that are not +// lists at all. `M.filters[1]` is the mean one, since a single filter has the +// same tag as the filters list it came out of. +/proc/auxtest_islist_samples() + var/mob/M = new + M.filters += filter(type = "blur") + var/savefile/F = new + var/list/L = list(1, 2) + return list( + null, 1, "string", M, /list, &L, + L, alist("a" = 1), L.vars, args, + M.verbs, M.contents, M.vars, M.overlays, M.group, M.vis_contents, + M.filters, M.filters[1], + F.dir, world.contents, world.vars, global.vars, + ) + +/proc/auxtest_islist(value) + return islist(value) + +/proc/auxtest_length(value) + return length(value) + // Tests /proc/auxtest_lists() CRASH() diff --git a/tests/test_runner/src/paths.rs b/tests/test_runner/src/paths.rs index 00179f29..3b71a3ed 100644 --- a/tests/test_runner/src/paths.rs +++ b/tests/test_runner/src/paths.rs @@ -34,7 +34,7 @@ pub fn find_dreamdaemon() -> PathBuf { path.push("bin/DreamDaemon"); #[cfg(windows)] - path.push("bin/dreamdaemon.exe"); + path.push("bin/dd.exe"); assert!(path.is_file(), "couldn't find dreamdaemon"); path From ac6b9079e6e238a97d5e11d0b6cf9cb6cb83abf2 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sat, 3 Oct 2026 23:01:15 -0400 Subject: [PATCH 03/20] fix debug server crashes, allow re-attaching, add a live test --- Cargo.lock | 9 + Cargo.toml | 2 +- debug_server/src/instruction_hooking.rs | 71 ++++-- debug_server/src/lib.rs | 20 +- debug_server/src/server.rs | 292 +++++++++++++--------- tests/debug_test/Cargo.toml | 16 ++ tests/debug_test/src/client.rs | 98 ++++++++ tests/debug_test/src/main.rs | 219 ++++++++++++++++ tests/debug_test_host/debug_test_host.dm | 63 +++++ tests/debug_test_host/debug_test_host.dme | 18 ++ tests/test_runner/src/main.rs | 36 +-- tests/test_runner/src/paths.rs | 38 ++- 12 files changed, 702 insertions(+), 180 deletions(-) create mode 100644 tests/debug_test/Cargo.toml create mode 100644 tests/debug_test/src/client.rs create mode 100644 tests/debug_test/src/main.rs create mode 100644 tests/debug_test_host/debug_test_host.dm create mode 100644 tests/debug_test_host/debug_test_host.dme diff --git a/Cargo.lock b/Cargo.lock index c134ab5b..7cdbf4e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -661,6 +661,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "debug_test" +version = "0.1.0" +dependencies = [ + "bincode", + "debug_server", + "serde", +] + [[package]] name = "debugid" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 98fc9a43..a32db037 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["auxcov", "auxtools", "auxtools-impl", "debug_server", "instruction_hooking", "tests/auxtest", "tests/byond_get", "tests/test_runner"] +members = ["auxcov", "auxtools", "auxtools-impl", "debug_server", "instruction_hooking", "tests/auxtest", "tests/byond_get", "tests/debug_test", "tests/test_runner"] resolver = "2" [workspace.package] diff --git a/debug_server/src/instruction_hooking.rs b/debug_server/src/instruction_hooking.rs index e1834ed0..319daab3 100644 --- a/debug_server/src/instruction_hooking.rs +++ b/debug_server/src/instruction_hooking.rs @@ -34,7 +34,8 @@ impl ProcInstanceRef { fn new(ptr: *mut raw_types::procs::ProcInstance) -> Self { unsafe { static mut PTR_REF_ID: u16 = 0x8000; - PTR_REF_ID += 1; + // Every step takes a new id, so a long session does run out of them + PTR_REF_ID = if PTR_REF_ID == u16::MAX { 0x8001 } else { PTR_REF_ID + 1 }; (*ptr).mega_hack = PTR_REF_ID; Self(PTR_REF_ID) } @@ -185,7 +186,9 @@ fn proc_instance_is_suspended(proc_ref: ProcInstanceRef) -> bool { #[runtime_handler] fn handle_runtime(error: &str) { - unsafe { + // BYOND calls this through extern "C" code, and a panic that gets back out + // there aborts the whole game + let result = std::panic::catch_unwind(|| unsafe { let ctx = *raw_types::funcs::CURRENT_EXECUTION_CONTEXT; // If this is eval code, don't catch the breakpoint @@ -198,6 +201,12 @@ fn handle_runtime(error: &str) { } CURRENT_ACTION = handle_breakpoint(ctx, BreakpointReason::Runtime(error.to_string())); + }); + + if result.is_err() { + if let Some(server) = unsafe { &mut *DEBUG_SERVER.get() } { + server.recover_from_panic(); + } } } @@ -213,8 +222,14 @@ impl InstructionHook for Server { } } + // An expression the debugger is evaluating runs through here too. + // Pausing in the middle of it would start a second pause inside the + // one that's waiting on the answer, so all it gets is its breakpoints + // stepped over. let server = self; - if server.process() { + let in_eval = server.is_in_eval(); + + if !in_eval && server.process() { unsafe { CURRENT_ACTION = DebuggerAction::Pause; } @@ -229,7 +244,9 @@ impl InstructionHook for Server { let mut did_breakpoint = false; unsafe { - match CURRENT_ACTION { + let action = if in_eval { DebuggerAction::None } else { CURRENT_ACTION }; + + match action { DebuggerAction::None => {} DebuggerAction::Pause => { @@ -311,7 +328,7 @@ impl InstructionHook for Server { if opcode == OPCODE_DEBUG_BREAK { // We don't want to break twice when stepping on to a breakpoint - if !did_breakpoint { + if !did_breakpoint && !in_eval { unsafe { CURRENT_ACTION = DebuggerAction::None; CURRENT_ACTION = handle_breakpoint(ctx, BreakpointReason::Breakpoint); @@ -323,8 +340,13 @@ impl InstructionHook for Server { let map = ORIGINAL_BYTECODE.lock().unwrap(); if let Some(original) = map.get(&PtrKey::new(opcode_ptr)) { unsafe { + // The breakpoint we stepped over last is normally back in + // place by now. It isn't if an eval ended on that very + // instruction, so put it back before we lose track of it. let deferred_replace = DEFERRED_INSTRUCTION_REPLACE.get(); - assert_eq!(*deferred_replace, None); + if let Some((src, dst)) = &*deferred_replace { + std::ptr::copy_nonoverlapping(src.as_ptr(), *dst, src.len()); + } *deferred_replace = Some((std::slice::from_raw_parts(opcode_ptr, original.len()).to_vec(), opcode_ptr)); std::ptr::copy_nonoverlapping(original.as_ptr(), opcode_ptr, original.len()); } @@ -430,21 +452,36 @@ pub fn unhook_instruction(proc: &Proc, offset: u32) -> Result<(), InstructionUnh Ok(()) } -pub fn get_hooked_offsets(proc: &Proc) -> Vec { - let bytecode = unsafe { proc.bytecode() }; +/// Takes every breakpoint back out of the bytecode and forgets any step in +/// progress. +pub fn unhook_all() { + unsafe { + CURRENT_ACTION = DebuggerAction::None; + *DEFERRED_INSTRUCTION_REPLACE.get() = None; + } - let mut env = disassemble_env::DisassembleEnv; - let (nodes, _error) = dmasm::disassembler::disassemble(bytecode, &mut env); + for (opcode_ptr, original) in ORIGINAL_BYTECODE.lock().unwrap().drain() { + unsafe { + std::ptr::copy_nonoverlapping(original.as_ptr(), opcode_ptr.0 as *mut u32, original.len()); + } + } +} - let mut offsets = vec![]; +/// The proc's bytecode without any of our breakpoints patched into it. +pub fn original_bytecode(proc: &Proc) -> Vec { + let (start, count) = unsafe { proc.bytecode_mut_ptr() }; + let mut bytecode = unsafe { std::slice::from_raw_parts(start, count as usize) }.to_vec(); - for node in nodes { - if let dmasm::Node::Instruction(ins, debug) = node { - if ins == dmasm::Instruction::AuxtoolsDebugBreak { - offsets.push(debug.offset); - } + for (opcode_ptr, original) in ORIGINAL_BYTECODE.lock().unwrap().iter() { + // The map holds every proc's breakpoints, and most aren't in this one + let Some(offset) = opcode_ptr.0.checked_sub(start as usize).map(|bytes| bytes / std::mem::size_of::()) else { + continue; + }; + + if let Some(hooked) = bytecode.get_mut(offset..offset + original.len()) { + hooked.copy_from_slice(original); } } - offsets + bytecode } diff --git a/debug_server/src/lib.rs b/debug_server/src/lib.rs index 4047d11a..bf1522da 100644 --- a/debug_server/src/lib.rs +++ b/debug_server/src/lib.rs @@ -16,7 +16,8 @@ mod mem_profiler_stub; use std::{ cell::UnsafeCell, - net::{IpAddr, Ipv4Addr, SocketAddr} + net::{IpAddr, Ipv4Addr, SocketAddr}, + panic::{catch_unwind, AssertUnwindSafe} }; pub(crate) use ::instruction_hooking::disassemble_env::DisassembleEnv; @@ -56,8 +57,15 @@ struct DebugServerInstructionHook<'a> { impl InstructionHook for DebugServerInstructionHook<'static> { fn handle_instruction(&mut self, ctx: *mut raw_types::procs::ExecutionContext) { - if let Some(debug_server) = self.debug_server.get_mut() { - debug_server.handle_instruction(ctx); + let Some(debug_server) = self.debug_server.get_mut() else { + return; + }; + + // BYOND calls this through extern "C" code, and a panic that gets back + // out there aborts the whole game. Losing the debugger is the better + // deal. + if catch_unwind(AssertUnwindSafe(|| debug_server.handle_instruction(ctx))).is_err() { + debug_server.recover_from_panic(); } } } @@ -69,6 +77,12 @@ fn enable_debugging(mode: Value, port: Value) { let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port); + // A second server would mean a second instruction hook, and both hooks + // would run the one server on every instruction + if mode != "NONE" && unsafe { (*DEBUG_SERVER.get()).is_some() } { + return Err(runtime!("debugging is already enabled")); + } + let server = match mode.as_str() { "NONE" => { return Ok(Value::NULL); diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index 41883e34..22f548fb 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -4,7 +4,10 @@ use std::{ error::Error, io::{Read, Write}, net::{SocketAddr, TcpListener, TcpStream}, - sync::mpsc, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc, Arc + }, thread, thread::JoinHandle }; @@ -17,7 +20,7 @@ use clap::{Arg, Command}; use instruction_hooking::disassemble_env; use super::{ - instruction_hooking::{get_hooked_offsets, hook_instruction, unhook_instruction}, + instruction_hooking::{hook_instruction, original_bytecode, unhook_all, unhook_instruction}, server_types::* }; use crate::mem_profiler; @@ -70,28 +73,34 @@ impl State { // Server = main-thread code // ServerThread = networking-thread code // -// We've got a couple of channels going on between Server/ServerThread -// connection: a TcpStream sent from the ServerThread for the Server to send -// responses on requests: requests from the debug-client for the Server to -// handle +// The ServerThread tells the Server everything through one channel of Events, +// so they arrive in the order they happened. // -// Limitations: only ever accepts one connection +// Limitations: one debug-client at a time. Another can connect once it leaves. // -enum ServerStream { - // The server is waiting for a Stream to be sent on the connection channel - Waiting(mpsc::Receiver), - +enum Event { + // A debug-client arrived. The TcpStream is for the Server to send responses on Connected(TcpStream), - // The server has finished being used + // A request from the debug-client for the Server to handle + Request(Request), + + // The debug-client left, or its connection broke Disconnected } +// Nothing a debug-client sends is anywhere near this big. Without a limit, any +// stray bytes on the port get read as a length and we try to allocate it. +const MAX_REQUEST_LEN: u32 = 1024 * 1024; + pub struct Server { - requests: mpsc::Receiver, - stream: ServerStream, - _thread: JoinHandle<()>, + events: mpsc::Receiver, + stream: Option, + thread: Option>, + // Where our listener is bound, when we have one + listen_addr: Option, + stop: Arc, should_catch_runtimes: bool, state: Option, in_eval: bool, @@ -101,7 +110,7 @@ pub struct Server { } struct ServerThread { - requests: mpsc::Sender + events: mpsc::Sender } impl Server { @@ -149,19 +158,21 @@ impl Server { pub fn connect(addr: &SocketAddr) -> std::io::Result { let stream = TcpStream::connect_timeout(addr, std::time::Duration::from_secs(5))?; - let (requests_sender, requests_receiver) = mpsc::channel(); + let (events_sender, events_receiver) = mpsc::channel(); - let server_thread = ServerThread { requests: requests_sender }; + let server_thread = ServerThread { events: events_sender }; - let cloned_stream = stream.try_clone().unwrap(); + let cloned_stream = stream.try_clone()?; let thread = thread::spawn(move || { server_thread.run(cloned_stream); }); let mut server = Server { - requests: requests_receiver, - stream: ServerStream::Connected(stream), - _thread: thread, + events: events_receiver, + stream: Some(stream), + thread: Some(thread), + listen_addr: None, + stop: Arc::new(AtomicBool::new(false)), should_catch_runtimes: true, state: None, in_eval: false, @@ -175,15 +186,17 @@ impl Server { } pub fn listen(addr: &SocketAddr) -> std::io::Result { - let (connection_sender, connection_receiver) = mpsc::channel(); - let (requests_sender, requests_receiver) = mpsc::channel(); + let (events_sender, events_receiver) = mpsc::channel(); + let stop = Arc::new(AtomicBool::new(false)); - let thread = ServerThread { requests: requests_sender }.spawn_listener(TcpListener::bind(addr)?, connection_sender); + let thread = ServerThread { events: events_sender }.spawn_listener(TcpListener::bind(addr)?, stop.clone()); Ok(Server { - requests: requests_receiver, - stream: ServerStream::Waiting(connection_receiver), - _thread: thread, + events: events_receiver, + stream: None, + thread: Some(thread), + listen_addr: Some(*addr), + stop, should_catch_runtimes: true, state: None, in_eval: false, @@ -502,9 +515,13 @@ impl Server { match hook_instruction(&proc, instruction.offset) { Ok(()) => { - if let Some(condition) = condition { - self.conditional_breakpoints.insert((proc.id, instruction.offset as u16), condition); - } + // The client re-sends breakpoints it already set, and no + // condition this time means the old one is gone + let key = (proc.id, instruction.offset as u16); + match condition { + Some(condition) => self.conditional_breakpoints.insert(key, condition), + None => self.conditional_breakpoints.remove(&key) + }; self.send_or_disconnect(Response::BreakpointSet { result: BreakpointSetResult::Success { line } @@ -523,9 +540,7 @@ impl Server { let proc = match auxtools::Proc::find_override(instruction.proc.path, instruction.proc.override_id) { Some(proc) => proc, None => { - self.send_or_disconnect(Response::BreakpointSet { - result: BreakpointSetResult::Failed - }); + self.send_or_disconnect(Response::BreakpointUnset { success: false }); return; } }; @@ -929,23 +944,14 @@ impl Server { fn handle_disassemble(&mut self, path: &str, id: u32) -> String { match auxtools::Proc::find_override(path, id) { Some(proc) => { - // Make sure to temporarily remove all breakpoints in this proc - let breaks = get_hooked_offsets(&proc); - - for offset in &breaks { - unhook_instruction(&proc, *offset).unwrap(); - } - - let bytecode = unsafe { proc.bytecode() }; + // The live bytecode has our breakpoints patched into it, which + // nobody wants to read + let bytecode = original_bytecode(&proc); let mut env = crate::DisassembleEnv; - let (nodes, error) = dmasm::disassembler::disassemble(bytecode, &mut env); + let (nodes, error) = dmasm::disassembler::disassemble(&bytecode, &mut env); let dism = dmasm::format_disassembly(&nodes, None); - for offset in &breaks { - hook_instruction(&proc, *offset).unwrap(); - } - match error { Some(error) => { format!("Dism for {:?}\n{}\n\tError: {:?}", proc, dism, error) @@ -1022,25 +1028,28 @@ impl Server { false } - fn check_connected(&mut self) -> bool { - match &self.stream { - ServerStream::Disconnected => false, - ServerStream::Connected(_) => true, - ServerStream::Waiting(receiver) => { - if let Ok(stream) = receiver.try_recv() { - self.stream = ServerStream::Connected(stream); - true - } else { - false + /// Applies whatever the networking thread has reported and hands back the + /// next request. `None` means nothing is waiting, or when blocking, that + /// the debug-client is gone. + fn recv_request(&mut self, block: bool) -> Option { + loop { + let event = if block { self.events.recv().ok()? } else { self.events.try_recv().ok()? }; + + match event { + Event::Connected(stream) => self.stream = Some(stream), + + Event::Disconnected => { + self.disconnect(); + + if block { + return None; + } } - } - } - } - fn wait_for_connection(&mut self) { - if let ServerStream::Waiting(receiver) = &self.stream { - if let Ok(stream) = receiver.recv() { - self.stream = ServerStream::Connected(stream); + // Sent by a client we've since given up on + Event::Request(_) if self.stream.is_none() => {} + + Event::Request(request) => return Some(request) } } } @@ -1049,16 +1058,20 @@ impl Server { let message = message.into(); eprintln!("Debug Server: {:?}", message); - if !self.check_connected() { - return; - } - self.send_or_disconnect(Response::Notification { message }); } + /// Something in here panicked. We don't know what state that left the + /// session in, so the debug-client gets dropped and can connect again. + pub fn recover_from_panic(&mut self) { + eprintln!("Debug server panicked, dropping the debug client"); + self.in_eval = false; + self.disconnect(); + } + pub fn handle_breakpoint(&mut self, _ctx: *mut raw_types::procs::ExecutionContext, reason: BreakpointReason) -> ContinueKind { // Ignore all breakpoints unless we're connected - if !self.check_connected() || (matches!(reason, BreakpointReason::Runtime(_)) && !self.should_catch_runtimes) { + if self.stream.is_none() || (matches!(reason, BreakpointReason::Runtime(_)) && !self.should_catch_runtimes) { return ContinueKind::Continue; } @@ -1087,7 +1100,13 @@ impl Server { self.notify(format!("Pausing execution (reason: {:?})", reason)); self.send_or_disconnect(Response::BreakpointHit { reason }); - while let Ok(request) = self.requests.recv() { + // A failed send drops the client without an event to tell us so, hence + // checking the stream as well + while self.stream.is_some() { + let Some(request) = self.recv_request(true) else { + break; + }; + // Hijack and handle any Continue requests if let Request::Continue { kind } = request { self.send_or_disconnect(Response::Ack); @@ -1113,15 +1132,11 @@ impl Server { // returns true if we need to pause pub fn process(&mut self) -> bool { - // Don't do anything until we're connected - if !self.check_connected() { - return false; - } - let mut should_pause = false; - while let Ok(request) = self.requests.try_recv() { - should_pause = should_pause || self.handle_request(request); + while let Some(request) = self.recv_request(false) { + // Not `||`, that would skip every request after a Pause + should_pause |= self.handle_request(request); } should_pause @@ -1130,9 +1145,7 @@ impl Server { /// Block while processing all received requests normally until the debug /// client is configured pub fn process_until_configured(&mut self) { - self.wait_for_connection(); - - while let Ok(request) = self.requests.recv() { + while let Some(request) = self.recv_request(true) { if let Request::Configured = request { self.send_or_disconnect(Response::Ack); break; @@ -1143,87 +1156,111 @@ impl Server { } fn send_or_disconnect(&mut self, response: Response) { - match self.stream { - ServerStream::Connected(_) => match self.send(response) { - Ok(_) => {} - Err(e) => { - eprintln!("Debug server failed to send message: {}", e); - self.disconnect(); - } - }, + // Nobody to tell. An earlier send on this connection failed, or the + // client left while we were busy. + let Some(stream) = &mut self.stream else { + return; + }; - ServerStream::Waiting(_) | ServerStream::Disconnected => { - unreachable!("Debug Server is not connected") - } + if let Err(e) = Self::send(stream, &response) { + eprintln!("Debug server failed to send message: {}", e); + self.disconnect(); } } fn disconnect(&mut self) { - if let ServerStream::Connected(stream) = &mut self.stream { + if let Some(mut stream) = self.stream.take() { eprintln!("Debug server disconnecting"); - let data = bincode::serialize(&Response::Disconnect).unwrap(); - let _ = stream.write_all(&(data.len() as u32).to_le_bytes()); - let _ = stream.write_all(&data[..]); - let _ = stream.flush(); + let _ = Self::send(&mut stream, &Response::Disconnect); let _ = stream.shutdown(std::net::Shutdown::Both); } - self.stream = ServerStream::Disconnected; + // Whoever connects next sets up their own breakpoints. Until then the + // old ones would only slow down the procs they sit in. + self.conditional_breakpoints.clear(); + self.should_catch_runtimes = true; + unhook_all(); } - fn send(&mut self, response: Response) -> Result<(), Box> { - if let ServerStream::Connected(stream) = &mut self.stream { - let data = bincode::serialize(&response)?; - stream.write_all(&(data.len() as u32).to_le_bytes())?; - stream.write_all(&data[..])?; - stream.flush()?; - return Ok(()); - } - - unreachable!(); + fn send(stream: &mut TcpStream, response: &Response) -> Result<(), Box> { + let data = bincode::serialize(response)?; + stream.write_all(&(data.len() as u32).to_le_bytes())?; + stream.write_all(&data[..])?; + stream.flush()?; + Ok(()) } } impl Drop for Server { fn drop(&mut self) { self.disconnect(); + + let Some(listen_addr) = self.listen_addr else { + return; + }; + + // The listener thread sits in accept() holding the port, and after a + // world reboot the next Server wants that port. Connecting is the only + // way to get it out of accept() so it can see the stop flag. + self.stop.store(true, Ordering::Relaxed); + if TcpStream::connect(listen_addr).is_err() { + return; + } + + if let Some(thread) = self.thread.take() { + let _ = thread.join(); + } } } impl ServerThread { - fn spawn_listener(self, listener: TcpListener, connection_sender: mpsc::Sender) -> JoinHandle<()> { - thread::spawn(move || match listener.accept() { - Ok((stream, _)) => { - match connection_sender.send(stream.try_clone().unwrap()) { - Ok(_) => {} - Err(e) => { - eprintln!("Debug server thread failed to pass cloned TcpStream: {}", e); - return; - } + fn spawn_listener(self, listener: TcpListener, stop: Arc) -> JoinHandle<()> { + thread::spawn(move || loop { + let stream = match listener.accept() { + Ok((stream, _)) => stream, + + Err(e) => { + eprintln!("Debug server failed to accept connection: {}", e); + return; } + }; - self.run(stream); + // That was Server::drop knocking, not a debug-client + if stop.load(Ordering::Relaxed) { + return; } - Err(e) => { - eprintln!("Debug server failed to accept connection: {}", e); + let cloned_stream = match stream.try_clone() { + Ok(cloned_stream) => cloned_stream, + + Err(e) => { + eprintln!("Debug server thread failed to clone TcpStream: {}", e); + continue; + } + }; + + if let Err(e) = self.events.send(Event::Connected(cloned_stream)) { + eprintln!("Debug server thread failed to pass cloned TcpStream: {}", e); + return; } + + self.run(stream); }) } // returns true if we should disconnect - fn handle_request(&mut self, data: &[u8]) -> Result> { + fn handle_request(&self, data: &[u8]) -> Result> { let request = bincode::deserialize::(data)?; if let Request::Disconnect = request { return Ok(true); } - self.requests.send(request)?; + self.events.send(Event::Request(request))?; Ok(false) } - fn run(mut self, mut stream: TcpStream) { + fn run(&self, mut stream: TcpStream) { let mut buf = vec![]; // The incoming stream is a u32 followed by a bincode-encoded Request. @@ -1238,6 +1275,11 @@ impl ServerThread { } }; + if len > MAX_REQUEST_LEN { + eprintln!("Debug server thread got a {} byte request, which is too big to be real", len); + break; + } + buf.resize(len as usize, 0); match stream.read_exact(&mut buf) { Ok(_) => (), @@ -1263,6 +1305,10 @@ impl ServerThread { } } - eprintln!("Debug server thread finished"); + // We can stop reading for reasons the other end doesn't know about + let _ = stream.shutdown(std::net::Shutdown::Both); + let _ = self.events.send(Event::Disconnected); + + eprintln!("Debug client connection closed"); } } diff --git a/tests/debug_test/Cargo.toml b/tests/debug_test/Cargo.toml new file mode 100644 index 00000000..0a0352e8 --- /dev/null +++ b/tests/debug_test/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "debug_test" +version = "0.1.0" +publish = false +authors.workspace = true +edition.workspace = true + +# Same trick as test_runner: depending on the cdylib gets debug_server +# compiled before `cargo run` runs us. +[dependencies] +debug_server = { path = "../../debug_server" } +serde = { version = "1", features = ["derive"] } +bincode = "1" + +[lints] +workspace = true diff --git a/tests/debug_test/src/client.rs b/tests/debug_test/src/client.rs new file mode 100644 index 00000000..ccfd0f7b --- /dev/null +++ b/tests/debug_test/src/client.rs @@ -0,0 +1,98 @@ +use std::{ + io::{Read, Write}, + net::{Ipv4Addr, TcpStream}, + time::{Duration, Instant} +}; + +use crate::server_types::{BreakpointReason, Request, Response}; + +// Long enough for a world reboot, short enough that a dead world fails the run +// instead of hanging it +const TIMEOUT: Duration = Duration::from_secs(30); + +/// Stands in for dm-langserver: speaks the debug server's protocol over TCP. +pub struct Client { + stream: TcpStream +} + +impl Client { + /// Keeps trying until the world is listening. + pub fn connect(port: u16) -> Self { + let start = Instant::now(); + + loop { + match TcpStream::connect((Ipv4Addr::LOCALHOST, port)) { + Ok(stream) => { + stream.set_read_timeout(Some(TIMEOUT)).unwrap(); + return Self { stream }; + } + + Err(e) => { + assert!(start.elapsed() < TIMEOUT, "couldn't connect to the debug server: {}", e); + std::thread::sleep(Duration::from_millis(100)); + } + } + } + } + + pub fn send(&mut self, request: Request) { + let data = bincode::serialize(&request).unwrap(); + self.stream.write_all(&(data.len() as u32).to_le_bytes()).unwrap(); + self.stream.write_all(&data).unwrap(); + } + + fn read_message(&mut self) -> std::io::Result { + let mut len_bytes = [0u8; 4]; + self.stream.read_exact(&mut len_bytes)?; + + let mut data = vec![0; u32::from_le_bytes(len_bytes) as usize]; + self.stream.read_exact(&mut data)?; + + Ok(bincode::deserialize(&data).unwrap()) + } + + /// The next message that isn't a notification. + pub fn recv(&mut self) -> Response { + loop { + match self.read_message().expect("debug server stopped answering") { + Response::Notification { message } => println!(" [debug server] {}", message), + response => return response + } + } + } + + pub fn request(&mut self, request: Request) -> Response { + self.send(request); + self.recv() + } + + /// Waits for the world to pause and says why it did. + pub fn expect_pause(&mut self) -> BreakpointReason { + match self.recv() { + Response::BreakpointHit { reason } => reason, + other => panic!("expected the world to pause, got {:?}", other) + } + } + + pub fn resume(&mut self) { + let response = self.request(Request::Continue { + kind: crate::server_types::ContinueKind::Continue + }); + assert!(matches!(response, Response::Ack), "expected Ack for Continue, got {:?}", response); + } + + /// Waits for the server to hang up on us. + pub fn wait_closed(mut self) { + loop { + match self.read_message() { + Ok(Response::Disconnect) => return, + Ok(_) => {} + Err(e) => { + let timed_out = matches!(e.kind(), std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock); + assert!(!timed_out, "debug server never hung up"); + return; + } + } + } + } +} diff --git a/tests/debug_test/src/main.rs b/tests/debug_test/src/main.rs new file mode 100644 index 00000000..4ab6e7db --- /dev/null +++ b/tests/debug_test/src/main.rs @@ -0,0 +1,219 @@ +//! Runs a tiny world with the debug server loaded and pokes it the way +//! dm-langserver would, to make sure the debugger can't take the game down. + +mod client; + +// test_runner already knows how to find BYOND, and the debug server's message +// types aren't exported from its cdylib. Both are pulled in as source, and +// each has bits this test never touches. +#[allow(dead_code)] +#[path = "../../test_runner/src/paths.rs"] +mod paths; +#[allow(dead_code)] +#[path = "../../../debug_server/src/server_types.rs"] +mod server_types; + +use std::{ + net::{Ipv4Addr, TcpListener}, + path::PathBuf, + process::{Child, Command}, + time::Duration +}; + +use client::Client; +use paths::ByondCommand; +use server_types::{BreakpointReason, BreakpointSetResult, InstructionRef, ProcRef, Request, Response}; + +// The debug server names global procs without the /proc part +const BREAKPOINT_TARGET: &str = "/breakpoint_target"; +const TICK_MARKER: &str = "/tick_marker"; + +struct World(Child); + +impl Drop for World { + fn drop(&mut self) { + let _ = self.0.kill(); + let _ = self.0.wait(); + } +} + +fn find_debug_server() -> PathBuf { + let mut path = std::env::current_exe().unwrap(); + path.pop(); + + #[cfg(unix)] + path.push("libdebug_server.so"); + + #[cfg(windows)] + path.push("debug_server.dll"); + + assert!(path.is_file(), "couldn't find debug_server"); + path +} + +fn host_file(name: &str) -> PathBuf { + let mut path = std::env::current_dir().unwrap(); + path.push("tests/debug_test_host"); + path.push(name); + path +} + +fn free_port() -> u16 { + TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap().local_addr().unwrap().port() +} + +fn start_of(proc: &str) -> InstructionRef { + InstructionRef { + proc: ProcRef { + path: proc.to_owned(), + override_id: 0 + }, + offset: 0 + } +} + +fn set_breakpoint(client: &mut Client, proc: &str, condition: Option<&str>) { + let response = client.request(Request::BreakpointSet { + instruction: start_of(proc), + condition: condition.map(str::to_owned) + }); + + assert!( + matches!(response, Response::BreakpointSet { + result: BreakpointSetResult::Success { .. } + }), + "couldn't set a breakpoint on {}: {:?}", + proc, + response + ); +} + +fn eval(client: &mut Client, frame_id: Option, command: &str) -> String { + let response = client.request(Request::Eval { + frame_id, + command: command.to_owned(), + context: None + }); + + match response { + Response::Eval(eval) => eval.value, + other => panic!("expected the result of {:?}, got {:?}", command, other) + } +} + +/// The proc the world is paused in. +fn paused_in(client: &mut Client) -> String { + match client.request(Request::Stacks) { + Response::Stacks { stacks } => stacks.into_iter().next().expect("paused with no stacks").name, + other => panic!("expected Stacks, got {:?}", other) + } +} + +fn expect_breakpoint_in(client: &mut Client, proc: &str) { + assert_eq!(client.expect_pause(), BreakpointReason::Breakpoint); + assert_eq!(paused_in(client), proc); +} + +fn pause(client: &mut Client) { + let response = client.request(Request::Pause); + assert!(matches!(response, Response::Ack), "expected Ack for Pause, got {:?}", response); + assert_eq!(client.expect_pause(), BreakpointReason::Pause); +} + +fn main() { + let res = Command::new(paths::find_dm()) + .with_byond_paths() + .arg(host_file("debug_test_host.dme")) + .status() + .unwrap(); + assert!(res.success(), "dreammaker build failed"); + + let port = free_port(); + + let _world = World( + Command::new(paths::find_dreamdaemon()) + .with_byond_paths() + .env("AUXTOOLS_DEBUG_DLL", find_debug_server()) + .env("AUXTOOLS_DEBUG_PORT", port.to_string()) + .arg(host_file("debug_test_host.dmb")) + .arg("-trusted") + .arg("-close") + .spawn() + .unwrap() + ); + + let dis_command = format!("#dis {}", BREAKPOINT_TARGET); + + let mut client = Client::connect(port); + let clean_disassembly = eval(&mut client, None, &dis_command); + set_breakpoint(&mut client, BREAKPOINT_TARGET, None); + expect_breakpoint_in(&mut client, BREAKPOINT_TARGET); + println!("ok: breakpoint hit"); + + // VS Code re-runs every watch expression on every stop, so this is the + // easy one to hit by accident + assert_eq!(eval(&mut client, Some(0), "breakpoint_target()"), "2"); + println!("ok: evaluating a proc that has a breakpoint in it"); + + assert_eq!(eval(&mut client, None, &dis_command), clean_disassembly); + println!("ok: disassembly doesn't show the breakpoint"); + + let response = client.request(Request::BreakpointUnset { + instruction: start_of("/proc/does_not_exist") + }); + assert!( + matches!(response, Response::BreakpointUnset { success: false }), + "unsetting a breakpoint on a missing proc got {:?}", + response + ); + println!("ok: unsetting a breakpoint on a missing proc"); + + // The condition is false, and working that out means calling a proc that + // has its own breakpoint. The second stop in tick_marker is the one that + // shows breakpoint_target got skipped. + set_breakpoint(&mut client, TICK_MARKER, None); + set_breakpoint(&mut client, BREAKPOINT_TARGET, Some("tick_marker() == 0")); + client.resume(); + expect_breakpoint_in(&mut client, TICK_MARKER); + client.resume(); + expect_breakpoint_in(&mut client, TICK_MARKER); + println!("ok: conditional breakpoint that calls a proc with a breakpoint"); + + // dm-langserver re-sends every breakpoint without unsetting it first, so + // this is how a condition gets removed + set_breakpoint(&mut client, BREAKPOINT_TARGET, None); + client.resume(); + expect_breakpoint_in(&mut client, BREAKPOINT_TARGET); + println!("ok: re-setting a breakpoint drops its old condition"); + + eval(&mut client, None, "start_runtimes()"); + client.resume(); + drop(client); + // start_runtimes() gives two seconds of runtime errors, wait them out + std::thread::sleep(Duration::from_secs(3)); + + let mut client = Client::connect(port); + pause(&mut client); + println!("ok: survived runtime errors with the debugger gone, and it can attach again"); + + client.resume(); + client.send(Request::Disconnect); + drop(client); + + let mut client = Client::connect(port); + let response = client.request(Request::Stacks); + assert!(matches!(response, Response::Stacks { .. }), "expected Stacks, got {:?}", response); + println!("ok: attaching again after a clean detach"); + + eval(&mut client, None, "request_reboot()"); + client.wait_closed(); + // The old world hangs up on us a moment before it stops listening. Don't + // get picked up by its last accept(). + std::thread::sleep(Duration::from_millis(500)); + + let mut client = Client::connect(port); + pause(&mut client); + println!("ok: attaching again after a world reboot"); + + println!("Debug server tests succeeded"); +} diff --git a/tests/debug_test_host/debug_test_host.dm b/tests/debug_test_host/debug_test_host.dm new file mode 100644 index 00000000..3fab357a --- /dev/null +++ b/tests/debug_test_host/debug_test_host.dm @@ -0,0 +1,63 @@ +#if DM_VERSION < 515 +#define call_ext call +#endif + +/proc/auxtools_stack_trace(msg) + CRASH(msg) + +/proc/auxtools_expr_stub() + CRASH("auxtools not loaded") + +/proc/enable_debugging(mode, port) + CRASH("auxtools not loaded") + +var/runtimes_left = 0 +var/reboot_requested = FALSE + +// The test puts its breakpoints on these two. They are called in this order, +// once per tick, so the test can tell which breakpoint it stopped on. +/proc/breakpoint_target() + return 2 + +/proc/tick_marker() + return 1 + +// Two seconds of runtime errors, then quiet again. The test detaches while +// they are going and comes back after they stop. +/proc/start_runtimes() + runtimes_left = 20 + +/proc/make_runtime() + var/list/nothing + return nothing.len + +/proc/request_reboot() + reboot_requested = TRUE + +/proc/main_loop(debug_dll) + while (!reboot_requested) + breakpoint_target() + tick_marker() + if (runtimes_left > 0) + runtimes_left-- + spawn() + make_runtime() + sleep(1) + + // Shutting down has to happen out here. The debugger evaluates + // request_reboot() from inside the debug server, which can't free itself. + call_ext(debug_dll, "auxtools_shutdown")() + world.Reboot() + +/world/New() + var/debug_dll = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") + var/init_result = call_ext(debug_dll, "auxtools_init")() + world.log << "init_result = [init_result]" + ASSERT(init_result == "SUCCESS") + + // No port given, so it comes from AUXTOOLS_DEBUG_PORT + enable_debugging("BACKGROUND") + + spawn() + main_loop(debug_dll) + . = ..() diff --git a/tests/debug_test_host/debug_test_host.dme b/tests/debug_test_host/debug_test_host.dme new file mode 100644 index 00000000..1cd91340 --- /dev/null +++ b/tests/debug_test_host/debug_test_host.dme @@ -0,0 +1,18 @@ +// DM Environment file for debug_test_host.dme. +// All manual changes should be made outside the BEGIN_ and END_ blocks. +// New source code should be placed in .dm files: choose File/New --> Code File. + +// BEGIN_INTERNALS +// END_INTERNALS + +// BEGIN_FILE_DIR +#define FILE_DIR . +// END_FILE_DIR + +// BEGIN_PREFERENCES +#define DEBUG +// END_PREFERENCES + +// BEGIN_INCLUDE +#include "debug_test_host.dm" +// END_INCLUDE diff --git a/tests/test_runner/src/main.rs b/tests/test_runner/src/main.rs index 813bd232..75659151 100644 --- a/tests/test_runner/src/main.rs +++ b/tests/test_runner/src/main.rs @@ -2,41 +2,7 @@ mod paths; use std::process::Command; -trait ByondCommand { - fn with_byond_paths(&mut self) -> &mut Self; -} - -#[cfg(unix)] -impl ByondCommand for Command { - // TODO: This doesn't read very nice - fn with_byond_paths(&mut self) -> &mut Command { - let byond_system = paths::find_byond(); - let byond_bin = paths::find_byond_bin(); - - let path = format!( - "{}:{}", - byond_bin.as_os_str().to_str().unwrap(), - std::env::var_os("PATH").unwrap().to_str().unwrap() - ); - - let ld_library_path = format!( - "{}:{}", - byond_bin.as_os_str().to_str().unwrap(), - std::env::var_os("LD_LIBRARY_PATH").unwrap().to_str().unwrap() - ); - - self.env("BYOND_SYSTEM", byond_system) - .env("PATH", path) - .env("LD_LIBRARY_PATH", ld_library_path) - } -} - -#[cfg(windows)] -impl ByondCommand for Command { - fn with_byond_paths(&mut self) -> &mut Command { - self - } -} +use paths::ByondCommand; fn main() { let res = Command::new(paths::find_dm()).with_byond_paths().arg(paths::find_dme()).status().unwrap(); diff --git a/tests/test_runner/src/paths.rs b/tests/test_runner/src/paths.rs index 3b71a3ed..0cf3d49c 100644 --- a/tests/test_runner/src/paths.rs +++ b/tests/test_runner/src/paths.rs @@ -1,4 +1,40 @@ -use std::path::PathBuf; +use std::{path::PathBuf, process::Command}; + +pub trait ByondCommand { + fn with_byond_paths(&mut self) -> &mut Self; +} + +#[cfg(unix)] +impl ByondCommand for Command { + // TODO: This doesn't read very nice + fn with_byond_paths(&mut self) -> &mut Command { + let byond_system = find_byond(); + let byond_bin = find_byond_bin(); + + let path = format!( + "{}:{}", + byond_bin.as_os_str().to_str().unwrap(), + std::env::var_os("PATH").unwrap().to_str().unwrap() + ); + + let ld_library_path = format!( + "{}:{}", + byond_bin.as_os_str().to_str().unwrap(), + std::env::var_os("LD_LIBRARY_PATH").unwrap().to_str().unwrap() + ); + + self.env("BYOND_SYSTEM", byond_system) + .env("PATH", path) + .env("LD_LIBRARY_PATH", ld_library_path) + } +} + +#[cfg(windows)] +impl ByondCommand for Command { + fn with_byond_paths(&mut self) -> &mut Command { + self + } +} pub fn find_byond() -> PathBuf { let path = PathBuf::from(std::env::var_os("BYOND_PATH").unwrap()); From e970c0be8bfd3e2bbcb04059495d486d64e41145 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sat, 3 Oct 2026 23:18:36 -0400 Subject: [PATCH 04/20] fix hook result cleanup, match structs to byond-re --- auxtools/src/hooks.cpp | 53 +++-- auxtools/src/hooks.rs | 13 +- auxtools/src/proc.rs | 4 +- auxtools/src/raw_types/funcs.cpp | 36 +--- auxtools/src/raw_types/funcs.rs | 10 +- auxtools/src/raw_types/lists.rs | 4 +- auxtools/src/raw_types/misc.rs | 1 + auxtools/src/raw_types/procs.rs | 288 ++++++++++------------------ auxtools/src/raw_types/strings.rs | 5 +- auxtools/src/raw_types/variables.rs | 13 ++ auxtools/src/symbols.rs | 3 +- tests/auxtest/src/hooks.rs | 31 +++ tests/auxtest/src/lib.rs | 1 + tests/auxtest_host/auxtest_host.dm | 11 ++ 14 files changed, 228 insertions(+), 245 deletions(-) create mode 100644 tests/auxtest/src/hooks.rs diff --git a/auxtools/src/hooks.cpp b/auxtools/src/hooks.cpp index decc8d1c..ada9354e 100644 --- a/auxtools/src/hooks.cpp +++ b/auxtools/src/hooks.cpp @@ -12,11 +12,19 @@ using Runtime_Ptr = void(*)(char *pError); // matched) and left nothing on the stack for `retn 4` to pop, so the stack drifted // until an SSE store faulted somewhere inside BYOND. Read byte-identical on // 516.1669 and 516.1687. -using CallProcById_Ptr = Value(*)(Value, uint32_t, uint32_t, uint32_t, Value, Value*, uint32_t, uint32_t, uint32_t); +// +// The last two arguments are a completion callback and a value to hand it. BYOND +// pushes the result's two dwords and then the value, which is the same thing as +// passing a Value. Plain cdecl on both platforms. +using CallProc_Callback = void(*)(Value, uint32_t); +using CallProcById_Ptr = Value(*)(Value, uint32_t, uint32_t, uint32_t, Value, Value*, uint32_t, CallProc_Callback, uint32_t); // The type of the hook defined in hooks.rs using CallProcById_Hook_Ptr = Value(*)(Value, uint32_t, uint32_t, uint32_t, Value, Value*, uint32_t, uint32_t, uint32_t); +// The bit of `proc_type` that says the caller is not going to look at the result +static const uint32_t PROC_TYPE_DISCARD_RESULT = 0x04; + extern "C" { // The ptr everybody else sees Runtime_Ptr runtime_byond = nullptr; @@ -24,6 +32,9 @@ extern "C" { // The original function - set by rust after hooking Runtime_Ptr runtime_original = nullptr; CallProcById_Ptr call_proc_by_id_original = nullptr; + + // Lives in funcs.cpp + extern void (*dec_ref_count_byond)(Value); } // If the top of this stack is true, we replace byond's runtime exceptions with our own @@ -51,12 +62,12 @@ extern "C" uint8_t call_proc_by_id_hook( Value usr, uint32_t proc_type, uint32_t proc_id, - uint32_t unk_0, + uint32_t override_depth, Value src, Value* args, - uint8_t args_count, - uint32_t unk_1, - uint32_t unk_2); + uint32_t args_count, + CallProc_Callback callback, + uint32_t callback_value); // Stands in for `call_proc_by_id` itself, so its shape has to match that function // exactly (see the note on `CallProcById_Ptr` above). Passes through to our rust hook. @@ -65,12 +76,12 @@ extern "C" Value call_proc_by_id_hook_trampoline( Value usr, uint32_t proc_type, uint32_t proc_id, - uint32_t unk_0, + uint32_t override_depth, Value src, Value* args, - uint8_t args_count, - uint32_t unk_1, - uint32_t unk_2 + uint32_t args_count, + CallProc_Callback callback, + uint32_t callback_value ) { Value ret; @@ -81,11 +92,23 @@ extern "C" Value call_proc_by_id_hook_trampoline( // one place that can hand the called proc a clean context. RuntimeContext runtime_scope(false); - if (call_proc_by_id_hook(&ret, usr, proc_type, proc_id, unk_0, src, args, args_count, unk_1, unk_2)) { - clean(ret); - return ret; - } else { - return call_proc_by_id_original(usr, proc_type, proc_id, unk_0, src, args, args_count, unk_1, unk_2); + if (!call_proc_by_id_hook(&ret, usr, proc_type, proc_id, override_depth, src, args, args_count, callback, callback_value)) { + return call_proc_by_id_original(usr, proc_type, proc_id, override_depth, src, args, args_count, callback, callback_value); + } + clean(ret); + + // The hook ran instead of `exec_proc`, so it owes the caller what `exec_proc` + // does when a proc ends. A caller that passed a callback gets the result + // through it, and one that set the discard bit doesn't want it at all. Both of + // them get null back and never release anything, so the result's reference is + // ours to drop. Done here and not in rust so a callback that throws doesn't + // have to unwind through a rust frame. + if (callback != nullptr) { + callback(ret, callback_value); + } + if (callback != nullptr || (proc_type & PROC_TYPE_DISCARD_RESULT)) { + dec_ref_count_byond(ret); + ret = Value{0, 0}; } - //return call_proc_by_id_hook(usr, proc_type, proc_id, unk_0, src, args, args_count, unk_1, unk_2); + return ret; } diff --git a/auxtools/src/hooks.rs b/auxtools/src/hooks.rs index 3ca0b725..5226275c 100644 --- a/auxtools/src/hooks.rs +++ b/auxtools/src/hooks.rs @@ -34,12 +34,12 @@ extern "C" { usr: raw_types::values::Value, proc_type: u32, proc_id: raw_types::procs::ProcId, - unk_0: u32, + override_depth: u32, src: raw_types::values::Value, args: *mut raw_types::values::Value, args_count_l: usize, - unk_1: u32, - unk_2: u32 + callback: u32, + callback_value: u32 ) -> raw_types::values::Value; } @@ -172,12 +172,13 @@ extern "C" fn call_proc_by_id_hook( usr_raw: raw_types::values::Value, _proc_type: u32, proc_id: raw_types::procs::ProcId, - _unknown1: u32, + _override_depth: u32, src_raw: raw_types::values::Value, args_ptr: *mut raw_types::values::Value, num_args: usize, - _unknown2: u32, - _unknown3: u32 + // the trampoline deals with these two once we return + _callback: u32, + _callback_value: u32 ) -> u8 { // copy the fn out so no borrow is held while the hook runs DM code, which // can call back into here or register hooks diff --git a/auxtools/src/proc.rs b/auxtools/src/proc.rs index 63b10c7e..b2706ddc 100644 --- a/auxtools/src/proc.rs +++ b/auxtools/src/proc.rs @@ -131,7 +131,9 @@ impl Proc { let args: Vec<_> = args.iter().map(|e| e.raw).collect(); - if raw_types::funcs::call_proc_by_id(&mut ret, Value::NULL.raw, 0, self.id, 0, Value::NULL.raw, args.as_ptr(), args.len(), 0, 0) == 1 { + // 2 is the proc_type BYOND's own direct calls pass. It never passes 0, and + // a `..()` with no parent hands back garbage in a frame that has it. + if raw_types::funcs::call_proc_by_id(&mut ret, Value::NULL.raw, 2, self.id, 0, Value::NULL.raw, args.as_ptr(), args.len(), 0, 0) == 1 { return Ok(Value::from_raw_owned(ret)); } } diff --git a/auxtools/src/raw_types/funcs.cpp b/auxtools/src/raw_types/funcs.cpp index 90b8de89..37a8970f 100644 --- a/auxtools/src/raw_types/funcs.cpp +++ b/auxtools/src/raw_types/funcs.cpp @@ -80,7 +80,6 @@ extern "C" DEFINE_byond(get_string_table_entry, void *, (uint32_t)); DEFINE_byond(inc_ref_count, void, (Value)); DEFINE_byond(dec_ref_count, void, (Value)); - DEFINE_byond_REGPARM3(get_list_by_id, void *, (uint32_t)); DEFINE_byond(get_assoc_element, Value, (Value, Value)); DEFINE_byond(set_assoc_element, void, (Value, Value, Value)); DEFINE_byond(create_list, uint32_t, (uint32_t)); @@ -138,18 +137,18 @@ extern "C" uint8_t call_proc_by_id( Value usr, uint32_t proc_type, uint32_t proc_id, - uint32_t unk_0, + uint32_t override_depth, Value src, const Value *args, - uint8_t args_count, - uint32_t unk_1, - uint32_t unk_2) + uint32_t args_count, + uint32_t callback, + uint32_t callback_value) { RuntimeContext ctx(false); BYOND_TRY { - *out = call_proc_by_id_byond(usr, proc_type, proc_id, unk_0, src, args, args_count, unk_1, unk_2); + *out = call_proc_by_id_byond(usr, proc_type, proc_id, override_depth, src, args, args_count, callback, callback_value); return 1; } BYOND_CATCH @@ -165,9 +164,9 @@ extern "C" uint8_t call_datum_proc_by_name( uint32_t proc_name, Value src, Value *args, - uint8_t args_count, - uint32_t unk_0, - uint32_t unk_1) + uint32_t args_count, + uint32_t callback, + uint32_t callback_value) { // Intercepts, unlike `call_proc_by_id` above. The errors this function raises // itself (an unknown proc name, a null receiver) happen before any DM code @@ -181,11 +180,11 @@ extern "C" uint8_t call_datum_proc_by_name( { clean(usr); clean(src); - for (int i = 0; i < args_count; i++) + for (uint32_t i = 0; i < args_count; i++) { clean(args[i]); } - *out = call_datum_proc_by_name_byond(usr, proc_type, proc_name, src, args, args_count, unk_0, unk_1); + *out = call_datum_proc_by_name_byond(usr, proc_type, proc_name, src, args, args_count, callback, callback_value); return 1; } BYOND_CATCH @@ -304,21 +303,6 @@ extern "C" uint8_t dec_ref_count(Value value) } } -extern "C" uint8_t get_list_by_id(void **out, uint32_t list_id) -{ - RuntimeContext ctx(true); - - BYOND_TRY - { - *out = get_list_by_id_byond(list_id); - return 1; - } - BYOND_CATCH - { - return 0; - } -} - extern "C" uint8_t get_assoc_element(Value *out, Value datum, Value index) { RuntimeContext ctx(true); diff --git a/auxtools/src/raw_types/funcs.rs b/auxtools/src/raw_types/funcs.rs index 55bf21ca..7c8baff1 100644 --- a/auxtools/src/raw_types/funcs.rs +++ b/auxtools/src/raw_types/funcs.rs @@ -42,12 +42,12 @@ extern "C" { usr: values::Value, proc_type: u32, proc_id: procs::ProcId, - unk_0: u32, + override_depth: u32, src: values::Value, args: *const values::Value, args_count_l: usize, - unk_1: u32, - unk_2: u32 + callback: u32, + callback_value: u32 ) -> u8; pub fn call_datum_proc_by_name( out: *mut values::Value, @@ -57,8 +57,8 @@ extern "C" { src: values::Value, args: *mut values::Value, args_count_l: usize, - unk_0: u32, - unk_1: u32 + callback: u32, + callback_value: u32 ) -> u8; pub fn get_proc_array_entry(out: *mut *mut procs::ProcEntry, id: procs::ProcId) -> u8; pub fn get_string_id(out: *mut strings::StringId, string: *const c_char) -> u8; diff --git a/auxtools/src/raw_types/lists.rs b/auxtools/src/raw_types/lists.rs index e11fa17b..14e6572c 100644 --- a/auxtools/src/raw_types/lists.rs +++ b/auxtools/src/raw_types/lists.rs @@ -19,8 +19,8 @@ pub struct AssociativeListEntry { pub left: *mut AssociativeListEntry, pub right: *mut AssociativeListEntry, pub parent: *mut AssociativeListEntry, - /// 0 = red, 1 = black. Padded to 4. - pub color: u32 + /// 0 = red, 1 = black. + pub color: u8 } #[repr(C)] diff --git a/auxtools/src/raw_types/misc.rs b/auxtools/src/raw_types/misc.rs index f6509f77..00324e22 100644 --- a/auxtools/src/raw_types/misc.rs +++ b/auxtools/src/raw_types/misc.rs @@ -138,6 +138,7 @@ pub fn set_bytecode(id: BytecodeId, new_bytecode: *mut u32, new_bytecode_count: (*misc).bytecode.bytecode = new_bytecode; (*misc).bytecode.count = new_bytecode_count; } + return; } let misc = misc as *mut Misc_V1; diff --git a/auxtools/src/raw_types/procs.rs b/auxtools/src/raw_types/procs.rs index ed3fba67..3ca9cdde 100644 --- a/auxtools/src/raw_types/procs.rs +++ b/auxtools/src/raw_types/procs.rs @@ -13,8 +13,9 @@ pub struct ProcEntry { pub name: strings::StringId, pub desc: strings::StringId, pub category: strings::StringId, + // the last `/` part of `path` + path_basename: strings::StringId, flags: u32, - unk_1: u32, pub metadata: ProcMetadata } @@ -24,24 +25,35 @@ pub struct ProcEntry { )] #[repr(C)] pub struct ProcMetadata { + // three bytes straight out of the .dmb, meaning unknown #[only_in(Post1630)] - unk_2: u32, + byte_24: i8, + #[only_in(Post1630)] + byte_25: u8, + #[only_in(Post1630)] + byte_26: i8, pub bytecode: misc::BytecodeId, pub locals: misc::LocalsId, - pub parameters: misc::ParametersId + pub parameters: misc::ParametersId, + // the proc `..()` goes to, 0xFFFF for none. Brings the entry to the 44 bytes + // BYOND strides its proc table by + #[only_in(Post1630)] + next_override: ProcId } #[repr(C)] pub struct ProcInstance { pub proc: ProcId, + /// The `proc_type` this frame was called with. `..()` reads it back. pub flags: u8, + /// Which override of the proc this frame runs, 0 for the most derived. + pub override_depth: u8, pub mega_hack: u16, pub usr: values::Value, pub src: values::Value, pub context: *mut ExecutionContext, pub argslist_idx: values::ValueData, unk_1: u32, - unk_2: u32, inner: ProcInstanceInner } @@ -65,20 +77,31 @@ impl ProcInstance { )] #[repr(C)] struct ProcInstanceInner { + #[only_in(Pre516)] + unk_2: u32, + // called with the result when the proc ends, null for none + #[only_in(Post516)] + callback: u32, #[only_in(Post516)] - unk_pre: u32, + callback_value: u32, pub args_count: u32, pub args: *mut values::Value, - #[only_in(Pre516)] - unk_3: [u8; 0x58], - #[only_in(Post516)] - unk_4: [u8; 0x58], + // room for ten values, shared by the args and the locals, so a small proc + // doesn't have to allocate + arg_local_store: [u8; 0x50], + internal_arg_count: u32, + // not 0 once the store has overflowed onto the heap + external_arg_count: u32, pub time_to_resume: u32 } // Linux is 148 bytes against Windows' 152: from `cache` onward the fields sit 4 // bytes earlier, which is the same shape Windows had before 1668. So Linux // always takes the Pre1668 variant. +// +// The whole difference is the call flags. Windows from 1668 keeps them in a +// dword of their own, everything else packs them into the byte after +// `test_flag`. #[versioned( Pre1668 if cfg!(unix) || crate::version::BYOND_VERSION_MAJOR <= 515 || (crate::version::BYOND_VERSION_MAJOR == 516 && crate::version::BYOND_VERSION_MINOR <= 1667), Post1668, @@ -92,32 +115,42 @@ pub struct ExecutionContext { pub bytecode: *mut u32, pub bytecode_offset: u16, pub test_flag: u8, - unk_0: u8, - cached_datum: values::Value, #[only_in(Pre1668)] - unk_1_pre: [u8; 0x10], + exec_call_flags: u8, #[only_in(Post1668)] - unk_1_post: [u8; 0x14], + exec_call_flags: u32, + cache: values::Value, + cache_key: values::Value, + // two pointers into exec_proc's own stack frame + scratch_ptr_a: u32, + scratch_ptr_b: u32, pub dot: values::Value, pub locals: *mut values::Value, pub stack: *mut values::Value, pub locals_count: u16, pub stack_size: u16, - unk_2: u32, + // the iterators of the loops this one is nested in + iterator_stack: u32, pub current_iterator: *mut values::Value, pub iterator_allocated: u32, pub iterator_length: u32, pub iterator_index: u32, - unk_3: u32, - unk_4: [u8; 0x03], - pub iterator_filtered_type: u8, - unk_5: u8, - unk_6: u8, - unk_7: u8, + pub iterator_filter_type: values::Value, + pub iterator_filter_bitflags: u32, + pub iterator_kind: u8, + // byond-re calls this one `jnz_lchk_loop_count` pub infinite_loop_count: u32, - unk_8: [u8; 0x02], - paused: u8, - unk_9: [u8; 0x33] + // 0 while the proc is sleeping. There is no separate "paused" byte + not_suspended: u8, + caller_chain_preserve_depth: u8, + background: u8, + exception_state: u8, + throw_handler: u32, + // timevals. The last is the deadline a background proc's loops run against + time_now: [u32; 2], + cpu_clock_init: [u32; 2], + cpu_clock_quantum: [u32; 2], + loop_timeout: [u32; 2] } #[repr(C)] @@ -146,181 +179,60 @@ pub struct SuspendedProcs { mod layout_tests { use std::mem::{offset_of, size_of}; - use super::{misc, strings, values}; - - // Reference structs: verbatim copies of the original hand-written variants, - // kept here so that any accidental layout change in the macro output is - // caught. - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefProcMetadataPre1630 { - bytecode: misc::BytecodeId, - locals: misc::LocalsId, - parameters: misc::ParametersId - } - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefProcMetadataPost1630 { - unk_2: u32, - bytecode: misc::BytecodeId, - locals: misc::LocalsId, - parameters: misc::ParametersId - } - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefProcInstanceInnerPre516 { - args_count: u32, - args: *mut values::Value, - unk_3: [u8; 0x58], - time_to_resume: u32 - } - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefProcInstanceInnerPost516 { - unk_3: u32, - args_count: u32, - args: *mut values::Value, - unk_4: [u8; 0x58], - time_to_resume: u32 - } - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefExecutionContextPre1668 { - proc_instance: *mut super::ProcInstance, - parent_context: *mut super::ExecutionContext, - filename: strings::StringId, - line: u32, - bytecode: *mut u32, - bytecode_offset: u16, - test_flag: u8, - unk_0: u8, - cached_datum: values::Value, - unk_1: [u8; 0x10], - dot: values::Value, - locals: *mut values::Value, - stack: *mut values::Value, - locals_count: u16, - stack_size: u16, - unk_2: u32, - current_iterator: *mut values::Value, - iterator_allocated: u32, - iterator_length: u32, - iterator_index: u32, - unk_3: u32, - unk_4: [u8; 0x03], - iterator_filtered_type: u8, - unk_5: u8, - unk_6: u8, - unk_7: u8, - infinite_loop_count: u32, - unk_8: [u8; 0x02], - paused: u8, - unk_9: [u8; 0x33] - } - - #[repr(C)] - #[derive(Copy, Clone)] - struct RefExecutionContextPost1668 { - proc_instance: *mut super::ProcInstance, - parent_context: *mut super::ExecutionContext, - filename: strings::StringId, - line: u32, - bytecode: *mut u32, - bytecode_offset: u16, - test_flag: u8, - unk_0: u8, - cached_datum: values::Value, - unk_1: [u8; 0x14], - dot: values::Value, - locals: *mut values::Value, - stack: *mut values::Value, - locals_count: u16, - stack_size: u16, - unk_2: u32, - current_iterator: *mut values::Value, - iterator_allocated: u32, - iterator_length: u32, - iterator_index: u32, - unk_3: u32, - unk_4: [u8; 0x03], - iterator_filtered_type: u8, - unk_5: u8, - unk_6: u8, - unk_7: u8, - infinite_loop_count: u32, - unk_8: [u8; 0x02], - paused: u8, - unk_9: [u8; 0x33] - } + // The numbers below are read off the binaries, 516.1688: what BYOND allocates + // for each struct and where its own code reaches into them. The ones for + // older builds are from byond-re's notes. #[test] - fn proc_metadata_pre1630() { - type Gen = super::ProcMetadataPre1630; - type Ref = RefProcMetadataPre1630; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, bytecode), offset_of!(Ref, bytecode)); - assert_eq!(offset_of!(Gen, locals), offset_of!(Ref, locals)); - assert_eq!(offset_of!(Gen, parameters), offset_of!(Ref, parameters)); - } - - #[test] - fn proc_metadata_post1630() { - type Gen = super::ProcMetadataPost1630; - type Ref = RefProcMetadataPost1630; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, bytecode), offset_of!(Ref, bytecode)); - assert_eq!(offset_of!(Gen, locals), offset_of!(Ref, locals)); - assert_eq!(offset_of!(Gen, parameters), offset_of!(Ref, parameters)); - } - - #[test] - fn proc_instance_inner_pre516() { - type Gen = super::ProcInstanceInnerPre516; - type Ref = RefProcInstanceInnerPre516; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, args_count), offset_of!(Ref, args_count)); - assert_eq!(offset_of!(Gen, args), offset_of!(Ref, args)); - assert_eq!(offset_of!(Gen, time_to_resume), offset_of!(Ref, time_to_resume)); + fn execution_context_linux_and_old_windows() { + type Gen = super::ExecutionContextPre1668; + assert_eq!(size_of::(), 0x94); + assert_eq!(offset_of!(Gen, bytecode_offset), 0x14); + assert_eq!(offset_of!(Gen, dot), 0x30); + assert_eq!(offset_of!(Gen, locals), 0x38); + assert_eq!(offset_of!(Gen, stack_size), 0x42); + assert_eq!(offset_of!(Gen, current_iterator), 0x48); + assert_eq!(offset_of!(Gen, iterator_kind), 0x64); + assert_eq!(offset_of!(Gen, infinite_loop_count), 0x68); } #[test] - fn proc_instance_inner_post516() { - type Gen = super::ProcInstanceInnerPost516; - type Ref = RefProcInstanceInnerPost516; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, args_count), offset_of!(Ref, args_count)); - assert_eq!(offset_of!(Gen, args), offset_of!(Ref, args)); - assert_eq!(offset_of!(Gen, time_to_resume), offset_of!(Ref, time_to_resume)); + fn execution_context_windows() { + type Gen = super::ExecutionContextPost1668; + assert_eq!(size_of::(), 0x98); + assert_eq!(offset_of!(Gen, bytecode_offset), 0x14); + assert_eq!(offset_of!(Gen, dot), 0x34); + assert_eq!(offset_of!(Gen, locals), 0x3C); + assert_eq!(offset_of!(Gen, stack_size), 0x46); + assert_eq!(offset_of!(Gen, current_iterator), 0x4C); + assert_eq!(offset_of!(Gen, iterator_kind), 0x68); + assert_eq!(offset_of!(Gen, infinite_loop_count), 0x6C); } #[test] - fn execution_context_pre1668() { - type Gen = super::ExecutionContextPre1668; - type Ref = RefExecutionContextPre1668; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, proc_instance), offset_of!(Ref, proc_instance)); - assert_eq!(offset_of!(Gen, filename), offset_of!(Ref, filename)); - assert_eq!(offset_of!(Gen, bytecode), offset_of!(Ref, bytecode)); - assert_eq!(offset_of!(Gen, dot), offset_of!(Ref, dot)); - assert_eq!(offset_of!(Gen, locals), offset_of!(Ref, locals)); - assert_eq!(offset_of!(Gen, locals_count), offset_of!(Ref, locals_count)); + fn proc_instance() { + type Inner = super::ProcInstanceInnerPost516; + let inner = offset_of!(super::ProcInstance, inner); + assert_eq!(size_of::(), 0x90); + assert_eq!(offset_of!(super::ProcInstance, override_depth), 0x05); + assert_eq!(offset_of!(super::ProcInstance, context), 0x18); + assert_eq!(inner + offset_of!(Inner, callback), 0x24); + assert_eq!(inner + offset_of!(Inner, args_count), 0x2C); + assert_eq!(inner + offset_of!(Inner, args), 0x30); + assert_eq!(inner + offset_of!(Inner, external_arg_count), 0x88); + assert_eq!(inner + offset_of!(Inner, time_to_resume), 0x8C); + // 515 and older have one dword less in front of the args + assert_eq!(inner + offset_of!(super::ProcInstanceInnerPre516, args_count), 0x28); } #[test] - fn execution_context_post1668() { - type Gen = super::ExecutionContextPost1668; - type Ref = RefExecutionContextPost1668; - assert_eq!(size_of::(), size_of::()); - assert_eq!(offset_of!(Gen, proc_instance), offset_of!(Ref, proc_instance)); - assert_eq!(offset_of!(Gen, filename), offset_of!(Ref, filename)); - assert_eq!(offset_of!(Gen, bytecode), offset_of!(Ref, bytecode)); - assert_eq!(offset_of!(Gen, dot), offset_of!(Ref, dot)); - assert_eq!(offset_of!(Gen, locals), offset_of!(Ref, locals)); - assert_eq!(offset_of!(Gen, locals_count), offset_of!(Ref, locals_count)); + fn proc_entry() { + let metadata = offset_of!(super::ProcEntry, metadata); + assert_eq!(size_of::(), 44); + assert_eq!(metadata + offset_of!(super::ProcMetadataPost1630, bytecode), 28); + assert_eq!(metadata + offset_of!(super::ProcMetadataPost1630, next_override), 40); + // before 1630 the entry is 36 bytes with the bytecode id at +24 + assert_eq!(metadata + size_of::(), 36); + assert_eq!(metadata + offset_of!(super::ProcMetadataPre1630, bytecode), 24); } } diff --git a/auxtools/src/raw_types/strings.rs b/auxtools/src/raw_types/strings.rs index a2a890c7..0097e3cc 100644 --- a/auxtools/src/raw_types/strings.rs +++ b/auxtools/src/raw_types/strings.rs @@ -23,5 +23,8 @@ pub struct StringEntry { pub right: *mut StringEntry, pub ref_count: u32, pub unk_1: u32, - pub unk_2: u32 + pub unk_2: u32, + pub encoding: u8, + /// Set for proc paths. BYOND never frees a string that has it. + pub is_proc_name: u8 } diff --git a/auxtools/src/raw_types/variables.rs b/auxtools/src/raw_types/variables.rs index 3cfa256b..84a9fd82 100644 --- a/auxtools/src/raw_types/variables.rs +++ b/auxtools/src/raw_types/variables.rs @@ -1,7 +1,20 @@ use super::strings; +// Not one BYOND struct, just two globals that sit next to each other: the name +// array and how many entries it has. `symbols` finds `entries` and backs up to +// wherever this starts. +#[cfg(windows)] #[repr(C)] pub struct VariableNameIdTable { pub entries: *const strings::StringId, pub count: u32 } + +// On Linux the count comes first. What follows `entries` there is the pointer +// to the global values, which is not a count of anything. +#[cfg(unix)] +#[repr(C)] +pub struct VariableNameIdTable { + pub count: u32, + pub entries: *const strings::StringId +} diff --git a/auxtools/src/symbols.rs b/auxtools/src/symbols.rs index 3bff2dd2..2204bdda 100644 --- a/auxtools/src/symbols.rs +++ b/auxtools/src/symbols.rs @@ -105,7 +105,8 @@ pub(crate) fn resolve_full() -> Result<(), String> { /// runs on every partial init and not only the first. pub(crate) fn resolve_partial() -> Result<(), String> { let (module, build) = module_and_build()?; - let address = resolve(&module, build, "variable_names")?; + // the recipe finds the name array, which is not where the table starts on Linux + let address = resolve(&module, build, "variable_names")? - std::mem::offset_of!(VariableNameIdTable, entries); unsafe { funcs::VARIABLE_NAMES = address as *const VariableNameIdTable; } diff --git a/tests/auxtest/src/hooks.rs b/tests/auxtest/src/hooks.rs new file mode 100644 index 00000000..1d932c97 --- /dev/null +++ b/tests/auxtest/src/hooks.rs @@ -0,0 +1,31 @@ +use auxtools::*; + +const DISCARDED: &str = "a hook result nobody is going to look at"; + +#[hook("/datum/auxtest_discard/New")] +fn discard_new() { + Value::from_string(DISCARDED) +} + +#[hook("/proc/auxtest_hooks")] +fn test_hooks() { + use raw_types::{funcs, strings}; + + // held for the whole test so the string can't be freed and remade under us + let held = StringRef::new(DISCARDED)?; + + unsafe { + let mut entry: *mut strings::StringEntry = std::ptr::null_mut(); + assert_eq!(funcs::get_string_table_entry(&mut entry, held.get_id()), 1); + + // BYOND runs New() with the "don't want the result" bit set and never + // releases what comes back, so the hook's result has to be dropped for it + let before = (*entry).ref_count; + Proc::find("/proc/auxtest_new_discard").unwrap().call(&[])?; + if (*entry).ref_count != before { + return Err(runtime!("test_hooks: a discarded hook result kept its reference")); + } + } + + Ok(Value::from(true)) +} diff --git a/tests/auxtest/src/lib.rs b/tests/auxtest/src/lib.rs index eda00c56..6a6bf37e 100644 --- a/tests/auxtest/src/lib.rs +++ b/tests/auxtest/src/lib.rs @@ -1,5 +1,6 @@ use auxtools::*; +mod hooks; mod lists; mod strings; mod value_from; diff --git a/tests/auxtest_host/auxtest_host.dm b/tests/auxtest_host/auxtest_host.dm index 2b4fd8ad..d8431e77 100644 --- a/tests/auxtest_host/auxtest_host.dm +++ b/tests/auxtest_host/auxtest_host.dm @@ -56,7 +56,17 @@ var/datum/weak_test_datum /proc/auxtest_length(value) return length(value) +// New() is hooked and hands back a string that nothing here ever reads +/datum/auxtest_discard/New() + CRASH() + +/proc/auxtest_new_discard() + new /datum/auxtest_discard + // Tests +/proc/auxtest_hooks() + CRASH() + /proc/auxtest_lists() CRASH() @@ -76,6 +86,7 @@ var/datum/weak_test_datum ASSERT(init_result == "SUCCESS") // Tests + ASSERT(auxtest_hooks() == TRUE) ASSERT(auxtest_lists() == TRUE) ASSERT(auxtest_strings() == TRUE) ASSERT(auxtest_value_from() == TRUE) From 6e1d6b8cd6887cb271386e9a119979898b729bb7 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sat, 3 Oct 2026 23:35:05 -0400 Subject: [PATCH 05/20] add alist support to auxtools and the debug server --- auxtools/src/list.rs | 48 +++++++++++++++++++++++- auxtools/src/raw_types/funcs.rs | 5 +++ auxtools/src/raw_types/lists.rs | 8 ++++ auxtools/src/symbols.rs | 4 +- auxtools/src/symbols/linux.rs | 18 +++++++++ auxtools/src/symbols/windows.rs | 18 +++++++++ debug_server/src/server.rs | 18 ++++++++- tests/auxtest/src/lists.rs | 36 ++++++++++++++++++ tests/auxtest_host/auxtest_host.dm | 10 +++++ tests/debug_test/src/main.rs | 21 +++++++++++ tests/debug_test_host/debug_test_host.dm | 3 ++ 11 files changed, 186 insertions(+), 3 deletions(-) diff --git a/auxtools/src/list.rs b/auxtools/src/list.rs index 7ac1fbe3..fc52d621 100644 --- a/auxtools/src/list.rs +++ b/auxtools/src/list.rs @@ -3,7 +3,7 @@ use std::iter::FromIterator; use crate::*; /// A wrapper around [Values](struct.Value.html) that make working with lists a -/// little easier +/// little easier. It can also hold an `/alist`, where numbers are keys. pub struct List { value: Value } @@ -97,6 +97,52 @@ impl List { self.len() == 0 } + /// Whether this is a DM `/alist`, where numbers are keys and not positions. + pub fn is_alist(&self) -> bool { + self.value.raw.tag == raw_types::values::ValueTag::Alist + } + + /// Every key and value of an `/alist`, in the order `for (var/k in A)` + /// gives them. + /// + /// This is a copy, so nothing here points into BYOND's tree once it returns. + pub fn alist_pairs(&self) -> DMResult> { + if !self.is_alist() { + return Err(runtime!("attempted to read alist pairs of a non-alist")); + } + + let id = unsafe { self.value.raw.data.id } as usize; + let record = unsafe { + // raw reads of BYOND's table, so a stale id has to be turned away here + if id >= *raw_types::funcs::ALIST_TABLE_COUNT as usize { + return Err(runtime!("alist id {} is outside the alist table", id)); + } + // BYOND reallocates the table as it grows, so read the pointer every time + *(*raw_types::funcs::ALIST_TABLE).add(id) + }; + if record.is_null() { + return Err(runtime!("alist id {} has no record", id)); + } + + let mut pairs = vec![]; + let mut node = unsafe { (*record).root }; + let mut pending = vec![]; + // left, node, right, without recursing + while !node.is_null() || !pending.is_empty() { + while !node.is_null() { + pending.push(node); + node = unsafe { (*node).left }; + } + let entry = pending.pop().unwrap(); + unsafe { + pairs.push((Value::from_raw((*entry).key), Value::from_raw((*entry).value))); + node = (*entry).right; + } + } + + Ok(pairs) + } + /// Whether DM's `islist()` says yes to this value. /// /// This asks BYOND instead of matching on the tag, because a tag can't diff --git a/auxtools/src/raw_types/funcs.rs b/auxtools/src/raw_types/funcs.rs index 7c8baff1..59022fe2 100644 --- a/auxtools/src/raw_types/funcs.rs +++ b/auxtools/src/raw_types/funcs.rs @@ -7,6 +7,11 @@ pub static mut CURRENT_EXECUTION_CONTEXT: *mut *mut procs::ExecutionContext = st pub static mut SUSPENDED_PROCS_BUFFER: *mut procs::SuspendedProcsBuffer = std::ptr::null_mut(); pub static mut SUSPENDED_PROCS: *mut procs::SuspendedProcs = std::ptr::null_mut(); +// BYOND's globals for the alist table: a pointer to the table, and its length. +// The table holds one record pointer per alist id, null for a free slot. +pub static mut ALIST_TABLE: *const *const *const lists::Alist = std::ptr::null(); +pub static mut ALIST_TABLE_COUNT: *const u32 = std::ptr::null(); + pub static mut VARIABLE_NAMES: *const variables::VariableNameIdTable = std::ptr::null(); // Function pointers exported by C++ but set by Rust diff --git a/auxtools/src/raw_types/lists.rs b/auxtools/src/raw_types/lists.rs index 14e6572c..bb34696e 100644 --- a/auxtools/src/raw_types/lists.rs +++ b/auxtools/src/raw_types/lists.rs @@ -23,6 +23,14 @@ pub struct AssociativeListEntry { pub color: u8 } +/// The 8-byte record an `/alist` id points at in the alist table. +#[repr(C)] +pub struct Alist { + pub root: *mut AssociativeListEntry, + // starts at 1 and nobody has worked out what it is + unknown: u32 +} + #[repr(C)] pub struct List { pub vector_part: *mut values::Value, diff --git a/auxtools/src/symbols.rs b/auxtools/src/symbols.rs index 2204bdda..81fca593 100644 --- a/auxtools/src/symbols.rs +++ b/auxtools/src/symbols.rs @@ -17,7 +17,7 @@ use linux::RECIPES; #[cfg(windows)] use windows::RECIPES; -use crate::raw_types::{funcs, procs, variables::VariableNameIdTable}; +use crate::raw_types::{funcs, lists, procs, variables::VariableNameIdTable}; /// The BYOND major version the patterns were checked against. The same build /// number under another major is a different binary. @@ -73,6 +73,8 @@ pub(crate) fn resolve_full() -> Result<(), String> { funcs::CURRENT_EXECUTION_CONTEXT = address("current_execution_context")? as *mut *mut procs::ExecutionContext; funcs::SUSPENDED_PROCS = address("suspended_procs")? as *mut procs::SuspendedProcs; funcs::SUSPENDED_PROCS_BUFFER = address("suspended_procs_buffer")? as *mut procs::SuspendedProcsBuffer; + funcs::ALIST_TABLE = address("alist_table_ptr")? as *const *const *const lists::Alist; + funcs::ALIST_TABLE_COUNT = address("alist_table_count")? as *const u32; funcs::call_proc_by_id_byond = function("call_proc_by_id")?; funcs::call_datum_proc_by_name_byond = function("call_datum_proc_by_name")?; funcs::get_proc_array_entry_byond = function("get_proc_array_entry")?; diff --git a/auxtools/src/symbols/linux.rs b/auxtools/src/symbols/linux.rs index cd4a7a29..42c24901 100644 --- a/auxtools/src/symbols/linux.rs +++ b/auxtools/src/symbols/linux.rs @@ -37,6 +37,10 @@ const RUNTIME_SIG: &str = const PROCDEF_ACCESSOR_SIG: &str = "8B 44 24 04 39 05 ?? ?? ?? ?? 76 ?? 6B C0 ?? 03 05 ?? ?? ?? ?? C3"; const STRING_ACCESSOR_SIG: &str = "56 53 83 EC 14 8B 44 24 20 39 05 ?? ?? ?? ?? 76 ?? 8B 15 ?? ?? ?? ?? 8B 04 82"; +// The 0x0F/0x55 tag dispatch identifies the accessor that reads the alist +// table. Operands are count=0 and pointer=1. +const ALIST_SIG: &str = "8B 54 24 04 8B 4C 24 08 80 FA 0F 74 ?? 31 C0 80 FA 55 75 ?? 3B 0D ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? 8B 04 88 85 C0 74 ?? 8B 00 C3"; + pub(crate) const RECIPES: &[Recipe] = &[ Recipe { name: "get_proc_array_entry", @@ -291,5 +295,19 @@ pub(crate) const RECIPES: &[Recipe] = &[ anchor: Anchor::Signature(SignatureTreatment::NoOffset, SUSPENDED_SIG), hops: &[], extract: Extract::AbsMem(OperandSelect::NthAbsMem(2)) + }, + Recipe { + name: "alist_table_count", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, ALIST_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + Recipe { + name: "alist_table_ptr", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, ALIST_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(1)) } ]; diff --git a/auxtools/src/symbols/windows.rs b/auxtools/src/symbols/windows.rs index c2bfdfc9..dcc74e04 100644 --- a/auxtools/src/symbols/windows.rs +++ b/auxtools/src/symbols/windows.rs @@ -27,6 +27,8 @@ const PROCDEF_ACCESSOR_SIG: &str = "55 8B EC 8B 45 08 3B 05 ?? ?? ?? ?? 72 04 33 // tail. const STRING_ACCESSOR_SIG: &str = "55 8B EC 8B 4D 08 3B 0D ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? 8B 04 88 85 C0 0F 85"; +const ALIST_SIG: &str = "3B 35 ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? C7 04 B0 00 00 00 00 46 3B 35 ?? ?? ?? ?? 72 EB"; + pub(crate) const RECIPES: &[Recipe] = &[ Recipe { name: "get_proc_array_entry", @@ -262,5 +264,21 @@ pub(crate) const RECIPES: &[Recipe] = &[ anchor: Anchor::Signature(SignatureTreatment::NoOffset, SUSPENDED_SIG), hops: &[], extract: Extract::AbsMem(OperandSelect::NthAbsMem(2)) + }, + // the alist accessor's base-less operands are count=0, table=1, and the + // repeated count=2 + Recipe { + name: "alist_table_count", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, ALIST_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(0)) + }, + Recipe { + name: "alist_table_ptr", + versions: SUPPORTED, + anchor: Anchor::Signature(SignatureTreatment::NoOffset, ALIST_SIG), + hops: &[], + extract: Extract::AbsMem(OperandSelect::NthAbsMem(1)) } ]; diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index 22f548fb..536dba4c 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -294,7 +294,7 @@ impl Server { fn stringify(value: &Value) -> String { if List::is_list(value) { match List::from_value(value) { - Ok(list) => format!("/list {{len = {}}}", list.len()), + Ok(list) => format!("/{} {{len = {}}}", if list.is_alist() { "alist" } else { "list" }, list.len()), Err(Runtime { message }) => format!("/list (failed to get len: {:?})", message) } } else { @@ -332,6 +332,22 @@ impl Server { fn list_to_variables(&mut self, value: &Value) -> Result, Runtime> { let state = self.state.as_ref().unwrap(); let list = List::from_value(value)?; + + // numbers are keys in an alist, so the position walk below would read + // the wrong entries + if list.is_alist() { + return Ok(list + .alist_pairs()? + .into_iter() + .enumerate() + .map(|(i, (key, value))| Variable { + name: format!("[{}]", i + 1), + value: format!("{} = {}", Self::stringify(&key), Self::stringify(&value)), + variables: Some(state.get_ref(Variables::ListPair { key, value })) + }) + .collect()); + } + let len = list.len(); let mut variables = vec![]; diff --git a/tests/auxtest/src/lists.rs b/tests/auxtest/src/lists.rs index f48ae0f8..972190c5 100644 --- a/tests/auxtest/src/lists.rs +++ b/tests/auxtest/src/lists.rs @@ -68,5 +68,41 @@ fn test_lists() { } } + let sample = Proc::find("/proc/auxtest_alist_sample").unwrap().call(&[])?; + let alist = sample.as_list()?; + if !alist.is_alist() || list_a.is_alist() { + return Err(runtime!("test_lists: is_alist is wrong")); + } + if alist.get(byond_string!("a"))?.as_number()? != 1.0 { + return Err(runtime!("test_lists: alist[\"a\"] != 1")); + } + // a number is a key here, not a position + if alist.get(7)?.as_string()? != "seven" { + return Err(runtime!("test_lists: alist[7] != \"seven\"")); + } + + let keys = Proc::find("/proc/auxtest_alist_keys").unwrap().call(&[&sample])?.as_list()?; + let pairs = alist.alist_pairs()?; + if pairs.len() != 3 || pairs.len() != keys.len() as usize { + return Err(runtime!("test_lists: alist_pairs found {} pairs, not 3", pairs.len())); + } + for (n, (key, value)) in pairs.iter().enumerate() { + if *key != keys.get(n as u32 + 1)? { + return Err(runtime!("test_lists: alist_pairs key {} is out of DM's order", n + 1)); + } + if *value != alist.get(key)? { + return Err(runtime!("test_lists: alist_pairs value {} != alist[key]", n + 1)); + } + } + + alist.set(8, byond_string!("eight"))?; + if alist.get(8)?.as_string()? != "eight" || alist.len() != 4 { + return Err(runtime!("test_lists: set on an alist didn't stick")); + } + + if list_a.alist_pairs().is_ok() { + return Err(runtime!("test_lists: alist_pairs accepted a plain list")); + } + Ok(Value::from(true)) } diff --git a/tests/auxtest_host/auxtest_host.dm b/tests/auxtest_host/auxtest_host.dm index d8431e77..374e612f 100644 --- a/tests/auxtest_host/auxtest_host.dm +++ b/tests/auxtest_host/auxtest_host.dm @@ -50,6 +50,16 @@ var/datum/weak_test_datum F.dir, world.contents, world.vars, global.vars, ) +// 7 is a key here and not a position, and "n" has a null value on purpose +/proc/auxtest_alist_sample() + return alist("a" = 1, 7 = "seven", "n" = null) + +/proc/auxtest_alist_keys(alist/A) + var/list/keys = list() + for (var/k in A) + keys += list(k) + return keys + /proc/auxtest_islist(value) return islist(value) diff --git a/tests/debug_test/src/main.rs b/tests/debug_test/src/main.rs index 4ab6e7db..c6cfdb30 100644 --- a/tests/debug_test/src/main.rs +++ b/tests/debug_test/src/main.rs @@ -155,6 +155,27 @@ fn main() { assert_eq!(eval(&mut client, Some(0), "breakpoint_target()"), "2"); println!("ok: evaluating a proc that has a breakpoint in it"); + // numbers are keys in an alist, so walking it by position shows nonsense + let response = client.request(Request::Eval { + frame_id: Some(0), + command: "alist_sample()".to_owned(), + context: None + }); + let Response::Eval(alist) = response else { + panic!("expected the result of alist_sample(), got {:?}", response); + }; + assert_eq!(alist.value, "/alist {len = 3}"); + let vars = match client.request(Request::Variables { + vars: alist.variables.expect("an alist should be expandable") + }) { + Response::Variables { vars } => vars, + other => panic!("expected Variables, got {:?}", other) + }; + let mut shown: Vec<_> = vars.into_iter().map(|var| var.value).collect(); + shown.sort(); + assert_eq!(shown, ["\"a\" = 1", "\"n\" = null", "7 = \"seven\""]); + println!("ok: an alist shows its key/value pairs"); + assert_eq!(eval(&mut client, None, &dis_command), clean_disassembly); println!("ok: disassembly doesn't show the breakpoint"); diff --git a/tests/debug_test_host/debug_test_host.dm b/tests/debug_test_host/debug_test_host.dm index 3fab357a..7175213d 100644 --- a/tests/debug_test_host/debug_test_host.dm +++ b/tests/debug_test_host/debug_test_host.dm @@ -22,6 +22,9 @@ var/reboot_requested = FALSE /proc/tick_marker() return 1 +/proc/alist_sample() + return alist("a" = 1, 7 = "seven", "n" = null) + // Two seconds of runtime errors, then quiet again. The test detaches while // they are going and comes back after they stop. /proc/start_runtimes() From 613eae95dfe64718a43197f1f8140958355f359e Mon Sep 17 00:00:00 2001 From: Lucy Date: Sat, 3 Oct 2026 23:44:22 -0400 Subject: [PATCH 06/20] fix stale proc pointers when byond moves its proc table (#87) --- auxtools/src/bytecode_manager.rs | 4 +-- auxtools/src/proc.rs | 39 ++++++++++++++++++------------ debug_server/src/mem_profiler.rs | 2 +- debug_server/src/server.rs | 10 ++------ tests/auxtest/src/lib.rs | 1 + tests/auxtest/src/procs.rs | 33 +++++++++++++++++++++++++ tests/auxtest_host/auxtest_host.dm | 11 +++++++++ 7 files changed, 74 insertions(+), 26 deletions(-) create mode 100644 tests/auxtest/src/procs.rs diff --git a/auxtools/src/bytecode_manager.rs b/auxtools/src/bytecode_manager.rs index 6b90015b..2e94a97c 100644 --- a/auxtools/src/bytecode_manager.rs +++ b/auxtools/src/bytecode_manager.rs @@ -59,7 +59,7 @@ pub fn shutdown() { let proc = Proc::from_id(id).unwrap(); unsafe { - raw_types::misc::set_bytecode(*(*proc.entry).metadata.bytecode(), ptr, len); + raw_types::misc::set_bytecode(*(*proc.entry()).metadata.bytecode(), ptr, len); } } @@ -104,6 +104,6 @@ pub fn set_bytecode(proc: &Proc, mut bytecode: Vec) { let len = u16::try_from(len).unwrap(); unsafe { - raw_types::misc::set_bytecode(*(*proc.entry).metadata.bytecode(), ptr, len); + raw_types::misc::set_bytecode(*(*proc.entry()).metadata.bytecode(), ptr, len); } } diff --git a/auxtools/src/proc.rs b/auxtools/src/proc.rs index b2706ddc..2d7f2fa1 100644 --- a/auxtools/src/proc.rs +++ b/auxtools/src/proc.rs @@ -33,11 +33,27 @@ use crate::*; #[derive(Clone)] pub struct Proc { pub id: raw_types::procs::ProcId, - pub entry: *mut raw_types::procs::ProcEntry, pub path: String } +fn entry_by_id(id: raw_types::procs::ProcId) -> *mut raw_types::procs::ProcEntry { + let mut entry: *mut raw_types::procs::ProcEntry = std::ptr::null_mut(); + unsafe { + assert_eq!(raw_types::funcs::get_proc_array_entry(&mut entry, id), 1); + } + entry +} + impl Proc { + /// BYOND's own record for this proc. + /// + /// Don't hold on to the pointer. BYOND moves its whole proc table whenever + /// the table grows (`new` on a verb path does it), so it is only good until + /// the next time DM code runs. + pub fn entry(&self) -> *mut raw_types::procs::ProcEntry { + entry_by_id(self.id) + } + /// Finds the first proc with the given path pub fn find>(path: S) -> Option { get_proc(path) @@ -49,19 +65,12 @@ impl Proc { } pub fn from_id(id: raw_types::procs::ProcId) -> Option { - let mut proc_entry: *mut raw_types::procs::ProcEntry = std::ptr::null_mut(); - unsafe { - assert_eq!(raw_types::funcs::get_proc_array_entry(&mut proc_entry, id), 1); - } + let proc_entry = entry_by_id(id); if proc_entry.is_null() { return None; } - let proc_name = strip_path(unsafe { StringRef::from_id((*proc_entry).path).into() }); - Some(Proc { - id, - entry: proc_entry, - path: proc_name.clone() - }) + let path = strip_path(unsafe { StringRef::from_id((*proc_entry).path).into() }); + Some(Proc { id, path }) } pub unsafe fn file_name(&self) -> Option { @@ -80,14 +89,14 @@ impl Proc { pub fn parameter_names(&self) -> Vec { unsafe { - let (data, count) = raw_types::misc::get_parameters(*(*self.entry).metadata.parameters()); + let (data, count) = raw_types::misc::get_parameters(*(*self.entry()).metadata.parameters()); (0..count).map(|i| StringRef::from_variable_id((*data.add(i)).name)).collect() } } pub fn local_names(&self) -> Vec { unsafe { - let (names, count) = raw_types::misc::get_locals(*(*self.entry).metadata.locals()); + let (names, count) = raw_types::misc::get_locals(*(*self.entry()).metadata.locals()); (0..count).map(|i| StringRef::from_variable_id(*names.add(i))).collect() } } @@ -97,7 +106,7 @@ impl Proc { } pub unsafe fn bytecode_mut_ptr(&self) -> (*mut u32, u16) { - raw_types::misc::get_bytecode(*(*self.entry).metadata.bytecode()) + raw_types::misc::get_bytecode(*(*self.entry()).metadata.bytecode()) } pub unsafe fn bytecode(&self) -> &[u32] { @@ -151,7 +160,7 @@ impl Proc { impl fmt::Debug for Proc { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let path = unsafe { (*self.entry).path }; + let path = unsafe { (*self.entry()).path }; write!(f, "Proc({:?})", unsafe { StringRef::from_id(path) }) } } diff --git a/debug_server/src/mem_profiler.rs b/debug_server/src/mem_profiler.rs index e4490377..f2e61521 100644 --- a/debug_server/src/mem_profiler.rs +++ b/debug_server/src/mem_profiler.rs @@ -211,7 +211,7 @@ impl State { totals[proc_idx].1 += size as u64; } - totals.sort_by(|x, y| x.1.cmp(&y.1)); + totals.sort_by_key(|x| x.1); for (proc, total) in totals { if let Some(proc) = Proc::from_id(proc) { diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index 536dba4c..6ca485f3 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -411,10 +411,7 @@ impl Server { fn get_stack(&self, stack_id: u32) -> Option<&Vec> { let stack_id = stack_id as usize; - let stacks = match &self.state { - Some(state) => &state.stacks, - None => return None - }; + let stacks = &self.state.as_ref()?.stacks; if stack_id == 0 { return Some(&stacks.active); @@ -445,10 +442,7 @@ impl Server { fn get_stack_frame(&self, frame_index: u32) -> Option<&debug::StackFrame> { let mut frame_index = frame_index as usize; - let stacks = match &self.state { - Some(state) => &state.stacks, - None => return None - }; + let stacks = &self.state.as_ref()?.stacks; if frame_index < stacks.active.len() { return Some(&stacks.active[frame_index]); diff --git a/tests/auxtest/src/lib.rs b/tests/auxtest/src/lib.rs index 6a6bf37e..f3d7f0ed 100644 --- a/tests/auxtest/src/lib.rs +++ b/tests/auxtest/src/lib.rs @@ -2,6 +2,7 @@ use auxtools::*; mod hooks; mod lists; +mod procs; mod strings; mod value_from; mod weak; diff --git a/tests/auxtest/src/procs.rs b/tests/auxtest/src/procs.rs new file mode 100644 index 00000000..f125627b --- /dev/null +++ b/tests/auxtest/src/procs.rs @@ -0,0 +1,33 @@ +use auxtools::{raw_types::procs::ProcId, *}; + +// BYOND moves its whole proc table when the table grows. A `Proc` we were +// already holding has to keep working after the move. +#[hook("/proc/auxtest_procs")] +fn test_procs() { + let table_start = || Proc::from_id(ProcId(0)).unwrap().entry(); + + let before: Vec<(Proc, Vec)> = (0..) + .map_while(|id| Proc::from_id(ProcId(id))) + .map(|proc| { + let bytecode = unsafe { proc.bytecode() }.to_vec(); + (proc, bytecode) + }) + .collect(); + let old_start = table_start(); + + Proc::find("/proc/auxtest_grow_proc_table") + .ok_or_else(|| runtime!("test_procs: /proc/auxtest_grow_proc_table not defined"))? + .call(&[])?; + + if table_start() == old_start { + return Err(runtime!("test_procs: the proc table never moved, so this checked nothing")); + } + + for (proc, bytecode) in before { + if unsafe { proc.bytecode() } != bytecode { + return Err(runtime!("test_procs: {} reads different bytecode after the proc table moved", proc.path)); + } + } + + Ok(Value::from(true)) +} diff --git a/tests/auxtest_host/auxtest_host.dm b/tests/auxtest_host/auxtest_host.dm index 374e612f..69110711 100644 --- a/tests/auxtest_host/auxtest_host.dm +++ b/tests/auxtest_host/auxtest_host.dm @@ -73,6 +73,13 @@ var/datum/weak_test_datum /proc/auxtest_new_discard() new /datum/auxtest_discard +// `new` on a verb path with a name BYOND has not seen yet adds one entry to its +// proc table, and the whole table moves when it grows +/proc/auxtest_grow_proc_table() + var/mob/M = new + for (var/i in 1 to 4096) + new /mob/verb/auxtest_verb(M, "auxtest clone [i]") + // Tests /proc/auxtest_hooks() CRASH() @@ -80,6 +87,9 @@ var/datum/weak_test_datum /proc/auxtest_lists() CRASH() +/proc/auxtest_procs() + CRASH() + /proc/auxtest_strings() CRASH() @@ -98,6 +108,7 @@ var/datum/weak_test_datum // Tests ASSERT(auxtest_hooks() == TRUE) ASSERT(auxtest_lists() == TRUE) + ASSERT(auxtest_procs() == TRUE) ASSERT(auxtest_strings() == TRUE) ASSERT(auxtest_value_from() == TRUE) From c8913f47f14b32365a417c49cce4edf4ea4677a8 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sat, 3 Oct 2026 23:48:37 -0400 Subject: [PATCH 07/20] test calling a proc from the library that hooked it (#44) --- tests/auxtest/src/hooks.rs | 22 ++++++++++++++++++++++ tests/auxtest_host/auxtest_host.dm | 10 ++++++++++ 2 files changed, 32 insertions(+) diff --git a/tests/auxtest/src/hooks.rs b/tests/auxtest/src/hooks.rs index 1d932c97..2aba691e 100644 --- a/tests/auxtest/src/hooks.rs +++ b/tests/auxtest/src/hooks.rs @@ -7,6 +7,16 @@ fn discard_new() { Value::from_string(DISCARDED) } +#[hook("/proc/auxtest_hooked_global")] +fn hooked_global(x: Value) { + Ok(Value::from(x.as_number()? + 1.0)) +} + +#[hook("/datum/auxtest_hooked/proc/hooked_method")] +fn hooked_method(x: Value) { + Ok(Value::from(x.as_number()? * 2.0)) +} + #[hook("/proc/auxtest_hooks")] fn test_hooks() { use raw_types::{funcs, strings}; @@ -27,5 +37,17 @@ fn test_hooks() { } } + // calling a proc we hooked ourselves has to land in the hook, by id and by name + let by_id = Proc::find("/proc/auxtest_hooked_global").unwrap().call(&[&Value::from(41.0)])?; + if by_id.as_number()? != 42.0 { + return Err(runtime!("test_hooks: calling a hooked global proc from Rust did not reach the hook")); + } + + let datum = Proc::find("/proc/auxtest_new_hooked").unwrap().call(&[])?; + let by_name = datum.call("hooked_method", &[&Value::from(21.0)])?; + if by_name.as_number()? != 42.0 { + return Err(runtime!("test_hooks: calling a hooked datum proc from Rust did not reach the hook")); + } + Ok(Value::from(true)) } diff --git a/tests/auxtest_host/auxtest_host.dm b/tests/auxtest_host/auxtest_host.dm index 69110711..768dd7a9 100644 --- a/tests/auxtest_host/auxtest_host.dm +++ b/tests/auxtest_host/auxtest_host.dm @@ -73,6 +73,16 @@ var/datum/weak_test_datum /proc/auxtest_new_discard() new /datum/auxtest_discard +// Both hooked, and then called from Rust by the same library that hooked them +/proc/auxtest_hooked_global(x) + CRASH() + +/datum/auxtest_hooked/proc/hooked_method(x) + CRASH() + +/proc/auxtest_new_hooked() + return new /datum/auxtest_hooked + // `new` on a verb path with a name BYOND has not seen yet adds one entry to its // proc table, and the whole table moves when it grows /proc/auxtest_grow_proc_table() From de6e30b731ab4cce7835d191035ca4755c786d6c Mon Sep 17 00:00:00 2001 From: Lucy Date: Sat, 3 Oct 2026 23:55:56 -0400 Subject: [PATCH 08/20] make a failed auxtools_init stay failed (#27) --- auxtools/src/lib.rs | 71 ++++++++++++++++-------------- tests/auxtest/src/lib.rs | 24 ++++++++-- tests/auxtest_host/auxtest_host.dm | 27 +++++++++++- 3 files changed, 84 insertions(+), 38 deletions(-) diff --git a/auxtools/src/lib.rs b/auxtools/src/lib.rs index 2015100f..eafc8170 100644 --- a/auxtools/src/lib.rs +++ b/auxtools/src/lib.rs @@ -3,7 +3,9 @@ //#[cfg(not(target_pointer_width = "32"))] // compile_error!("Auxtools must be compiled for a 32-bit target"); -mod byond_ffi; +// public so `byond_ffi_fn!` can reach it from another crate +#[doc(hidden)] +pub mod byond_ffi; mod bytecode_manager; pub mod debug; mod hooks; @@ -20,7 +22,10 @@ mod value_from; pub mod version; mod weak_value; -use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + OnceLock +}; pub use auxtools_impl::{full_shutdown, hook, init, pin_dll, runtime_handler, shutdown}; /// For crates that need to find something else in BYOND's binary. @@ -75,9 +80,29 @@ fn pin_dll() -> Result<(), ()> { Ok(()) } +// The first failed init. Trying again would run setup on top of what the +// failed attempt left behind, so every later call hands this back instead. +static INIT_FAILURE: OnceLock = OnceLock::new(); + byond_ffi_fn! { auxtools_init(_input) { + if let Some(failure) = INIT_FAILURE.get() { + return Some(failure.clone()); + } + + match try_init() { + Ok(()) => Some("SUCCESS".to_owned()), + Err(e) => { + // a hook must not fire in a library that only got half set up + hooks::clear_hooks(); + proc::clear_procs(); + Some(INIT_FAILURE.get_or_init(|| format!("FAILED ({})", e)).clone()) + } + } +} } + +fn try_init() -> Result<(), String> { if get_init_level() == InitLevel::None { - return Some("SUCCESS".to_owned()) + return Ok(()); } let mut did_full = false; @@ -85,40 +110,24 @@ byond_ffi_fn! { auxtools_init(_input) { if get_init_level() == InitLevel::Full { did_full = true; - if let Err(e) = version::init() { - return Some(format!("FAILED ({})", e)); - } - - if let Err(e) = symbols::resolve_full() { - return Some(format!("FAILED ({})", e)); - } - - if pin_dll().is_err() { - return Some("FAILED (Could not pin the library in memory.)".to_owned()); - } - - if hooks::init().is_err() { - return Some("Failed (Couldn't initialize proc hooking)".to_owned()); - } + version::init()?; + symbols::resolve_full()?; + pin_dll().map_err(|()| "Could not pin the library in memory.".to_owned())?; + hooks::init().map_err(|_| "Couldn't initialize proc hooking".to_owned())?; set_init_level(InitLevel::Partial); } - if get_init_level() == InitLevel::Partial { did_partial = true; // This is a heap ptr so fetch it on partial loads - if let Err(e) = symbols::resolve_partial() { - return Some(format!("FAILED ({})", e)); - } + symbols::resolve_partial()?; proc::populate_procs(); for cthook in inventory::iter:: { - if let Err(e) = hooks::hook(cthook.proc_path, cthook.hook) { - return Some(format!("FAILED (Could not hook proc {}: {:?})", cthook.proc_path, e)); - } + hooks::hook(cthook.proc_path, cthook.hook).map_err(|e| format!("Could not hook proc {}: {:?}", cthook.proc_path, e))?; } set_init_level(InitLevel::None); } @@ -130,19 +139,15 @@ byond_ffi_fn! { auxtools_init(_input) { // Run user-defined initializers if did_full { - if let Err(err) = init::run_full_init() { - return Some(format!("FAILED ({})", err)); - } + init::run_full_init()?; } if did_partial { - if let Err(err) = init::run_partial_init() { - return Some(format!("FAILED ({})", err)); - } + init::run_partial_init()?; } - Some("SUCCESS".to_owned()) -} } + Ok(()) +} byond_ffi_fn! { auxtools_shutdown(_input) { if get_init_level() != InitLevel::None { diff --git a/tests/auxtest/src/lib.rs b/tests/auxtest/src/lib.rs index f3d7f0ed..7dbd0784 100644 --- a/tests/auxtest/src/lib.rs +++ b/tests/auxtest/src/lib.rs @@ -1,3 +1,5 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + use auxtools::*; mod hooks; @@ -17,8 +19,24 @@ fn inc_counter() { })) } -#[hook("/proc/auxtest_out")] -fn out(msg: Value) { - eprintln!("\n{}", msg.as_string()?); +// Not a hook, so a failure still gets reported when hooks are what broke +byond_ffi_fn! { auxtest_out(msg) { + eprintln!("\n{}", msg); + Some(String::new()) +} } + +static FAIL_NEXT_INIT: AtomicBool = AtomicBool::new(false); + +#[hook("/proc/auxtest_fail_next_init")] +fn fail_next_init() { + FAIL_NEXT_INIT.store(true, Ordering::Relaxed); Ok(Value::NULL) } + +#[init(partial)] +fn fail_init_on_request() -> Result<(), String> { + if FAIL_NEXT_INIT.load(Ordering::Relaxed) { + return Err("auxtest asked for this init to fail".to_owned()); + } + Ok(()) +} diff --git a/tests/auxtest_host/auxtest_host.dm b/tests/auxtest_host/auxtest_host.dm index 768dd7a9..11f88018 100644 --- a/tests/auxtest_host/auxtest_host.dm +++ b/tests/auxtest_host/auxtest_host.dm @@ -11,12 +11,30 @@ /proc/auxtools_stack_trace(msg) CRASH(msg) -/proc/auxtest_out() - // Graceful failure +// Goes straight to the library instead of through a hook, so a failure still +// gets reported when hooks are what broke +/proc/auxtest_out(msg) + call_ext(auxtools_test_dll(), "auxtest_out")(msg) /proc/auxtest_inc_counter() CRASH() +/proc/auxtest_fail_next_init() + CRASH() + +// A failed init has to stay failed, and leave none of its hooks behind +/proc/auxtest_failed_init(auxtest_dll) + var/first = call_ext(auxtest_dll, "auxtools_init")() + ASSERT(findtext(first, "FAILED") == 1) + ASSERT(call_ext(auxtest_dll, "auxtools_init")() == first) + + var/reached_hook = TRUE + try + auxtest_hooks() + catch + reached_hook = FALSE + ASSERT(!reached_hook) + /proc/concat_strings(a, b) return addtext(a, b) @@ -128,6 +146,11 @@ var/datum/weak_test_datum // Stop testing after the 8th reboot if (auxtest_inc_counter() == 8) + // last, because nothing can init again in this process afterwards + auxtest_fail_next_init() + call_ext(auxtest_dll, "auxtools_shutdown")() + auxtest_failed_init(auxtest_dll) + auxtest_out("SUCCESS: Finished") call_ext(auxtest_dll, "auxtools_shutdown")() shutdown() From b9a5f8388b39aeb5736e87bac56c6825e87134b3 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 00:00:32 -0400 Subject: [PATCH 09/20] use the real crate version in the debug server's help text --- debug_server/src/server.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index 6ca485f3..20e5864d 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -116,7 +116,7 @@ struct ServerThread { impl Server { pub fn setup_app() -> Command<'static> { Command::new("Auxtools Debug Server") - .version("2.2.2") + .version(env!("CARGO_PKG_VERSION")) .subcommand_required(true) .no_binary_name(true) .color(clap::ColorChoice::Never) From 9d07a8d2606d5c0617a6ba877b9834b4c4af76c9 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 00:01:18 -0400 Subject: [PATCH 10/20] run cargo fmt --- auxcov/src/codecov.rs | 8 +++++--- auxtools/src/debug.rs | 4 ++-- auxtools/src/list.rs | 9 ++++++--- auxtools/src/proc.rs | 5 +++-- auxtools/src/raw_types/procs.rs | 6 +++--- auxtools/src/string.rs | 8 ++++---- auxtools/src/symbols.rs | 3 ++- auxtools/src/symbols/linux.rs | 9 +++++---- auxtools/src/value_from.rs | 3 ++- debug_server/src/assemble_env.rs | 3 ++- debug_server/src/instruction_hooking.rs | 27 ++++++++++++++----------- debug_server/src/server.rs | 19 +++++++++-------- tests/auxtest/src/hooks.rs | 6 ++++-- tests/auxtest/src/strings.rs | 12 +++++------ tests/test_runner/src/main.rs | 4 ++-- 15 files changed, 72 insertions(+), 54 deletions(-) diff --git a/auxcov/src/codecov.rs b/auxcov/src/codecov.rs index 51aefb1c..714b5c67 100644 --- a/auxcov/src/codecov.rs +++ b/auxcov/src/codecov.rs @@ -170,7 +170,8 @@ impl Tracker { fn finalize(&mut self) -> Result<(), Vec> { let mut errors_option = None; for context in &self.contexts { - let result = context.finalize(); // dropping the results because what can ya do? + let result = context.finalize(); // dropping the results because + // what can ya do? if let Err(error) = result { match &mut errors_option { None => { @@ -195,8 +196,9 @@ impl Tracker { impl Drop for Tracker { fn drop(&mut self) { - let _result = self.finalize(); // dropping the result here because what can ya - // do? + let _result = self.finalize(); // dropping the result here because what + // can ya + // do? } } diff --git a/auxtools/src/debug.rs b/auxtools/src/debug.rs index 6f036c91..bc45db7f 100644 --- a/auxtools/src/debug.rs +++ b/auxtools/src/debug.rs @@ -36,8 +36,8 @@ impl StackFrame { let src = Value::from_raw((*instance).src); let dot = Value::from_raw(*(*context).dot()); - // Make sure to handle arguments/locals with no names (when there are more - // values than names) + // Make sure to handle arguments/locals with no names (when there are + // more values than names) let args = (0..(*instance).args_count()) .map(|i| { let name = param_names.get(i as usize).cloned(); diff --git a/auxtools/src/list.rs b/auxtools/src/list.rs index fc52d621..9453824f 100644 --- a/auxtools/src/list.rs +++ b/auxtools/src/list.rs @@ -105,7 +105,8 @@ impl List { /// Every key and value of an `/alist`, in the order `for (var/k in A)` /// gives them. /// - /// This is a copy, so nothing here points into BYOND's tree once it returns. + /// This is a copy, so nothing here points into BYOND's tree once it + /// returns. pub fn alist_pairs(&self) -> DMResult> { if !self.is_alist() { return Err(runtime!("attempted to read alist pairs of a non-alist")); @@ -113,11 +114,13 @@ impl List { let id = unsafe { self.value.raw.data.id } as usize; let record = unsafe { - // raw reads of BYOND's table, so a stale id has to be turned away here + // raw reads of BYOND's table, so a stale id has to be turned away + // here if id >= *raw_types::funcs::ALIST_TABLE_COUNT as usize { return Err(runtime!("alist id {} is outside the alist table", id)); } - // BYOND reallocates the table as it grows, so read the pointer every time + // BYOND reallocates the table as it grows, so read the pointer + // every time *(*raw_types::funcs::ALIST_TABLE).add(id) }; if record.is_null() { diff --git a/auxtools/src/proc.rs b/auxtools/src/proc.rs index 2d7f2fa1..7b50f4d5 100644 --- a/auxtools/src/proc.rs +++ b/auxtools/src/proc.rs @@ -140,8 +140,9 @@ impl Proc { let args: Vec<_> = args.iter().map(|e| e.raw).collect(); - // 2 is the proc_type BYOND's own direct calls pass. It never passes 0, and - // a `..()` with no parent hands back garbage in a frame that has it. + // 2 is the proc_type BYOND's own direct calls pass. It never passes + // 0, and a `..()` with no parent hands back garbage in + // a frame that has it. if raw_types::funcs::call_proc_by_id(&mut ret, Value::NULL.raw, 2, self.id, 0, Value::NULL.raw, args.as_ptr(), args.len(), 0, 0) == 1 { return Ok(Value::from_raw_owned(ret)); } diff --git a/auxtools/src/raw_types/procs.rs b/auxtools/src/raw_types/procs.rs index 3ca9cdde..0f25846f 100644 --- a/auxtools/src/raw_types/procs.rs +++ b/auxtools/src/raw_types/procs.rs @@ -179,9 +179,9 @@ pub struct SuspendedProcs { mod layout_tests { use std::mem::{offset_of, size_of}; - // The numbers below are read off the binaries, 516.1688: what BYOND allocates - // for each struct and where its own code reaches into them. The ones for - // older builds are from byond-re's notes. + // The numbers below are read off the binaries, 516.1688: what BYOND + // allocates for each struct and where its own code reaches into them. + // The ones for older builds are from byond-re's notes. #[test] fn execution_context_linux_and_old_windows() { diff --git a/auxtools/src/string.rs b/auxtools/src/string.rs index 0d5c6f22..b9dac04d 100644 --- a/auxtools/src/string.rs +++ b/auxtools/src/string.rs @@ -25,8 +25,8 @@ impl StringRef { return None; } - // Here we're going from value -> raw -> new value because to get that juicy - // static lifetime + // Here we're going from value -> raw -> new value because to get that + // juicy static lifetime Some(StringRef { value: unsafe { Value::from_raw(value.raw) } }) @@ -86,8 +86,8 @@ impl fmt::Debug for StringRef { while let Some(&byte) = iter.next() { if byte == 0xFF { - // NOTE: Doesn't hold state for formatting, so some strings relying on are a - // little off + // NOTE: Doesn't hold state for formatting, so some strings + // relying on are a little off format.extend_from_slice(match iter.next() { None => break, Some(1) | Some(2) | Some(3) | Some(4) => b"[]", diff --git a/auxtools/src/symbols.rs b/auxtools/src/symbols.rs index 81fca593..9b787d11 100644 --- a/auxtools/src/symbols.rs +++ b/auxtools/src/symbols.rs @@ -107,7 +107,8 @@ pub(crate) fn resolve_full() -> Result<(), String> { /// runs on every partial init and not only the first. pub(crate) fn resolve_partial() -> Result<(), String> { let (module, build) = module_and_build()?; - // the recipe finds the name array, which is not where the table starts on Linux + // the recipe finds the name array, which is not where the table starts on + // Linux let address = resolve(&module, build, "variable_names")? - std::mem::offset_of!(VariableNameIdTable, entries); unsafe { funcs::VARIABLE_NAMES = address as *const VariableNameIdTable; diff --git a/auxtools/src/symbols/linux.rs b/auxtools/src/symbols/linux.rs index 42c24901..b224cc5e 100644 --- a/auxtools/src/symbols/linux.rs +++ b/auxtools/src/symbols/linux.rs @@ -9,9 +9,9 @@ use byond_scan::{Anchor, Extract, OperandSelect, Recipe, SignatureTreatment, Ver // resolver, newer ones are tried and can still fail. // // The floor is 1664 and not Windows' 1659 because of proc hooks. On 1659 GCC -// inlined `call_proc_by_id` into the interpreter, so a DM proc call never enters -// the function the hook patches and no `#[hook]` ever fires. 1660 to 1663 were -// never checked. +// inlined `call_proc_by_id` into the interpreter, so a DM proc call never +// enters the function the hook patches and no `#[hook]` ever fires. 1660 to +// 1663 were never checked. const SUPPORTED: VersionRange = VersionRange { min: 1664, max: 1688 }; // The first build where `remove_from_list` takes its arguments on the stack. @@ -39,7 +39,8 @@ const STRING_ACCESSOR_SIG: &str = "56 53 83 EC 14 8B 44 24 20 39 05 ?? ?? ?? ?? // The 0x0F/0x55 tag dispatch identifies the accessor that reads the alist // table. Operands are count=0 and pointer=1. -const ALIST_SIG: &str = "8B 54 24 04 8B 4C 24 08 80 FA 0F 74 ?? 31 C0 80 FA 55 75 ?? 3B 0D ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? 8B 04 88 85 C0 74 ?? 8B 00 C3"; +const ALIST_SIG: &str = + "8B 54 24 04 8B 4C 24 08 80 FA 0F 74 ?? 31 C0 80 FA 55 75 ?? 3B 0D ?? ?? ?? ?? 73 ?? A1 ?? ?? ?? ?? 8B 04 88 85 C0 74 ?? 8B 00 C3"; pub(crate) const RECIPES: &[Recipe] = &[ Recipe { diff --git a/auxtools/src/value_from.rs b/auxtools/src/value_from.rs index edb89de5..c8dd898c 100644 --- a/auxtools/src/value_from.rs +++ b/auxtools/src/value_from.rs @@ -72,7 +72,8 @@ impl + Clone, B: Into + Clone> TryFrom<&HashMap> for for (k, v) in hashmap { // This can fail for basically any reason that BYOND decides, - // because in the end this just ends up calling into BYOND with the Value's. + // because in the end this just ends up calling into BYOND with the + // Value's. res.set(k.clone(), v.clone())?; } diff --git a/debug_server/src/assemble_env.rs b/debug_server/src/assemble_env.rs index ec6ddefe..57b9fcf3 100644 --- a/debug_server/src/assemble_env.rs +++ b/debug_server/src/assemble_env.rs @@ -7,7 +7,8 @@ impl dmasm::assembler::AssembleEnv for AssembleEnv { let string = StringRef::from_raw(string).ok()?; let id = string.get_id(); - // We leak here because the assembled code now holds a reference to this string + // We leak here because the assembled code now holds a reference to this + // string std::mem::forget(string); Some(id.0) diff --git a/debug_server/src/instruction_hooking.rs b/debug_server/src/instruction_hooking.rs index 319daab3..d57fd792 100644 --- a/debug_server/src/instruction_hooking.rs +++ b/debug_server/src/instruction_hooking.rs @@ -212,8 +212,8 @@ fn handle_runtime(error: &str) { impl InstructionHook for Server { fn handle_instruction(&mut self, ctx: *mut raw_types::procs::ExecutionContext) { - // Always handle the deferred instruction replacement first - everything else - // will depend on it + // Always handle the deferred instruction replacement first - everything + // else will depend on it unsafe { let deferred = DEFERRED_INSTRUCTION_REPLACE.get(); if let Some((src, dst)) = &*deferred { @@ -239,8 +239,8 @@ impl InstructionHook for Server { let opcode = unsafe { *opcode_ptr }; let is_dbgline = opcode == OPCODE_DBGLINE; - // This lets us ignore any actual breakpoints we hit if we've already paused for - // another reason + // This lets us ignore any actual breakpoints we hit if we've already + // paused for another reason let mut did_breakpoint = false; unsafe { @@ -268,8 +268,9 @@ impl InstructionHook for Server { if is_dbgline && target.is(*(*ctx).proc_instance()) { CURRENT_ACTION = DebuggerAction::BreakOnNext; } else { - // If the context isn't in any stacks, it has just returned. Break! - // TODO: Don't break if the context's stack is gone (returned to C) + // If the context isn't in any stacks, it has just + // returned. Break! TODO: Don't + // break if the context's stack is gone (returned to C) if !proc_instance_is_in_stack(ctx, target) && !proc_instance_is_suspended(target) { CURRENT_ACTION = DebuggerAction::None; CURRENT_ACTION = handle_breakpoint(ctx, BreakpointReason::Step); @@ -292,8 +293,10 @@ impl InstructionHook for Server { let in_stack = proc_instance_is_in_stack(ctx, parent); let is_suspended = proc_instance_is_suspended(parent); - // If the context isn't in any stacks, it has just returned. Break! - // TODO: Don't break if the context's stack is gone (returned to C) + // If the context isn't in any stacks, it has just + // returned. Break! + // TODO: Don't break if the context's stack is gone + // (returned to C) if !in_stack && !is_suspended { CURRENT_ACTION = DebuggerAction::None; CURRENT_ACTION = handle_breakpoint(ctx, BreakpointReason::Step); @@ -335,8 +338,8 @@ impl InstructionHook for Server { } } - // ORIGINAL_BYTECODE won't contain an entry if this breakpoint has already been - // removed + // ORIGINAL_BYTECODE won't contain an entry if this breakpoint has + // already been removed let map = ORIGINAL_BYTECODE.lock().unwrap(); if let Some(original) = map.get(&PtrKey::new(opcode_ptr)) { unsafe { @@ -432,8 +435,8 @@ pub fn unhook_instruction(proc: &Proc, offset: u32) -> Result<(), InstructionUnh bytecode.as_mut_ptr().add(offset as usize) }; - // ORIGINAL_BYTECODE won't contain an entry if this breakpoint has already been - // removed + // ORIGINAL_BYTECODE won't contain an entry if this breakpoint has already + // been removed let mut map = ORIGINAL_BYTECODE.lock().unwrap(); if let Some(original) = map.get(&PtrKey::new(opcode_ptr)) { unsafe { diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index 20e5864d..d144347e 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -378,8 +378,8 @@ impl Server { } fn object_to_variables(&mut self, value: &Value) -> Result, Runtime> { - // Grab `value.vars`. We have a little hack for globals which use a special - // type. + // Grab `value.vars`. We have a little hack for globals which use a + // special type. let vars = List::from_value(&unsafe { if value.raw.tag == ValueTag::World && value.raw.data.id == 1 { Value::new(ValueTag::GlobalVars, ValueData { id: 0 }) @@ -389,7 +389,8 @@ impl Server { })?; let mut variables = vec![]; - let mut top_variables = vec![]; // These fields get displayed on top of all others + let mut top_variables = vec![]; // These fields get displayed on top of + // all others for i in 1..=vars.len() { let name = vars.get(i)?.as_string()?; @@ -858,8 +859,9 @@ impl Server { let result = match proc.call(&arg_values) { Ok(res) => { if let Ok(list) = res.as_list() { - // The rest are the potentially mutated parameters. We need to commit them to - // the function that called us. TODO: This sucks, obviously. + // The rest are the potentially mutated parameters. We need + // to commit them to the function that + // called us. TODO: This sucks, obviously. let len = list.len(); for i in 2..=len { let value = list.get(i).unwrap(); @@ -1087,7 +1089,8 @@ impl Server { self.state = Some(State::new()); - // Exit now if this is a conditional breakpoint and the condition doesn't pass! + // Exit now if this is a conditional breakpoint and the condition + // doesn't pass! if reason == BreakpointReason::Breakpoint { let proc = unsafe { (*(*(*_ctx).proc_instance())).proc }; let offset = unsafe { *(*_ctx).bytecode_offset() }; @@ -1101,8 +1104,8 @@ impl Server { } } - // We might have just executed some code so invalidate the stacks we already - // fetched + // We might have just executed some code so invalidate the + // stacks we already fetched self.state.as_mut().unwrap().invalidate_stacks(); } } diff --git a/tests/auxtest/src/hooks.rs b/tests/auxtest/src/hooks.rs index 2aba691e..baab4339 100644 --- a/tests/auxtest/src/hooks.rs +++ b/tests/auxtest/src/hooks.rs @@ -29,7 +29,8 @@ fn test_hooks() { assert_eq!(funcs::get_string_table_entry(&mut entry, held.get_id()), 1); // BYOND runs New() with the "don't want the result" bit set and never - // releases what comes back, so the hook's result has to be dropped for it + // releases what comes back, so the hook's result has to be dropped for + // it let before = (*entry).ref_count; Proc::find("/proc/auxtest_new_discard").unwrap().call(&[])?; if (*entry).ref_count != before { @@ -37,7 +38,8 @@ fn test_hooks() { } } - // calling a proc we hooked ourselves has to land in the hook, by id and by name + // calling a proc we hooked ourselves has to land in the hook, by id and by + // name let by_id = Proc::find("/proc/auxtest_hooked_global").unwrap().call(&[&Value::from(41.0)])?; if by_id.as_number()? != 42.0 { return Err(runtime!("test_hooks: calling a hooked global proc from Rust did not reach the hook")); diff --git a/tests/auxtest/src/strings.rs b/tests/auxtest/src/strings.rs index fc5ca5f3..1158a4e7 100644 --- a/tests/auxtest/src/strings.rs +++ b/tests/auxtest/src/strings.rs @@ -30,8 +30,8 @@ fn test_strings() { return Err(runtime!("test_string: string_a's reference count != 0")); } - // Creating a value from our strings should result in both having a reference - // count of 1. + // Creating a value from our strings should result in both having a + // reference count of 1. let value_a = Value::new(values::ValueTag::String, values::ValueData { string: string_a }); let value_b = Value::new(values::ValueTag::String, values::ValueData { string: string_b }); @@ -46,8 +46,8 @@ fn test_strings() { let concatenated = Proc::find("/proc/concat_strings").unwrap().call(&[&value_a, &value_b])?; - // Returned value should be equal to string_a_contents .. string_b_contents - // and have a ref count of 1 + // Returned value should be equal to string_a_contents .. + // string_b_contents and have a ref count of 1 if concatenated.raw.tag != values::ValueTag::String { return Err(runtime!("test_string: concat_strings did not return a string")); } @@ -66,8 +66,8 @@ fn test_strings() { return Err(runtime!("test_string: expected_concat != actual_concat")); } - // The strings should still have a reference count of 1 after concat_strings has - // used them + // The strings should still have a reference count of 1 after + // concat_strings has used them if (*string_a_entry).ref_count != 1 { return Err(runtime!("test_string: string_a's reference count != 1 after concat_strings")); } diff --git a/tests/test_runner/src/main.rs b/tests/test_runner/src/main.rs index 75659151..c3fb2839 100644 --- a/tests/test_runner/src/main.rs +++ b/tests/test_runner/src/main.rs @@ -8,8 +8,8 @@ fn main() { let res = Command::new(paths::find_dm()).with_byond_paths().arg(paths::find_dme()).status().unwrap(); assert!(res.success(), "dreamdaemon build failed"); - // Here we depend on BYOND not fucking with stderr too much so we can hijack it - // for our own communication + // Here we depend on BYOND not fucking with stderr too much so we can hijack + // it for our own communication let output = Command::new(paths::find_dreamdaemon()) .with_byond_paths() .env("AUXTEST_DLL", paths::find_dll()) From c1d69db96e29813e778a7d9c6221ce8b84ba695d Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 00:19:40 -0400 Subject: [PATCH 11/20] fix debug server shutdown hang and stale paused frames after a panic --- debug_server/Cargo.toml | 2 +- debug_server/src/server.rs | 58 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/debug_server/Cargo.toml b/debug_server/Cargo.toml index 17cf6074..0ac80ddd 100644 --- a/debug_server/Cargo.toml +++ b/debug_server/Cargo.toml @@ -23,7 +23,7 @@ region = "3" retour = { workspace = true } [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["libloaderapi", "errhandlingapi", "processthreadsapi"] } +winapi = { version = "0.3", features = ["libloaderapi", "errhandlingapi", "ioapiset", "processthreadsapi"] } [lints] workspace = true diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index d144347e..18dc992a 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -1078,6 +1078,9 @@ impl Server { pub fn recover_from_panic(&mut self) { eprintln!("Debug server panicked, dropping the debug client"); self.in_eval = false; + // A panic mid-pause skips the cleanup at the end of handle_breakpoint. + // The frames in here point at procs that are about to carry on running. + self.state = None; self.disconnect(); } @@ -1186,6 +1189,16 @@ impl Server { eprintln!("Debug server disconnecting"); let _ = Self::send(&mut stream, &Response::Disconnect); let _ = stream.shutdown(std::net::Shutdown::Both); + + // On Windows a shutdown stops later reads, but the one the + // networking thread is already sat in waits for the client to hang + // up too. Server::drop joins that thread, so don't leave it up to + // the client. + #[cfg(windows)] + unsafe { + use std::os::windows::io::AsRawSocket; + winapi::um::ioapiset::CancelIoEx(stream.as_raw_socket() as _, std::ptr::null_mut()); + } } // Whoever connects next sets up their own breakpoints. Until then the @@ -1220,6 +1233,16 @@ impl Drop for Server { return; } + // A debug-client the thread accepted but we never got told about is + // still in the queue, and the thread is stuck reading from it. Hang up + // on whoever turns up. The channel closes once the thread is gone. + while let Ok(event) = self.events.recv() { + if let Event::Connected(stream) = event { + self.stream = Some(stream); + self.disconnect(); + } + } + if let Some(thread) = self.thread.take() { let _ = thread.join(); } @@ -1325,3 +1348,38 @@ impl ServerThread { eprintln!("Debug client connection closed"); } } + +#[cfg(test)] +mod tests { + use std::{ + net::{Ipv4Addr, SocketAddr, TcpListener, TcpStream}, + sync::mpsc, + thread, + time::Duration + }; + + use super::Server; + + #[test] + fn drop_hangs_up_on_a_client_it_never_processed() { + let port = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap().local_addr().unwrap().port(); + let addr = SocketAddr::from((Ipv4Addr::LOCALHOST, port)); + let (done, dropped) = mpsc::channel(); + + // A hung drop never returns, so it gets its own thread to be stuck on + thread::spawn(move || { + let server = Server::listen(&addr).unwrap(); + let _client = TcpStream::connect(addr).unwrap(); + // Long enough for the networking thread to start reading from the + // client. Nothing here tells the server it connected. + thread::sleep(Duration::from_millis(200)); + + drop(server); + let _ = done.send(()); + }); + + dropped + .recv_timeout(Duration::from_secs(5)) + .expect("dropping the server hung on a client that was still connected"); + } +} From db65c05d6f9c7f41809e5fc1403ebd75bc96002a Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 00:31:03 -0400 Subject: [PATCH 12/20] keep Proc off other threads, stop the debug listener when the OS picked its port --- auxtools/src/proc.rs | 21 ++++++++++++++++++--- debug_server/src/server.rs | 18 ++++++++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/auxtools/src/proc.rs b/auxtools/src/proc.rs index 7b50f4d5..f48f65ab 100644 --- a/auxtools/src/proc.rs +++ b/auxtools/src/proc.rs @@ -1,7 +1,8 @@ use std::{ cell::RefCell, collections::{hash_map::Entry, HashMap}, - fmt + fmt, + marker::PhantomData }; use ahash::RandomState; @@ -30,10 +31,20 @@ use crate::*; // [get_proc] retrieves the base proc. /// Used to hook and call procs. +/// +/// A `Proc` stays on the thread BYOND runs on. Calling one from anywhere else +/// would run DM code while the game is in the middle of something else. +/// +/// ```compile_fail +/// fn assert_send() {} +/// assert_send::(); +/// ``` #[derive(Clone)] pub struct Proc { pub id: raw_types::procs::ProcId, - pub path: String + pub path: String, + // Keeps a Proc from being sent to another thread + phantom: PhantomData<*mut ()> } fn entry_by_id(id: raw_types::procs::ProcId) -> *mut raw_types::procs::ProcEntry { @@ -70,7 +81,11 @@ impl Proc { return None; } let path = strip_path(unsafe { StringRef::from_id((*proc_entry).path).into() }); - Some(Proc { id, path }) + Some(Proc { + id, + path, + phantom: PhantomData + }) } pub unsafe fn file_name(&self) -> Option { diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index 18dc992a..8e8327c4 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -189,13 +189,17 @@ impl Server { let (events_sender, events_receiver) = mpsc::channel(); let stop = Arc::new(AtomicBool::new(false)); - let thread = ServerThread { events: events_sender }.spawn_listener(TcpListener::bind(addr)?, stop.clone()); + let listener = TcpListener::bind(addr)?; + // Asking for port 0 gets whichever port the OS picks, and Server::drop + // has to connect to that one + let listen_addr = listener.local_addr()?; + let thread = ServerThread { events: events_sender }.spawn_listener(listener, stop.clone()); Ok(Server { events: events_receiver, stream: None, thread: Some(thread), - listen_addr: Some(*addr), + listen_addr: Some(listen_addr), stop, should_catch_runtimes: true, state: None, @@ -1360,6 +1364,16 @@ mod tests { use super::Server; + #[test] + fn drop_stops_a_listener_on_a_port_the_os_picked() { + let server = Server::listen(&SocketAddr::from((Ipv4Addr::LOCALHOST, 0))).unwrap(); + // The listener thread holds the only other copy, until it exits + let stop = server.stop.clone(); + + drop(server); + assert_eq!(std::sync::Arc::strong_count(&stop), 1, "the listener thread outlived the server"); + } + #[test] fn drop_hangs_up_on_a_client_it_never_processed() { let port = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap().local_addr().unwrap().port(); From 9304bb4221345a7004c9218cf88fce0422feceb4 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 00:36:13 -0400 Subject: [PATCH 13/20] export HookFailure and ProcHook, fix the hook doc example --- auxtools-impl/src/lib.rs | 2 +- auxtools/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/auxtools-impl/src/lib.rs b/auxtools-impl/src/lib.rs index e65a7cf1..bb284e90 100644 --- a/auxtools-impl/src/lib.rs +++ b/auxtools-impl/src/lib.rs @@ -138,7 +138,7 @@ pub fn pin_dll(attr: TokenStream) -> TokenStream { /// /// Here we define a hook that multiplies a number passed to it by two. /// It can now be used to hook procs, for example -/// `hooks::hook("/proc/double_up", double_up);` +/// `Proc::find("/proc/double_up").unwrap().hook(double_up);` /// ```ignore /// #[hook] /// fn double_up(num: Value) { diff --git a/auxtools/src/lib.rs b/auxtools/src/lib.rs index eafc8170..aac99dd8 100644 --- a/auxtools/src/lib.rs +++ b/auxtools/src/lib.rs @@ -32,7 +32,7 @@ pub use auxtools_impl::{full_shutdown, hook, init, pin_dll, runtime_handler, shu pub use byond_scan; /// Used by the [pin_dll] macro to set dll pinning pub use ctor; -pub use hooks::{CompileTimeHook, RuntimeErrorHook}; +pub use hooks::{CompileTimeHook, HookFailure, ProcHook, RuntimeErrorHook}; use init::{get_init_level, set_init_level, InitLevel}; pub use init::{FullInitFunc, FullShutdownFunc, PartialInitFunc, PartialShutdownFunc}; /// Used by the [hook](attr.hook.html) macro to aggregate all compile-time hooks From e46364912f660070e17eb63766bcd5563141fadd Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 00:39:59 -0400 Subject: [PATCH 14/20] fix stale docs and examples --- README.md | 4 ++-- auxtools-impl/src/lib.rs | 11 ++++------- auxtools/src/byond_ffi.rs | 6 +++--- auxtools/src/proc.rs | 4 ++-- auxtools/src/value.rs | 2 +- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 5bba49c1..ed302f8c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Debug Server - Working with SpaceManiac's [SpacemanDMM](https://github.com/Space The patterns are run by [byond-scan](https://github.com/Absolucy/byond-scan) and live in `auxtools/src/symbols/`. They are copies of the ones in the byond-re repo's `byond_catalog`, which checks each against every local build, so a change to one has to land in both. -What was run on Linux (516.1687): the `auxtest` suite, and the debug server starting up. The code coverage tool's report was checked on Windows and Linux 1687 against a world with a known set of lines. Stepping and breakpoints with a real debugger attached were not tested on either platform. +What was run on Linux (516.1687): the `auxtest` suite, and `debug_test`, which sets breakpoints, evaluates expressions and re-attaches through a stand-in for the debugger. The code coverage tool's report was checked on Windows and Linux 1687 against a world with a known set of lines. Stepping and breakpoints with a real debugger attached were not tested on either platform. ## Dependencies @@ -75,7 +75,7 @@ Linux: ```sh export PKG_CONFIG_ALLOW_CROSS=1 cargo build --release --target i686-unknown-linux-gnu -# output: target/i686-unknown-linux-gnu/release/libauxtools.so +# output: target/i686-unknown-linux-gnu/release/libauxtools.so,libdebug_server.so,libauxcov.so ``` Windows: diff --git a/auxtools-impl/src/lib.rs b/auxtools-impl/src/lib.rs index bb284e90..c20d8bfb 100644 --- a/auxtools-impl/src/lib.rs +++ b/auxtools-impl/src/lib.rs @@ -142,10 +142,7 @@ pub fn pin_dll(attr: TokenStream) -> TokenStream { /// ```ignore /// #[hook] /// fn double_up(num: Value) { -/// if let Some(num) = num.as_number() { -/// Value::from(num * 2.0); -/// } -/// Value::NULL +/// Ok(Value::from(num.as_number()? * 2.0)) /// } /// ``` /// @@ -154,9 +151,9 @@ pub fn pin_dll(attr: TokenStream) -> TokenStream { /// ```ignore /// #[hook("/mob/proc/on_honked")] /// fn on_honked(honker: Value) { -/// src.call("gib", &[]); -/// honker.call("laugh", &[]); -/// Value::NULL +/// src.call("gib", &[])?; +/// honker.call("laugh", &[])?; +/// Ok(Value::NULL) /// } /// ``` #[proc_macro_attribute] diff --git a/auxtools/src/byond_ffi.rs b/auxtools/src/byond_ffi.rs index 30437943..21b8dbeb 100644 --- a/auxtools/src/byond_ffi.rs +++ b/auxtools/src/byond_ffi.rs @@ -74,9 +74,9 @@ pub fn byond_return(value: Option>) -> *const c_char { /// /// # Examples /// ```ignore -/// // byond_ffi_fn! { my_proc(_input) { -/// // Some("Hello, BYOND!".to_owned()) -/// // } +/// byond_ffi_fn! { my_proc(_input) { +/// Some("Hello, BYOND!".to_owned()) +/// } } /// ``` #[macro_export] macro_rules! byond_ffi_fn { diff --git a/auxtools/src/proc.rs b/auxtools/src/proc.rs index f48f65ab..ba780f4c 100644 --- a/auxtools/src/proc.rs +++ b/auxtools/src/proc.rs @@ -27,8 +27,8 @@ use crate::*; // world << "!!!" // ``` // -// To get the nth override, use [get_proc_override]: `let hello = get_proc_override("/proc/hello", n).unwrap()` -// [get_proc] retrieves the base proc. +// To get the nth override, use [Proc::find_override]: `let hello = Proc::find_override("/proc/hello", n).unwrap()` +// [Proc::find] retrieves the base proc. /// Used to hook and call procs. /// diff --git a/auxtools/src/value.rs b/auxtools/src/value.rs index 50e5c020..59f4efe5 100644 --- a/auxtools/src/value.rs +++ b/auxtools/src/value.rs @@ -316,7 +316,7 @@ impl Value { } /// Creates a Value that references a byond string. - /// Will panic if the given string contains null bytes + /// Returns an error if the given string contains null bytes /// /// # Examples: /// ```ignore From aceb1c4f95d8b8534600db931fdb80b65cb05d86 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 00:48:04 -0400 Subject: [PATCH 15/20] debug server: expand /vector and /pixloc into their fields --- debug_server/src/server.rs | 18 +++++++++++- tests/debug_test/src/main.rs | 36 ++++++++++++++++++++++++ tests/debug_test_host/debug_test_host.dm | 14 +++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index 8e8327c4..6512290a 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -286,13 +286,22 @@ impl Server { offset } + // 516 primitives have no `vars` list, so their fields are spelled out here + fn primitive_fields(value: &Value) -> Option<&'static [&'static str]> { + match value.raw.tag { + ValueTag::Vector => Some(&["x", "y", "z", "len", "size"]), + ValueTag::PixLoc => Some(&["x", "y", "z", "step_x", "step_y", "loc"]), + _ => None + } + } + fn is_object(value: &Value) -> bool { // Hack for globals if value.raw.tag == ValueTag::World && unsafe { value.raw.data.id == 1 } { return true; } - value.get(byond_string!("vars")).is_ok() + Self::primitive_fields(value).is_some() || value.get(byond_string!("vars")).is_ok() } fn stringify(value: &Value) -> String { @@ -382,6 +391,13 @@ impl Server { } fn object_to_variables(&mut self, value: &Value) -> Result, Runtime> { + if let Some(fields) = Self::primitive_fields(value) { + return fields + .iter() + .map(|&name| Ok(self.value_to_variable(name.to_owned(), &value.get(StringRef::new(name)?)?))) + .collect(); + } + // Grab `value.vars`. We have a little hack for globals which use a // special type. let vars = List::from_value(&unsafe { diff --git a/tests/debug_test/src/main.rs b/tests/debug_test/src/main.rs index c6cfdb30..06822c00 100644 --- a/tests/debug_test/src/main.rs +++ b/tests/debug_test/src/main.rs @@ -109,6 +109,26 @@ fn paused_in(client: &mut Client) -> String { } } +/// Evals `command` and returns its one-line text and its expanded fields as +/// "name = value" strings. +fn eval_fields(client: &mut Client, command: &str) -> (String, Vec) { + let response = client.request(Request::Eval { + frame_id: Some(0), + command: command.to_owned(), + context: None + }); + let Response::Eval(eval) = response else { + panic!("expected the result of {:?}, got {:?}", command, response); + }; + let refs = eval.variables.unwrap_or_else(|| panic!("{} should be expandable", command)); + let vars = match client.request(Request::Variables { vars: refs }) { + Response::Variables { vars } => vars, + other => panic!("expected Variables, got {:?}", other) + }; + let fields = vars.into_iter().map(|var| format!("{} = {}", var.name, var.value)).collect(); + (eval.value, fields) +} + fn expect_breakpoint_in(client: &mut Client, proc: &str) { assert_eq!(client.expect_pause(), BreakpointReason::Breakpoint); assert_eq!(paused_in(client), proc); @@ -176,6 +196,22 @@ fn main() { assert_eq!(shown, ["\"a\" = 1", "\"n\" = null", "7 = \"seven\""]); println!("ok: an alist shows its key/value pairs"); + // size is a float, so only its start is checked + let (text, fields) = eval_fields(&mut client, "vector_sample()"); + assert_eq!(text, "vector(1,2,3)"); + assert_eq!(fields[..4], ["x = 1", "y = 2", "z = 3", "len = 3"]); + assert!(fields[4].starts_with("size = 3.74"), "unexpected size: {}", fields[4]); + let (text, fields) = eval_fields(&mut client, "vector2_sample()"); + assert_eq!(text, "vector(4,5)"); + assert_eq!(fields[..4], ["x = 4", "y = 5", "z = 0", "len = 2"]); + println!("ok: a vector shows its components"); + + let (text, fields) = eval_fields(&mut client, "pixloc_sample()"); + assert_eq!(text, "pixloc(1,1,1)"); + assert_eq!(fields[..5], ["x = 1", "y = 1", "z = 1", "step_x = 0", "step_y = 0"]); + assert!(fields[5].starts_with("loc = "), "unexpected loc: {}", fields[5]); + println!("ok: a pixloc shows its position"); + assert_eq!(eval(&mut client, None, &dis_command), clean_disassembly); println!("ok: disassembly doesn't show the breakpoint"); diff --git a/tests/debug_test_host/debug_test_host.dm b/tests/debug_test_host/debug_test_host.dm index 7175213d..dad895cb 100644 --- a/tests/debug_test_host/debug_test_host.dm +++ b/tests/debug_test_host/debug_test_host.dm @@ -25,6 +25,15 @@ var/reboot_requested = FALSE /proc/alist_sample() return alist("a" = 1, 7 = "seven", "n" = null) +/proc/vector_sample() + return vector(1, 2, 3) + +/proc/vector2_sample() + return vector(4, 5) + +/proc/pixloc_sample() + return pixloc(1, 1, 1) + // Two seconds of runtime errors, then quiet again. The test detaches while // they are going and comes back after they stop. /proc/start_runtimes() @@ -53,6 +62,11 @@ var/reboot_requested = FALSE world.Reboot() /world/New() + // pixloc_sample() needs a real tile to point at + maxx = 5 + maxy = 5 + maxz = 1 + var/debug_dll = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") var/init_result = call_ext(debug_dll, "auxtools_init")() world.log << "init_result = [init_result]" From 9e81f999ab725801e6fed8c82878faa9b5b8f581 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 00:53:16 -0400 Subject: [PATCH 16/20] debug server: expand /callee into its fields --- debug_server/src/server.rs | 4 +++- tests/debug_test/src/main.rs | 23 ++++++++++++++++------- tests/debug_test_host/debug_test_host.dm | 2 ++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index 6512290a..363b2715 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -286,11 +286,13 @@ impl Server { offset } - // 516 primitives have no `vars` list, so their fields are spelled out here + // 516 primitives have no `vars` list, so their fields are spelled out here. + // a callee only reads while its proc is still running fn primitive_fields(value: &Value) -> Option<&'static [&'static str]> { match value.raw.tag { ValueTag::Vector => Some(&["x", "y", "z", "len", "size"]), ValueTag::PixLoc => Some(&["x", "y", "z", "step_x", "step_y", "loc"]), + ValueTag::Callee => Some(&["proc", "file", "line", "src", "usr", "args", "caller"]), _ => None } } diff --git a/tests/debug_test/src/main.rs b/tests/debug_test/src/main.rs index 06822c00..14723ce8 100644 --- a/tests/debug_test/src/main.rs +++ b/tests/debug_test/src/main.rs @@ -109,11 +109,11 @@ fn paused_in(client: &mut Client) -> String { } } -/// Evals `command` and returns its one-line text and its expanded fields as -/// "name = value" strings. -fn eval_fields(client: &mut Client, command: &str) -> (String, Vec) { +/// Evals `command` in `frame_id` and returns its one-line text and its expanded +/// fields as "name = value" strings. +fn eval_fields(client: &mut Client, frame_id: u32, command: &str) -> (String, Vec) { let response = client.request(Request::Eval { - frame_id: Some(0), + frame_id: Some(frame_id), command: command.to_owned(), context: None }); @@ -197,21 +197,30 @@ fn main() { println!("ok: an alist shows its key/value pairs"); // size is a float, so only its start is checked - let (text, fields) = eval_fields(&mut client, "vector_sample()"); + let (text, fields) = eval_fields(&mut client, 0, "vector_sample()"); assert_eq!(text, "vector(1,2,3)"); assert_eq!(fields[..4], ["x = 1", "y = 2", "z = 3", "len = 3"]); assert!(fields[4].starts_with("size = 3.74"), "unexpected size: {}", fields[4]); - let (text, fields) = eval_fields(&mut client, "vector2_sample()"); + let (text, fields) = eval_fields(&mut client, 0, "vector2_sample()"); assert_eq!(text, "vector(4,5)"); assert_eq!(fields[..4], ["x = 4", "y = 5", "z = 0", "len = 2"]); println!("ok: a vector shows its components"); - let (text, fields) = eval_fields(&mut client, "pixloc_sample()"); + let (text, fields) = eval_fields(&mut client, 0, "pixloc_sample()"); assert_eq!(text, "pixloc(1,1,1)"); assert_eq!(fields[..5], ["x = 1", "y = 1", "z = 1", "step_x = 0", "step_y = 0"]); assert!(fields[5].starts_with("loc = "), "unexpected loc: {}", fields[5]); println!("ok: a pixloc shows its position"); + // frame 1 is main_loop, which holds a callee of itself + let (text, fields) = eval_fields(&mut client, 1, "this_proc"); + assert!(text.contains("/proc/main_loop"), "unexpected callee text: {}", text); + assert_eq!(fields[..2], ["proc = /proc/main_loop", "file = \"debug_test_host.dm\""]); + assert!(fields[2].starts_with("line = "), "unexpected line: {}", fields[2]); + assert_eq!(fields[3..6], ["src = null", "usr = null", "args = /list {len = 1}"]); + assert!(fields[6].starts_with("caller = "), "unexpected caller: {}", fields[6]); + println!("ok: a callee shows its proc"); + assert_eq!(eval(&mut client, None, &dis_command), clean_disassembly); println!("ok: disassembly doesn't show the breakpoint"); diff --git a/tests/debug_test_host/debug_test_host.dm b/tests/debug_test_host/debug_test_host.dm index dad895cb..84534c2d 100644 --- a/tests/debug_test_host/debug_test_host.dm +++ b/tests/debug_test_host/debug_test_host.dm @@ -47,6 +47,8 @@ var/reboot_requested = FALSE reboot_requested = TRUE /proc/main_loop(debug_dll) + // a callee only reads while its proc is running, so the test looks at this one + var/callee/this_proc = callee while (!reboot_requested) breakpoint_target() tick_marker() From e46adb308cc5dff94769a6ea5de79f7a5d406038 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 08:57:12 -0400 Subject: [PATCH 17/20] that is le refcount --- auxtools/src/raw_types/lists.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auxtools/src/raw_types/lists.rs b/auxtools/src/raw_types/lists.rs index bb34696e..272dfbd2 100644 --- a/auxtools/src/raw_types/lists.rs +++ b/auxtools/src/raw_types/lists.rs @@ -27,8 +27,8 @@ pub struct AssociativeListEntry { #[repr(C)] pub struct Alist { pub root: *mut AssociativeListEntry, - // starts at 1 and nobody has worked out what it is - unknown: u32 + /// A new alist starts at 1, not 0. + pub refcount: u32 } #[repr(C)] From e24f6ad506663b303c52e580dff3d546a60a9b70 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 11:35:04 -0400 Subject: [PATCH 18/20] update deps --- Cargo.lock | 1480 +++++++++++++++++++++++++++------------------------- 1 file changed, 773 insertions(+), 707 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7cdbf4e0..d338e6c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,20 +2,11 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -30,12 +21,12 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -43,33 +34,27 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] [[package]] name = "anstream" -version = "0.6.18" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -82,43 +67,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] @@ -138,7 +124,7 @@ dependencies = [ "attribute-derive-macro", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] @@ -154,7 +140,7 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] @@ -170,9 +156,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "auxcov" @@ -213,29 +199,20 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] -name = "backtrace" -version = "0.3.74" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets", -] +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64" -version = "0.22.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bincode" @@ -254,9 +231,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "block-buffer" @@ -269,12 +246,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" dependencies = [ "memchr", - "serde", + "serde_core", ] [[package]] @@ -289,9 +266,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byond-scan" @@ -314,15 +291,15 @@ dependencies = [ [[package]] name = "bytecount" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" [[package]] name = "byteorder" @@ -332,28 +309,26 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bzip2" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", - "libc", ] [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] @@ -371,10 +346,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.5" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" +checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -393,23 +369,22 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-link", ] [[package]] @@ -461,9 +436,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.23" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -471,27 +446,27 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.23" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", - "clap_lex 0.7.4", + "clap_lex 1.1.1", "strsim 0.11.1", "terminal_size", ] [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] @@ -505,15 +480,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "collection_literals" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" +checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084" [[package]] name = "color_space" @@ -523,9 +498,9 @@ checksum = "52fdfaf2bee6357023bf7f95b15a8ef0b82759d2bce705cc45efcae9ae10f0ff" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "constant_time_eq" @@ -543,68 +518,84 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + [[package]] name = "cpp_demangle" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" dependencies = [ "cfg-if", ] [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.2.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -612,18 +603,18 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crypto-common" @@ -642,7 +633,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] @@ -681,18 +672,15 @@ dependencies = [ [[package]] name = "deflate64" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" [[package]] name = "derivative" @@ -707,31 +695,31 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.2.7" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +checksum = "2e2b94854e8576378ccda7c8de8a66ed8b4e8acbd2c50ec3418ea6c8aaf4b567" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "deunicode" -version = "1.6.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" [[package]] name = "digest" @@ -746,21 +734,21 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "dmasm" version = "0.1.0" -source = "git+https://github.com/willox/dmasm#71900bef3b209d469762bae72244b1d6abb4be2b" +source = "git+https://github.com/willox/dmasm#6cec0e9a60cb3b3a056daca8c791d36795185154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", "dreammaker", "nom", ] @@ -777,7 +765,7 @@ dependencies = [ "foldhash", "get-size", "get-size-derive", - "indexmap 2.7.0", + "indexmap 2.14.2", "interval-tree", "lodepng", "ordered-float", @@ -790,50 +778,62 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" dependencies = [ "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "fastrand" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", "libz-sys", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -865,18 +865,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -884,33 +884,33 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", "futures-io", @@ -918,7 +918,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -933,9 +932,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", @@ -955,14 +954,14 @@ checksum = "13a1bcfb855c1f340d5913ab542e36f25a1c56f57de79022928297632435dec2" dependencies = [ "attribute-derive", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", @@ -970,16 +969,35 @@ dependencies = [ ] [[package]] -name = "gimli" -version = "0.31.1" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] [[package]] name = "globset" -version = "0.4.15" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" dependencies = [ "aho-corasick", "bstr", @@ -994,7 +1012,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", "ignore", "walkdir", ] @@ -1006,7 +1024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c5f4731e3050bae8455c1c50d21c6187acac4fb003bc1ee9b6ea7e3e6d90d57" dependencies = [ "chrono", - "clap 4.5.23", + "clap 4.6.7", "crossbeam-channel", "flate2", "globset", @@ -1021,7 +1039,7 @@ dependencies = [ "regex", "rustc-hash", "rustc_version", - "semver 1.0.24", + "semver 1.0.28", "serde", "serde_json", "simplelog", @@ -1038,9 +1056,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.7" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ "atomic-waker", "bytes", @@ -1048,7 +1066,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.7.0", + "indexmap 2.14.2", "slab", "tokio", "tokio-util", @@ -1063,9 +1081,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -1090,9 +1108,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" [[package]] name = "hmac" @@ -1105,20 +1123,19 @@ dependencies = [ [[package]] name = "http" -version = "1.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -1126,12 +1143,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -1139,9 +1156,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "humansize" @@ -1154,13 +1171,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.2" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -1174,16 +1192,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1207,33 +1223,41 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ + "base64 0.23.1", "bytes", "futures-channel", "futures-util", "http", "http-body", + "httparse", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -1258,21 +1282,23 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", + "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -1281,104 +1307,67 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", + "icu_locale_core", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.91", -] - [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1387,9 +1376,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1397,9 +1386,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.23" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" dependencies = [ "crossbeam-deque", "globset", @@ -1423,12 +1412,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.17.1", ] [[package]] @@ -1442,9 +1431,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] @@ -1472,73 +1461,75 @@ source = "git+https://github.com/SpaceManiac/SpacemanDMM?tag=suite-1.10#c1101fb3 [[package]] name = "inventory" -version = "0.3.16" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d80fade88dd420ce0d9ab6f7c58ef2272dde38db874657950f827d4982c817" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" dependencies = [ "rustversion", ] [[package]] name = "ipnet" -version = "2.10.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ + "getrandom 0.4.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libz-sys" -version = "1.1.20" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" dependencies = [ "cc", "pkg-config", @@ -1547,27 +1538,21 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "lodepng" -version = "3.10.7" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2dea7cda68e381418c985fd8f32a9c279a21ae8c715f2376adb20c27a0fad3" +checksum = "fe7982db11054edc023a1b424dddcc65be18f71fa46ec6bde2efcfc1fb6b22da" dependencies = [ "crc32fast", "flate2", @@ -1577,9 +1562,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lzma-rs" @@ -1591,11 +1576,22 @@ dependencies = [ "crc", ] +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "mach2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" dependencies = [ "libc", ] @@ -1612,15 +1608,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" dependencies = [ "libc", ] @@ -1639,22 +1635,23 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", "wasi", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1669,18 +1666,25 @@ dependencies = [ [[package]] name = "msvc-demangler" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4c25a3bb7d880e8eceab4822f3141ad0700d20f025991c1f03bd3d00219a5fc" +checksum = "fbeff6bd154a309b2ada5639b2661ca6ae4599b34e8487dc276d2cd637da2d76" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", + "itoa", ] +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -1705,9 +1709,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-traits" @@ -1720,11 +1724,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.3.9", + "hermit-abi 0.5.3", "libc", ] @@ -1738,31 +1742,27 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.7" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "once_cell" -version = "1.20.2" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -1775,20 +1775,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.104" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -1843,26 +1843,25 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.7.15" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "45d3aca230fad2e6f6317ca0a72724338c4960cb97168a85cdee66df4a9a21a8" dependencies = [ "memchr", - "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "284b60557f2c4a2e72ad3f2d34d42685a2fa4a6a61d0d2a10c0ae2a5e916c2cf" dependencies = [ "pest", "pest_generator", @@ -1870,33 +1869,31 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "1d9d1f08a115309ee99268cf85e5228e0e56aa9caf8841ec12866b6be07c3109" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "ed93ba1a9ffcca32130a5188701c81c0c49cf00d4b7c5007d5148951d743adcb" dependencies = [ - "once_cell", "pest", - "sha2", ] [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", @@ -1904,9 +1901,9 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", @@ -1914,9 +1911,9 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", @@ -1932,35 +1929,38 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pkg-config" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] -name = "pkg-config" -version = "0.3.31" +name = "potential_utf" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -1970,9 +1970,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] @@ -2014,9 +2014,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -2032,9 +2032,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -2047,7 +2047,7 @@ checksum = "a7b5abe3fe82fdeeb93f44d66a7b444dedf2e4827defb0a8e69c437b2de2ef94" dependencies = [ "quote", "quote-use-macros", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] @@ -2059,14 +2059,26 @@ dependencies = [ "derive-where", "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ "libc", "rand_chacha", @@ -2089,14 +2101,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.17", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -2104,9 +2116,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -2114,9 +2126,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" dependencies = [ "aho-corasick", "memchr", @@ -2126,9 +2138,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -2137,9 +2149,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "region" @@ -2155,11 +2167,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-channel", @@ -2173,28 +2185,26 @@ dependencies = [ "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-registry", ] [[package]] @@ -2215,39 +2225,38 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" dependencies = [ "bytemuck", ] [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.17", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -2255,27 +2264,27 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.24", + "semver 1.0.28", ] [[package]] name = "rustix" -version = "0.38.42" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "once_cell", "rustls-pki-types", @@ -2285,25 +2294,19 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.2.0" +name = "rustls-pki-types" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ - "rustls-pki-types", + "zeroize", ] -[[package]] -name = "rustls-pki-types" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" - [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -2312,15 +2315,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -2333,21 +2336,27 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" -version = "2.11.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.6.0", - "core-foundation", + "bitflags 2.13.2", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -2355,9 +2364,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.13.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -2375,9 +2384,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.24" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "semver-parser" @@ -2387,34 +2396,45 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "serde_json" -version = "1.0.134" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -2431,9 +2451,9 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures", @@ -2441,27 +2461,22 @@ dependencies = [ ] [[package]] -name = "sha2" -version = "0.10.8" +name = "shlex" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] -name = "shlex" -version = "1.3.0" +name = "simd-adler32" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] -name = "simd-adler32" -version = "0.3.7" +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simplelog" @@ -2476,18 +2491,15 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slice-pool2" @@ -2507,31 +2519,25 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" [[package]] name = "socket2" -version = "0.5.8" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" @@ -2553,9 +2559,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.12.4" +version = "12.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd33e73f154e36ec223c18013f7064a2c120f1162fc086ac9933542def186b00" +checksum = "332615d90111d8eeaf86a84dc9bbe9f65d0d8c5cf11b4caccedc37754eb0dcfd" dependencies = [ "debugid", "memmap2", @@ -2565,9 +2571,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.12.4" +version = "12.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e51191290147f071777e37fe111800bb82a9059f9c95b19d2dd41bfeddf477" +checksum = "912017718eb4d21930546245af9a3475c9dccf15675a5c215664e76621afc471" dependencies = [ "cpp_demangle", "msvc-demangler", @@ -2588,9 +2594,20 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.91" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" dependencies = [ "proc-macro2", "quote", @@ -2608,23 +2625,23 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.6.0", - "core-foundation", + "bitflags 2.13.2", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -2663,22 +2680,22 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.14.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tera" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" +checksum = "e8004bca281f2d32df3bacd59bc67b312cb4c70cea46cbd79dbe8ac5ed206722" dependencies = [ "chrono", "chrono-tz", @@ -2693,7 +2710,7 @@ dependencies = [ "serde", "serde_json", "slug", - "unic-segment", + "unicode-segmentation", ] [[package]] @@ -2707,12 +2724,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" +checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2736,58 +2753,57 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.16.1" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +checksum = "6ecfad6c3abc80a577f2b91c1e412ee57e7a060d430b553c1b0c940974ebcd49" [[package]] name = "thiserror" -version = "2.0.9" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.9" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "time" -version = "0.3.37" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "itoa", "libc", "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -2795,9 +2811,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -2805,17 +2821,16 @@ dependencies = [ [[package]] name = "tokio" -version = "1.42.0" +version = "1.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" dependencies = [ - "backtrace", "bytes", "libc", "mio", "pin-project-lite", "socket2", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2830,9 +2845,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.1" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ "rustls", "tokio", @@ -2840,13 +2855,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -2860,6 +2876,45 @@ dependencies = [ "serde", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.2", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -2868,9 +2923,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-core", @@ -2878,9 +2933,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] @@ -2893,9 +2948,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.17.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -2904,60 +2959,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" +name = "unicode-ident" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" [[package]] -name = "unicode-ident" -version = "1.0.14" +name = "unicode-segmentation" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -2973,21 +2984,16 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -3002,11 +3008,13 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.11.0" +version = "1.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" dependencies = [ - "getrandom", + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -3042,53 +3050,48 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasm-bindgen" -version = "0.2.99" +name = "wasip2" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.99" +name = "wasm-bindgen" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.91", + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" dependencies = [ - "cfg-if", "js-sys", - "once_cell", + "tokio", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3096,28 +3099,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.91", - "wasm-bindgen-backend", + "syn 3.0.6", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ "js-sys", "wasm-bindgen", @@ -3141,11 +3147,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3156,41 +3162,72 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.52.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-targets", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[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 2.0.119", ] +[[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 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ + "windows-link", "windows-result", "windows-strings", - "windows-targets", ] [[package]] name = "windows-result" -version = "0.2.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-result", - "windows-targets", + "windows-link", ] [[package]] @@ -3204,11 +3241,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-targets", + "windows-link", ] [[package]] @@ -3276,24 +3313,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "write16" -version = "1.0.0" +name = "wit-bindgen" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -3301,83 +3346,93 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", ] [[package]] name = "zerovec" -version = "0.10.4" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -3386,20 +3441,20 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.91", + "syn 3.0.6", ] [[package]] name = "zip" -version = "2.2.2" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "aes", "arbitrary", @@ -3410,57 +3465,68 @@ dependencies = [ "deflate64", "displaydoc", "flate2", + "getrandom 0.3.4", "hmac", - "indexmap 2.7.0", + "indexmap 2.14.2", "lzma-rs", "memchr", "pbkdf2", - "rand", "sha1", "thiserror", "time", + "xz2", "zeroize", "zopfli", "zstd", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + [[package]] name = "zopfli" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" dependencies = [ "bumpalo", "crc32fast", - "lockfree-object-pool", "log", - "once_cell", "simd-adler32", ] [[package]] name = "zstd" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.2.1" +version = "7.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059" +checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" +version = "2.1.0+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" +checksum = "0ef0a8027ec3ee71300ab3bcbcd0393f434aa72b91ca6d635a39941deae8eea0" dependencies = [ "cc", "pkg-config", From 54cd7fe009d9ad3b46d224fba17bcaed2f4ccf92 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 11:36:26 -0400 Subject: [PATCH 19/20] debug_server update stuff --- Cargo.lock | 2 +- debug_server/Cargo.toml | 2 +- debug_server/src/server.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d338e6c0..f852e415 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -638,7 +638,7 @@ dependencies = [ [[package]] name = "debug_server" -version = "2.3.8" +version = "2.4.0" dependencies = [ "auxtools", "bincode", diff --git a/debug_server/Cargo.toml b/debug_server/Cargo.toml index 0ac80ddd..597cc529 100644 --- a/debug_server/Cargo.toml +++ b/debug_server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "debug_server" -version = "2.3.8" +version = "2.4.0" publish = false authors.workspace = true edition.workspace = true diff --git a/debug_server/src/server.rs b/debug_server/src/server.rs index 363b2715..795aec86 100644 --- a/debug_server/src/server.rs +++ b/debug_server/src/server.rs @@ -288,7 +288,7 @@ impl Server { // 516 primitives have no `vars` list, so their fields are spelled out here. // a callee only reads while its proc is still running - fn primitive_fields(value: &Value) -> Option<&'static [&'static str]> { + const fn primitive_fields(value: &Value) -> Option<&'static [&'static str]> { match value.raw.tag { ValueTag::Vector => Some(&["x", "y", "z", "len", "size"]), ValueTag::PixLoc => Some(&["x", "y", "z", "step_x", "step_y", "loc"]), From e5add34bca8fd1b0c7f840fcb42fb2383db2cad1 Mon Sep 17 00:00:00 2001 From: Lucy Date: Sun, 4 Oct 2026 11:39:43 -0400 Subject: [PATCH 20/20] re-eport `byond_scan::MODULE_NAME` --- auxtools/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/auxtools/src/lib.rs b/auxtools/src/lib.rs index aac99dd8..cc141ea0 100644 --- a/auxtools/src/lib.rs +++ b/auxtools/src/lib.rs @@ -54,6 +54,9 @@ extern crate winapi; pub static PIN_DLL: AtomicBool = AtomicBool::new(true); +/// wrapper for [`byond_scan::MODULE_NAME`](byond_scan::MODULE_NAME) +pub const BYONDCORE: &str = byond_scan::MODULE_NAME; + // This strange section of code retrieves our DLL using the init function's // address. This increments the DLL reference count, which prevents unloading. #[cfg(windows)]