Discover Steam MW3 and Black Ops installations

This commit is contained in:
vtroc7
2026-10-02 20:06:28 +03:00
parent d48a9f6502
commit d352dbdfd8
4 changed files with 79 additions and 52 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ pub use progress::{
StageOutcome, StageScope, StageSnapshot, WorkCount, peak_resident_bytes,
process_resident_bytes,
};
pub use steam::{MW2_SHORTCUT, SteamCandidate, SteamProbe, link_steam_mw2};
pub use steam::{MW2_SHORTCUT, SteamCandidate, SteamProbe, link_steam_games};
pub use zone::{
Iw4WireFormat, Iw5ZoneMemory, T5ZoneMemory, ZoneImage, ZoneMemory, ZoneOpenError, open_zone,
open_zone_shared, parse_zone_image, xfile_arena_row, zone_share_counts,
+64 -42
View File
@@ -4,16 +4,17 @@ use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;
use crate::ZoneGame;
use crate::discover::GamesRoot;
#[cfg(windows)]
use crate::discover::{IW4_ZONE_VERSION, peek_zone_version, search_roots};
use crate::discover::{search_roots, zone_game_for_path};
pub const MW2_SHORTCUT: &str = "Modern Warfare 2.lnk";
#[derive(Clone, Debug, Default)]
pub struct SteamProbe {
pub steam_found: bool,
pub tried: Vec<(PathBuf, SteamCandidate)>,
pub tried: Vec<(ZoneGame, PathBuf, SteamCandidate)>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -26,47 +27,67 @@ pub enum SteamCandidate {
}
#[cfg(windows)]
pub fn link_steam_mw2(root: &GamesRoot) -> SteamProbe {
pub fn link_steam_games(root: &GamesRoot) -> SteamProbe {
let mut probe = SteamProbe::default();
let link = root.0.join(MW2_SHORTCUT);
if link.exists() || has_mw2(&search_roots(&root.0)) {
let roots = search_roots(&root.0);
let titles = [
(ZoneGame::Iw4, MW2_SHORTCUT, "Call of Duty Modern Warfare 2"),
(
ZoneGame::Iw5,
"Modern Warfare 3.lnk",
"Call of Duty Modern Warfare 3",
),
(ZoneGame::T5, "Black Ops.lnk", "Call of Duty Black Ops"),
];
let missing = titles
.into_iter()
.filter(|(game, shortcut, _)| !root.0.join(shortcut).exists() && !has_game(&roots, *game))
.collect::<Vec<_>>();
if missing.is_empty() {
return probe;
}
let libraries = steam_libraries();
probe.steam_found = !libraries.is_empty();
let mut valid = Vec::new();
for library in libraries {
let path = library
.join("steamapps")
.join("common")
.join("Call of Duty Modern Warfare 2");
let candidate = if !path.is_dir() {
SteamCandidate::Missing
} else if has_mw2(std::slice::from_ref(&path)) {
valid.push(probe.tried.len());
SteamCandidate::Linked
} else {
SteamCandidate::NoMultiplayerData
};
probe.tried.push((path, candidate));
}
match valid[..] {
[index] => {
let (target, candidate) = &mut probe.tried[index];
match create_shortcut(&link, target) {
Ok(()) => diag::info!(
Zone,
"linked Steam MW2 {} as {}",
target.display(),
link.display()
),
Err(error) => *candidate = SteamCandidate::ShortcutFailed(error),
}
for (game, shortcut, folder) in missing {
let link = root.0.join(shortcut);
let mut valid = Vec::new();
for library in &libraries {
let path = library.join("steamapps").join("common").join(folder);
let candidate = if !path.is_dir() {
SteamCandidate::Missing
} else if has_game(std::slice::from_ref(&path), game) {
valid.push(probe.tried.len());
SteamCandidate::Linked
} else {
SteamCandidate::NoMultiplayerData
};
probe.tried.push((game, path, candidate));
}
[] => {}
_ => {
for index in valid {
probe.tried[index].1 = SteamCandidate::OneOfSeveral;
match valid[..] {
[index] => {
let (_, target, candidate) = &mut probe.tried[index];
match create_shortcut(&link, target) {
Ok(()) => diag::info!(
Zone,
"linked Steam {} as {}",
target.display(),
link.display()
),
Err(error) => {
diag::warn!(Zone, "{error}");
*candidate = SteamCandidate::ShortcutFailed(error);
}
}
}
[] => {}
_ => {
diag::warn!(
Zone,
"several Steam installs of {folder}; create {shortcut} to select one"
);
for index in valid {
probe.tried[index].2 = SteamCandidate::OneOfSeveral;
}
}
}
}
@@ -74,19 +95,20 @@ pub fn link_steam_mw2(root: &GamesRoot) -> SteamProbe {
}
#[cfg(not(windows))]
pub fn link_steam_mw2(_root: &GamesRoot) -> SteamProbe {
pub fn link_steam_games(_root: &GamesRoot) -> SteamProbe {
SteamProbe::default()
}
#[cfg(windows)]
fn has_mw2(roots: &[PathBuf]) -> bool {
fn has_game(roots: &[PathBuf], game: ZoneGame) -> bool {
roots.iter().any(|root| {
std::iter::once(root.clone())
.chain(subdirs(root))
.flat_map(|tree| subdirs(&tree.join("zone")))
.any(|language| {
peek_zone_version(&language.join("common_mp.ff")) == Some(IW4_ZONE_VERSION)
.flat_map(|tree| {
let zone = tree.join("zone");
std::iter::once(zone.clone()).chain(subdirs(&zone))
})
.any(|language| zone_game_for_path(&language.join("common_mp.ff")) == Some(game))
})
}
+6 -2
View File
@@ -126,7 +126,7 @@ pub fn launch(
acceptance: Option<AcceptanceLaunch>,
cheats: sim::HostCheats,
) {
let steam = asset_transport::link_steam_mw2(&games);
let steam = asset_transport::link_steam_games(&games);
diag::info!(Launch, "{}", asset_transport::games_root_report(&games));
if let Some(plan) = crate::frame_owner::prefer_performance_cores() {
@@ -235,7 +235,11 @@ fn mw2_not_found(
text.push_str("Steam was not found on this PC.\n");
} else {
text.push_str("Tried in Steam:\n");
for (folder, candidate) in &steam.tried {
for (_, folder, candidate) in steam
.tried
.iter()
.filter(|(game, _, _)| *game == asset_core::ZoneGame::Iw4)
{
let outcome = match candidate {
SteamCandidate::Missing => "not installed here".to_owned(),
SteamCandidate::NoMultiplayerData => {
+8 -7
View File
@@ -19,12 +19,13 @@ IW4L/
`iw4l.exe licenses` prints the licence and notice texts compiled into the executable.
The runtime reads the installations those shortcuts point to. MW2 multiplayer data
is required for the menu; BO1 and MW3 are optional. Without `Modern Warfare 2.lnk`
and without MW2 in the folder or its shortcuts, `iw4l.exe` looks in the Steam
libraries and, when exactly one has MW2 multiplayer data, creates
`Modern Warfare 2.lnk` to it. Otherwise it shows the folders it tried and how to
add the shortcut by hand: right-click inside the folder, New > Shortcut, paste the
MW2 folder path.
is required for the menu; BO1 and MW3 are optional. For each missing title,
`iw4l.exe` checks the Steam libraries and creates its shortcut when exactly one
install has supported multiplayer data. Existing shortcuts and titles already
found in the configured folders are preserved. If several installs qualify,
create a shortcut to the one you want. If MW2 is still missing, the launcher shows
the folders it tried and how to add the shortcut: right-click inside the launcher
folder, New > Shortcut, paste the game folder path.
Launch `iw4l.exe`. Before starting the game or contacting QUIC, it checks its
community's HTTPS manifest. An unchanged executable starts normally. An update
@@ -46,5 +47,5 @@ One adjacent `.iw4l-server` is selected automatically; with several, set
`IW4L_COMMUNITY` to the chosen file path before launching. See [`MASTER.md`](MASTER.md).
On Windows the executable directory is the working directory. Game discovery
uses `IW4L_GAMES` or shortcuts beside `iw4l.exe`, then Steam for MW2; local `.env` settings remain
uses `IW4L_GAMES` or shortcuts beside `iw4l.exe`, then Steam for MW2, BO1 and MW3; local `.env` settings remain
available for game configuration. Publishing: [`DEPLOY.md`](DEPLOY.md).