paavohtl Claude Sonnet 4.6 commited on
Commit
9233b4e
Β·
1 Parent(s): 410a66e

feat: add openwa-config crate with WA installation locator

Browse files

Centralizes game path discovery into a new openwa-config crate.
find_wa_dir() checks OPENWA_WA_PATH env var then falls back to
HKCU\Software\Team17SoftwareLTD\WormsArmageddon (PATH value).

Removes all hardcoded Steam library paths from launcher, test runner,
and scheme_parse integration test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Cargo.lock CHANGED
@@ -1469,6 +1469,13 @@ version = "1.21.3"
1469
  source = "registry+https://github.com/rust-lang/crates.io-index"
1470
  checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
1471
 
 
 
 
 
 
 
 
1472
  [[package]]
1473
  name = "openwa-core"
1474
  version = "0.1.0"
@@ -1476,6 +1483,7 @@ dependencies = [
1476
  "heapless",
1477
  "hound",
1478
  "inventory",
 
1479
  "openwa-debug-proto",
1480
  "openwa-derive",
1481
  "windows",
@@ -1518,12 +1526,16 @@ dependencies = [
1518
  [[package]]
1519
  name = "openwa-launcher"
1520
  version = "0.1.0"
 
 
 
1521
 
1522
  [[package]]
1523
  name = "openwa-test-runner"
1524
  version = "0.1.0"
1525
  dependencies = [
1526
  "chrono",
 
1527
  ]
1528
 
1529
  [[package]]
 
1469
  source = "registry+https://github.com/rust-lang/crates.io-index"
1470
  checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
1471
 
1472
+ [[package]]
1473
+ name = "openwa-config"
1474
+ version = "0.1.0"
1475
+ dependencies = [
1476
+ "windows-sys 0.59.0",
1477
+ ]
1478
+
1479
  [[package]]
1480
  name = "openwa-core"
1481
  version = "0.1.0"
 
1483
  "heapless",
1484
  "hound",
1485
  "inventory",
1486
+ "openwa-config",
1487
  "openwa-debug-proto",
1488
  "openwa-derive",
1489
  "windows",
 
1526
  [[package]]
1527
  name = "openwa-launcher"
1528
  version = "0.1.0"
1529
+ dependencies = [
1530
+ "openwa-config",
1531
+ ]
1532
 
1533
  [[package]]
1534
  name = "openwa-test-runner"
1535
  version = "0.1.0"
1536
  dependencies = [
1537
  "chrono",
1538
+ "openwa-config",
1539
  ]
1540
 
1541
  [[package]]
crates/openwa-config/Cargo.toml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "openwa-config"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ description = "Configuration and installation discovery for OpenWA tools"
6
+
7
+ [dependencies]
8
+ windows-sys = { version = "0.59", features = [
9
+ "Win32_Foundation",
10
+ "Win32_System_Registry",
11
+ ] }
crates/openwa-config/src/lib.rs ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Configuration and installation discovery for OpenWA tools.
2
+
3
+ use std::path::PathBuf;
4
+
5
+ use windows_sys::Win32::System::Registry::*;
6
+
7
+ const WA_REGISTRY_KEY: &[u8] = b"Software\\Team17SoftwareLTD\\WormsArmageddon\0";
8
+ const WA_PATH_VALUE: &[u8] = b"PATH\0";
9
+
10
+ /// Locate the Worms Armageddon installation directory.
11
+ ///
12
+ /// Search order:
13
+ /// 1. `OPENWA_WA_PATH` environment variable β€” may be either the WA.exe path or
14
+ /// the installation directory. If it ends with `.exe`, the parent directory
15
+ /// is returned.
16
+ /// 2. Windows registry: `HKEY_CURRENT_USER\Software\Team17SoftwareLTD\WormsArmageddon`,
17
+ /// value `PATH`.
18
+ ///
19
+ /// Returns `None` if the installation cannot be located.
20
+ pub fn find_wa_dir() -> Option<PathBuf> {
21
+ if let Some(dir) = from_env() {
22
+ return Some(dir);
23
+ }
24
+ from_registry()
25
+ }
26
+
27
+ fn from_env() -> Option<PathBuf> {
28
+ let val = std::env::var_os("OPENWA_WA_PATH")?;
29
+ let path = PathBuf::from(val);
30
+ // Accept either a WA.exe path or a directory.
31
+ if path.extension().map(|e| e.eq_ignore_ascii_case("exe")).unwrap_or(false) {
32
+ path.parent().map(|p| p.to_path_buf())
33
+ } else {
34
+ Some(path)
35
+ }
36
+ }
37
+
38
+ fn from_registry() -> Option<PathBuf> {
39
+ let mut buf = [0u8; 512];
40
+ let len = read_registry_sz(WA_REGISTRY_KEY, WA_PATH_VALUE, &mut buf)?;
41
+ let s = std::str::from_utf8(&buf[..len]).ok()?;
42
+ if s.is_empty() {
43
+ return None;
44
+ }
45
+ Some(PathBuf::from(s))
46
+ }
47
+
48
+ /// Read a REG_SZ value from `HKCU\<key>`. Returns the number of bytes written
49
+ /// to `buf` (excluding null terminator), or `None` on any failure.
50
+ fn read_registry_sz(key: &[u8], value: &[u8], buf: &mut [u8]) -> Option<usize> {
51
+ unsafe {
52
+ let mut hkey: HKEY = std::ptr::null_mut();
53
+ let status = RegOpenKeyExA(
54
+ HKEY_CURRENT_USER,
55
+ key.as_ptr(),
56
+ 0,
57
+ KEY_READ,
58
+ &mut hkey,
59
+ );
60
+ if status != 0 {
61
+ return None;
62
+ }
63
+
64
+ let mut data_size = buf.len() as u32;
65
+ let mut value_type: u32 = 0;
66
+ let status = RegQueryValueExA(
67
+ hkey,
68
+ value.as_ptr(),
69
+ std::ptr::null_mut(),
70
+ &mut value_type,
71
+ buf.as_mut_ptr(),
72
+ &mut data_size,
73
+ );
74
+ RegCloseKey(hkey);
75
+
76
+ if status != 0 || value_type != REG_SZ || data_size == 0 {
77
+ return None;
78
+ }
79
+
80
+ // data_size includes the null terminator
81
+ Some((data_size - 1) as usize)
82
+ }
83
+ }
crates/openwa-core/Cargo.toml CHANGED
@@ -11,6 +11,9 @@ inventory = "0.3.22"
11
  openwa-derive = { path = "../openwa-derive" }
12
  openwa-debug-proto = { path = "../openwa-debug-proto" }
13
 
 
 
 
14
  [target.'cfg(target_os = "windows")'.dependencies]
15
  windows = { version = "0.62", features = [
16
  "Win32_Media_Audio_DirectSound",
 
11
  openwa-derive = { path = "../openwa-derive" }
12
  openwa-debug-proto = { path = "../openwa-debug-proto" }
13
 
14
+ [dev-dependencies]
15
+ openwa-config = { path = "../openwa-config" }
16
+
17
  [target.'cfg(target_os = "windows")'.dependencies]
18
  windows = { version = "0.62", features = [
19
  "Win32_Media_Audio_DirectSound",
crates/openwa-core/tests/scheme_parse.rs CHANGED
@@ -181,8 +181,11 @@ fn v2_super_weapons_zeroed_region() {
181
  /// Parse all .wsc files from the game directory if available.
182
  #[test]
183
  fn parse_all_game_schemes() {
184
- let schemes_dir =
185
- Path::new("I:/games/SteamLibrary/steamapps/common/Worms Armageddon/User/Schemes");
 
 
 
186
  if !schemes_dir.exists() {
187
  eprintln!("Skipping: game schemes directory not found");
188
  return;
 
181
  /// Parse all .wsc files from the game directory if available.
182
  #[test]
183
  fn parse_all_game_schemes() {
184
+ let Some(wa_dir) = openwa_config::find_wa_dir() else {
185
+ eprintln!("Skipping: WA installation not found");
186
+ return;
187
+ };
188
+ let schemes_dir = wa_dir.join("User/Schemes");
189
  if !schemes_dir.exists() {
190
  eprintln!("Skipping: game schemes directory not found");
191
  return;
crates/openwa-launcher/Cargo.toml CHANGED
@@ -9,3 +9,4 @@ name = "openwa-launcher"
9
  path = "src/main.rs"
10
 
11
  [dependencies]
 
 
9
  path = "src/main.rs"
10
 
11
  [dependencies]
12
+ openwa-config = { path = "../openwa-config" }
crates/openwa-launcher/src/main.rs CHANGED
@@ -111,13 +111,12 @@ fn run() -> Result<(), String> {
111
  }
112
  }
113
 
114
- // Locate WA.exe: explicit arg > env var > hardcoded fallback paths.
115
  let wa_exe = if let Some(p) = wa_path_arg {
116
  PathBuf::from(p)
117
- } else if let Ok(p) = std::env::var("OPENWA_WA_PATH") {
118
- PathBuf::from(p)
119
  } else {
120
- find_wa_hardcoded()
 
121
  .ok_or("WA.exe not found. Set OPENWA_WA_PATH or pass the path as an argument.")?
122
  };
123
 
@@ -296,14 +295,3 @@ fn path_to_cstring(p: &Path) -> Result<CString, String> {
296
  .map_err(|e| format!("path contains nul byte: {e}"))
297
  }
298
 
299
- /// Try a few well-known default Steam install locations for WA.exe.
300
- /// Users with non-standard library locations should set OPENWA_WA_PATH instead.
301
- fn find_wa_hardcoded() -> Option<PathBuf> {
302
- const CANDIDATES: &[&str] = &[
303
- r"I:\games\SteamLibrary\steamapps\common\Worms Armageddon\WA.exe",
304
- r"C:\Program Files (x86)\Steam\steamapps\common\Worms Armageddon\WA.exe",
305
- r"C:\Program Files\Steam\steamapps\common\Worms Armageddon\WA.exe",
306
- ];
307
-
308
- CANDIDATES.iter().map(PathBuf::from).find(|p| p.exists())
309
- }
 
111
  }
112
  }
113
 
114
+ // Locate WA.exe: explicit arg > env var / registry (via openwa-config).
115
  let wa_exe = if let Some(p) = wa_path_arg {
116
  PathBuf::from(p)
 
 
117
  } else {
118
+ openwa_config::find_wa_dir()
119
+ .map(|d| d.join("WA.exe"))
120
  .ok_or("WA.exe not found. Set OPENWA_WA_PATH or pass the path as an argument.")?
121
  };
122
 
 
295
  .map_err(|e| format!("path contains nul byte: {e}"))
296
  }
297
 
 
 
 
 
 
 
 
 
 
 
 
crates/openwa-test-runner/Cargo.toml CHANGED
@@ -10,3 +10,4 @@ path = "src/main.rs"
10
 
11
  [dependencies]
12
  chrono = "0.4"
 
 
10
 
11
  [dependencies]
12
  chrono = "0.4"
13
+ openwa-config = { path = "../openwa-config" }
crates/openwa-test-runner/src/main.rs CHANGED
@@ -24,11 +24,6 @@ const TIMEOUT_SECS: u64 = 120;
24
  const REPLAYS_DIR: &str = "testdata/replays";
25
  const RUNS_DIR: &str = "testdata/runs";
26
 
27
- /// Known WA.exe locations to try if not specified.
28
- const WA_CANDIDATES: &[&str] = &[
29
- "I:/games/SteamLibrary/steamapps/common/Worms Armageddon/WA.exe",
30
- "C:/Program Files (x86)/Steam/steamapps/common/Worms Armageddon/WA.exe",
31
- ];
32
 
33
  // ─── Types ──────────────────────────────────────────────────────────────────
34
 
@@ -167,22 +162,7 @@ fn find_wa_exe(override_path: Option<&Path>) -> Option<PathBuf> {
167
  return Some(p.to_path_buf());
168
  }
169
  }
170
-
171
- if let Ok(p) = env::var("OPENWA_WA_PATH") {
172
- let path = PathBuf::from(p);
173
- if path.exists() {
174
- return Some(path);
175
- }
176
- }
177
-
178
- for candidate in WA_CANDIDATES {
179
- let path = PathBuf::from(candidate);
180
- if path.exists() {
181
- return Some(path);
182
- }
183
- }
184
-
185
- None
186
  }
187
 
188
  fn find_launcher() -> Option<PathBuf> {
 
24
  const REPLAYS_DIR: &str = "testdata/replays";
25
  const RUNS_DIR: &str = "testdata/runs";
26
 
 
 
 
 
 
27
 
28
  // ─── Types ──────────────────────────────────────────────────────────────────
29
 
 
162
  return Some(p.to_path_buf());
163
  }
164
  }
165
+ openwa_config::find_wa_dir().map(|d| d.join("WA.exe"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  }
167
 
168
  fn find_launcher() -> Option<PathBuf> {