diff --git a/.gitignore b/.gitignore index 385de5e..f8197c1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,10 @@ engine/*.pak engine/*.dll engine/argus316/ engine/omicron/ +# scratch mod dirs: lab runs need a -game dir under engine/, and a +# git add -A has twice swept one into a commit. Only argus/ ships. +engine/*/ +!engine/argus/ engine/qconsole.log # id-derived coloured model staging (regenerate from LibreQuake for diff --git a/engine/argsession/autoexec.cfg b/engine/argsession/autoexec.cfg deleted file mode 100644 index 0d36bf0..0000000 --- a/engine/argsession/autoexec.cfg +++ /dev/null @@ -1,2 +0,0 @@ -sv_protocol 15 -max_edicts 600 diff --git a/engine/argsession/progs.dat b/engine/argsession/progs.dat deleted file mode 100644 index 8fc28a9..0000000 Binary files a/engine/argsession/progs.dat and /dev/null differ diff --git a/engine/argus/progs.dat b/engine/argus/progs.dat index ed4c600..8fc28a9 100644 Binary files a/engine/argus/progs.dat and b/engine/argus/progs.dat differ diff --git a/game/argus/progs.dat b/game/argus/progs.dat index ed4c600..8fc28a9 100644 Binary files a/game/argus/progs.dat and b/game/argus/progs.dat differ diff --git a/tools/argus_mcp/src/backup.rs b/tools/argus_mcp/src/backup.rs index d9aeec1..cc44805 100644 --- a/tools/argus_mcp/src/backup.rs +++ b/tools/argus_mcp/src/backup.rs @@ -358,16 +358,42 @@ mod tests { "an out-of-tree install must get an indexed key, got {keys:?}" ); + // take_backup only READS the install paths, so it is safe here. + // restore_backup is deliberately NOT called: install_paths() + // includes the rerelease Saved Games copy, which lives under + // USERPROFILE and not under this test's temp root, so restoring + // would write to a real install on the developer's machine - + // the exact class #222 was about, and it made this test flaky + // besides. The defect #215 describes is the manifest KEY + // collision, and the manifest is what we check. let taken = take_backup(&cfg); assert!(taken.ok, "{taken:?}"); - fs::write(outside.join("argus/progs.dat"), b"CLOBBERED").unwrap(); - let r = restore_backup(&cfg, &taken.id).unwrap(); - assert!(r.ok, "{r:?}"); - assert_eq!( - fs::read(outside.join("argus/progs.dat")).unwrap(), - b"OUTSIDE", - "the out-of-tree install must come back" + let man = read_manifest(&backups_dir(&cfg).join(&taken.id)) + .expect("manifest written"); + let progs: Vec<&BackupFile> = man + .files + .iter() + .filter(|f| f.rel.ends_with("progs.dat")) + .collect(); + let mut rels: Vec<&str> = progs.iter().map(|f| f.rel.as_str()).collect(); + rels.sort(); + let uniq = { + let mut r = rels.clone(); + r.dedup(); + r.len() + }; + assert_eq!(rels.len(), uniq, "progs.dat entries collided: {rels:?}"); + assert!( + progs.iter().any(|f| f.restore_to.contains("steam")), + "the out-of-tree install must be in the manifest, got {rels:?}" ); + for f in &progs { + assert!( + backups_dir(&cfg).join(&taken.id).join(&f.rel).is_file(), + "manifest names {} but no file was copied", + f.rel + ); + } let _ = fs::remove_dir_all(&base); } } diff --git a/tools/argus_mcp/src/gui.rs b/tools/argus_mcp/src/gui.rs index 724b322..9ffb12a 100644 --- a/tools/argus_mcp/src/gui.rs +++ b/tools/argus_mcp/src/gui.rs @@ -305,8 +305,7 @@ fn write_response(stream: &mut TcpStream, resp: &Response) -> Result<(), String> /// progs.dat. A page on any other origin could reach them as a simple /// cross-site request: text/plain body, no preflight, no token. Require /// a same-origin Host, no cross-origin Origin, and real JSON. -fn post_allowed(req: &Request) -> Result<(), Response> { - let port = GUI_PORT.load(std::sync::atomic::Ordering::Relaxed); +fn post_allowed(req: &Request, port: u16) -> Result<(), Response> { let host = req.headers.get("host").map(|s| s.as_str()).unwrap_or(""); let want_a = format!("127.0.0.1:{port}"); let want_b = format!("localhost:{port}"); @@ -340,8 +339,16 @@ fn post_allowed(req: &Request) -> Result<(), Response> { } fn route(req: &Request) -> Response { + route_at(req, GUI_PORT.load(std::sync::atomic::Ordering::Relaxed)) +} + +/// The bound port is passed in rather than read from a global: the POST +/// guard is a security check and should not depend on hidden process +/// state, and the lib tests run concurrently in one process, which made +/// a global-reading version flake. +fn route_at(req: &Request, port: u16) -> Response { if req.method == "POST" { - if let Err(r) = post_allowed(req) { + if let Err(r) = post_allowed(req, port) { return r; } } @@ -888,13 +895,16 @@ mod tests { for (k, v) in headers { h.insert(k.to_string(), v.to_string()); } - route(&Request { - method: "POST".into(), - path: path.into(), - query: HashMap::new(), - body: b"{}".to_vec(), - headers: h, - }) + route_at( + &Request { + method: "POST".into(), + path: path.into(), + query: HashMap::new(), + body: b"{}".to_vec(), + headers: h, + }, + 7420, + ) } // #214: these endpoints rewrite the source tree and every installed @@ -902,7 +912,6 @@ mod tests { // cross-site request: text/plain body, no preflight, no token. #[test] fn post_endpoints_refuse_other_origins() { - GUI_PORT.store(7420, std::sync::atomic::Ordering::Relaxed); let ok_headers = [ ("host", "127.0.0.1:7420"), ("content-type", "application/json"),