jevsnes.git / apps / hotdemo / src / main.rs
main.rsannotatedmain.rssource79 lines · 3.0 KB · raw

A subsecond hot-patch guinea pig: no game, no window, just a loop that calls one function through subsecond::call once a second and watches for a patch to land. See ../README.md for the file it watches and what a successful run looks like.

6use std::io::Write;
7use std::path::Path;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::time::Duration;

The counter tick prints and increments itself. A plain static, not a parameter threaded through a closure - see ../CLAUDE.md for why subsecond::call needs a bare fn item passed directly (subsecond::call(tick)), never || tick(n).

15static TICKS: AtomicU64 = AtomicU64::new(0);

The function tools/hotpatch patches. The string it prints is the only thing that changes between v1/v2/v3 - the loop, the pid, and the process itself never restart. #[inline(never)] is load-bearing, not tidiness: see ../CLAUDE.md.

21#[inline(never)]
22fn tick() {
23    let n = TICKS.fetch_add(1, Ordering::Relaxed);
24    println!("hotdemo v1 tick={n}");
25}
27fn main() {
28    // A patcher needs both of these before it can build anything: the pid to
29    // address the running process's MCP-equivalent (here, just a log line a
30    // human/agent reads), and the ASLR reference `subsecond::apply_patch`
31    // rebases the jump table against (subsecond/src/lib.rs:716-750, "main"'s
32    // live address in *this* process).
33    println!(
34        "hotdemo pid={} aslr_reference={:#x}",
35        std::process::id(),
36        subsecond::aslr_reference()
37    );
38    std::io::stdout().flush().ok();
39
40    let patch_path = Path::new("run/hotdemo.patch.json");
41    loop {
42        subsecond::call(tick);
43        std::io::stdout().flush().ok();
44
45        if patch_path.exists() {
46            apply_pending_patch(patch_path);
47        }
48
49        std::thread::sleep(Duration::from_secs(1));
50    }
51}

Read run/hotdemo.patch.json (a serialized subsecond_types::JumpTable), apply it, and delete the file - the same drop-a-file protocol dx's devtools websocket exists to avoid needing here (research/ subsecond-patch-build.md §4: jev's MCP server is the in-process equivalent for apps/native; hotdemo does not even need that much).

58fn apply_pending_patch(patch_path: &Path) {
59    let bytes = match std::fs::read(patch_path) {
60        Ok(bytes) => bytes,
61        Err(e) => {
62            eprintln!("hotdemo: failed to read {}: {e}", patch_path.display());
63            return;
64        }
65    };
66
67    match serde_json::from_slice::<subsecond_types::JumpTable>(&bytes) {
68        Ok(table) => match unsafe { subsecond::apply_patch(table) } {
69            Ok(()) => println!("hotdemo: patch applied"),
70            Err(e) => eprintln!("hotdemo: patch apply failed: {e}"),
71        },
72        Err(e) => eprintln!("hotdemo: bad patch json in {}: {e}", patch_path.display()),
73    }
74
75    if let Err(e) = std::fs::remove_file(patch_path) {
76        eprintln!("hotdemo: failed to remove {}: {e}", patch_path.display());
77    }
78    std::io::stdout().flush().ok();
79}