jevsnes.git / apps / hotdemo / src / main.rs
main.rsannotatedmain.rssource79 lines · 3.0 KB · raw
1//! A subsecond hot-patch guinea pig: no game, no window, just a loop that
2//! calls one function through `subsecond::call` once a second and watches
3//! for a patch to land. See `../README.md` for the file it watches and what
4//! a successful run looks like.
5
6use std::io::Write;
7use std::path::Path;
8use std::sync::atomic::{AtomicU64, Ordering};
9use std::time::Duration;
10
11/// The counter `tick` prints and increments itself. A plain static, not a
12/// parameter threaded through a closure - see `../CLAUDE.md` for why
13/// `subsecond::call` needs a bare `fn` item passed directly
14/// (`subsecond::call(tick)`), never `|| tick(n)`.
15static TICKS: AtomicU64 = AtomicU64::new(0);
16
17/// The function `tools/hotpatch` patches. The string it prints is the only
18/// thing that changes between v1/v2/v3 - the loop, the pid, and the process
19/// itself never restart. `#[inline(never)]` is load-bearing, not tidiness:
20/// see `../CLAUDE.md`.
21#[inline(never)]
22fn tick() {
23    let n = TICKS.fetch_add(1, Ordering::Relaxed);
24    println!("hotdemo v1 tick={n}");
25}
26
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}
52
53/// Read `run/hotdemo.patch.json` (a serialized `subsecond_types::JumpTable`),
54/// apply it, and delete the file - the same drop-a-file protocol `dx`'s
55/// devtools websocket exists to avoid needing here (research/
56/// subsecond-patch-build.md §4: jev's MCP server is the in-process
57/// 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}