diff --git a/crates/hud/src/hudelem.rs b/crates/hud/src/hudelem.rs index b819ff1..2431fbb 100644 --- a/crates/hud/src/hudelem.rs +++ b/crates/hud/src/hudelem.rs @@ -59,103 +59,3 @@ pub(crate) fn hudelem_text_fx(elem: &HudElem, cg_time: i32) -> Option }, }) } - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum ProducerStatus { - Implemented, - - Unreachable(&'static str), - - Gap, -} - -pub struct ScriptedProducer { - pub script_fn: &'static str, - - pub cite: &'static str, - - pub produces: &'static str, - pub status: ProducerStatus, -} - -pub const SCRIPTED_PRODUCERS: &[ScriptedProducer] = &[ - ScriptedProducer { - script_fn: "_hud_message::init", - cite: "gsc:maps/mp/gametypes/_hud_message.gsc@sha256:e5773c6ad4e840b7#L5-41", - produces: "precached outcome strings and splash defaults", - status: ProducerStatus::Gap, - }, - ScriptedProducer { - script_fn: "_hud_message::initNotifyMessage", - cite: "gsc:maps/mp/gametypes/_hud_message.gsc@sha256:e5773c6ad4e840b7#L69-145", - produces: "title / text / text2 / icon / overlay elements, fonts, offsets, glow", - status: ProducerStatus::Gap, - }, - ScriptedProducer { - script_fn: "_hud_message::notifyMessage", - cite: "gsc:maps/mp/gametypes/_hud_message.gsc@sha256:e5773c6ad4e840b7#L163-188", - produces: "the four splash queues and their serialisation", - status: ProducerStatus::Gap, - }, - ScriptedProducer { - script_fn: "_hud_message::showNotifyMessage", - cite: "gsc:maps/mp/gametypes/_hud_message.gsc@sha256:e5773c6ad4e840b7#L221-374", - produces: "the splash timeline: text/value, pulse, icon layering, fade, scale", - status: ProducerStatus::Gap, - }, - ScriptedProducer { - script_fn: "_hud_message::lowerMessageThink", - cite: "gsc:maps/mp/gametypes/_hud_message.gsc@sha256:e5773c6ad4e840b7#L720-742", - produces: "the lower message and its timer", - status: ProducerStatus::Gap, - }, - ScriptedProducer { - script_fn: "_hud_message::matchOutcomeNotify", - cite: "gsc:maps/mp/gametypes/_hud_message.gsc@sha256:e5773c6ad4e840b7#L766-806", - produces: "match outcome presentation", - status: ProducerStatus::Gap, - }, - ScriptedProducer { - script_fn: "_hud_message::outcomeNotify", - cite: "gsc:maps/mp/gametypes/_hud_message.gsc@sha256:e5773c6ad4e840b7#L1038-1171", - produces: "FFA title + reason + HE_TYPE_PLAYERNAME 1st/2nd/3rd leftover", - status: ProducerStatus::Implemented, - }, - ScriptedProducer { - script_fn: "_hud_message::hintMessage", - cite: "gsc:maps/mp/gametypes/_hud_message.gsc@sha256:e5773c6ad4e840b7#L58-66", - produces: "OBJECTIVES_DM_HINT leftover (notifyText, 4s, glow 0.3/0.6/0.3); not the splash queue", - status: ProducerStatus::Implemented, - }, - ScriptedProducer { - script_fn: "_damagefeedback::updateDamageFeedback", - cite: "gsc:maps/mp/gametypes/_damagefeedback.gsc@sha256:141664ce14ccb2d7#L28-93", - produces: "attacker hit X on g_hudelems (archived=true); CopyInUseHudElems + BG_LerpHudColors at cg.time", - status: ProducerStatus::Implemented, - }, - ScriptedProducer { - script_fn: "_rank::scorePopup", - cite: "gsc:maps/mp/gametypes/_rank.gsc@sha256:4b31033a8baf6c9a#L504-557", - produces: "center HE_TYPE_VALUE + MP_PLUS; FFA !rankingEnabled colour from givePlayerScore", - status: ProducerStatus::Implemented, - }, - ScriptedProducer { - script_fn: "_gamelogic::matchStartTimer", - cite: "gsc:maps/mp/gametypes/_gamelogic.gsc@sha256:75b0836bafb28b3f#L985-1022", - produces: "server FontString loc + pulsing hudbig setValue; leftover CG_DrawHudElem", - status: ProducerStatus::Implemented, - }, - ScriptedProducer { - script_fn: "_events::firstBlood", - cite: "gsc:maps/mp/_events.gsc@sha256:88cc647a71e0925e#L563-572", - produces: "SplashNotifyDelayed firstblood when level.numKills==1; ActivateSplash stand-in", - status: ProducerStatus::Implemented, - }, -]; - -pub fn open_producer_count() -> usize { - SCRIPTED_PRODUCERS - .iter() - .filter(|p| p.status == ProducerStatus::Gap) - .count() -} diff --git a/crates/hud/src/lib.rs b/crates/hud/src/lib.rs index ea9ffa8..6a9b616 100644 --- a/crates/hud/src/lib.rs +++ b/crates/hud/src/lib.rs @@ -37,9 +37,7 @@ pub use draw2d::{ pub use gaps::{GapCause, HudGap, HudPresentationGaps}; pub use gpu_list::{HudTessBatch, HudTessGpuFrame, HudTessTechnique, HudTessVertex}; pub use hitmarker::PendingHitmarker; -pub use hudelem::{ - HudElemSoundLatch, ProducerStatus, SCRIPTED_PRODUCERS, ScriptedProducer, open_producer_count, -}; +pub use hudelem::HudElemSoundLatch; pub use overhead_names::{ OverheadPosedHead, OverheadPosedPlayerFrame, OverheadPosedPlayerFramePublished, }; diff --git a/xtask/src/publish_check.rs b/xtask/src/publish_check.rs index 2e0cb11..cea00be 100644 --- a/xtask/src/publish_check.rs +++ b/xtask/src/publish_check.rs @@ -12,12 +12,13 @@ //! private key, a piece of an original game install — none of it is the //! product, and a stray `git add -f` is all it takes. This is the half worth //! running before every push. -//! 2. **Retail offsets.** A standalone runtime resolves nothing against a -//! retail image, so a hardcoded address from one is dead weight wherever it -//! appears, and a decompiler placeholder name (`FUN_…`, `DAT_…`) is a -//! half-finished symbol. Naming IW4x, KisakCOD, an original executable or -//! Ghidra is *not* a finding: crediting what was read is the policy, not a -//! leak. +//! 2. **Retail offsets and pinned source.** A standalone runtime resolves +//! nothing against a retail image, so a hardcoded address from one is dead +//! weight wherever it appears, a decompiler placeholder name (`FUN_…`, +//! `DAT_…`) is a half-finished symbol, and a script file pinned by hash and +//! line range is an index into somebody else's tree. Naming IW4x, KisakCOD, +//! an original executable or Ghidra is *not* a finding: crediting what was +//! read is the policy, not a leak. //! //! It walks the tracked tree, so it sees exactly what a push would publish — //! and only that. It cannot see history, and it cannot see a release archive; @@ -322,6 +323,25 @@ fn address_in_name(lower: &str) -> bool { }) } +/// A script file pinned by content hash and line range. Nothing here reads one, +/// and a `ScriptGap` id, which names a hole in our own runtime, is not this +/// shape. +fn gsc_citation(lower: &str) -> bool { + let Some(at) = lower.find(".gsc@sha256:") else { + return lower.find(".gsc#l").is_some_and(|at| { + lower[at + ".gsc#l".len()..] + .as_bytes() + .first() + .is_some_and(u8::is_ascii_digit) + }); + }; + lower[at + ".gsc@sha256:".len()..] + .bytes() + .take_while(|b| is_hex(*b)) + .count() + >= 16 +} + fn scan_line(line: &str) -> Option { let lower = line.to_ascii_lowercase(); let bytes = lower.as_bytes(); @@ -362,6 +382,9 @@ fn scan_line(line: &str) -> Option { if address_in_name(&lower) { return Some("address inside an identifier".to_string()); } + if gsc_citation(&lower) { + return Some("script citation (path, hash and line range)".to_string()); + } None } @@ -508,6 +531,24 @@ mod tests { assert!(!looks_binary(b"pub const KICK_STEP_MS: i32 = 5;\n")); } + /// A script pinned by hash and line range is an index into somebody else's + /// tree. The gap ids, which name holes in our own runtime, are not that. + #[test] + fn catches_a_pinned_script() { + for line in [ + r#"pub const CITE: &str = "gsc:maps/mp/_destructables.gsc@sha256:05084ef406fb5ea1#L1-19";"#, + "// maps/mp/gametypes/_rank.gsc#L504-557 is where the popup lives", + ] { + assert!(scan_line(line).is_some(), "missed: {line}"); + } + for line in [ + r#"Self::DestructablesPlayFx => "gsc.mp._destructables.destructable_destruct","#, + r#"let raw = zone.rawfile("maps/mp/_destructables.gsc")?;"#, + ] { + assert!(scan_line(line).is_none(), "false positive: {line}"); + } + } + /// Naming what was read is the policy, so none of this is a finding any /// more. Each line is one the old gate rejected. #[test]