diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cfcfe3..ecbef7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - CSV output files now include `FinalBuild` column showing the normalized config - `update_best_car()` tie detection now checks both `car_rid` and `normalized_config` - Applied `rustfmt` formatting across the entire codebase +- `capture run` now reverts the device to its pre-capture state before closing BlueStacks — clears the global proxy, removes the adb reverse rule, unmounts the tmpfs CA overlay, and deletes the CA staging files in `/data/local/tmp` (same revert as `capture clean`) ### Removed - `plans/code-review-v2.md` (completed action items) @@ -36,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `trash.csv` Config column now lists only the copies to delete: for cars where just one tune is useless (e.g. owned 332 + best-everywhere 323) only the dead tune is shown instead of all owned configs - Cars the records site knows but has no times for (typically new prize cars, returned as `200` with an empty body) no longer abort `fetch` after 3 retries — they are skipped with an info log - Empty timing files are now treated like missing files instead of producing `EOF while parsing a value` parse errors +- A crashed or killed capture no longer breaks BlueStacks networking: `capture run` clears a stale global `http_proxy` left behind by a previous run at startup (the setting persists VM reboots and black-holes all proxy-honoring traffic, e.g. Play Store and the browser) +- Missing `u8` type annotation on `parse()` in output tests that broke compilation of `cargo test` and `cargo clippy --all-targets` on current rustc ## [0.3.0] - 2026-05-07 diff --git a/src/capture/windows.rs b/src/capture/windows.rs index 78ae50e..7094219 100644 --- a/src/capture/windows.rs +++ b/src/capture/windows.rs @@ -599,6 +599,35 @@ fn clear_device_proxy(config: &CaptureConfig) { let _ = adb_shell(config, "settings delete global http_proxy"); } +fn remove_adb_reverse(config: &CaptureConfig) { + let remove_socket = format!("--remove tcp:{}", config.proxy.port); + let _ = adb(config, &["reverse", remove_socket.as_str()]); +} + +fn clear_stale_proxy(config: &CaptureConfig) { + if let Ok(value) = adb_shell(config, "settings get global http_proxy") { + let value = value.trim(); + if !value.is_empty() && value != "null" { + clear_device_proxy(config); + println!("cleared stale proxy setting left by a previous run: {value}"); + } + } + remove_adb_reverse(config); +} + +fn revert_device_state(config: &CaptureConfig) { + clear_device_proxy(config); + remove_adb_reverse(config); + let filename = android_ca_filename(CA_COMMON_NAME); + let _ = adb_shell_root(config, &format!("rm -f /system/etc/security/cacerts/{filename}")); + let _ = adb_shell_root(config, "umount /system/etc/security/cacerts"); + let _ = adb_shell_root(config, "rm -rf /data/local/tmp/cacerts-backup"); + let _ = adb_shell(config, "rm -f /data/local/tmp/topdrives-ca.pem"); + println!( + "device reverted: proxy cleared, CA removed from the trust store, staging files deleted" + ); +} + fn setup_adb_reverse(config: &CaptureConfig, port: u16) -> Result<()> { let socket = format!("tcp:{port}"); match adb(config, &["reverse", socket.as_str(), socket.as_str()]) { @@ -661,6 +690,7 @@ async fn command_run(config: &CaptureConfig) -> Result<()> { let (cert_pem, key_pem) = load_or_create_ca(&project_root)?; ensure_device_connected(config)?; + clear_stale_proxy(config); let sdk_level = android_sdk_level(config)?; if sdk_level > 28 { println!("warning: Android {sdk_level} detected, the tmpfs CA injection is only tested up to Android 9"); @@ -705,10 +735,7 @@ async fn command_run(config: &CaptureConfig) -> Result<()> { _ = tokio::signal::ctrl_c() => Err("aborted".into()), }; - clear_device_proxy(config); - let remove_socket = format!("--remove tcp:{}", config.proxy.port); - let _ = adb(config, &["reverse", remove_socket.as_str()]); - println!("device proxy settings restored"); + revert_device_state(config); println!("stopping {GAME_PACKAGE}"); let _ = adb_shell(config, &format!("am force-stop {GAME_PACKAGE}")); @@ -791,13 +818,7 @@ async fn command_replay(config: &CaptureConfig) -> Result<()> { async fn command_clean(config: &CaptureConfig) -> Result<()> { ensure_device_connected(config)?; - clear_device_proxy(config); - let remove_socket = format!("--remove tcp:{}", config.proxy.port); - let _ = adb(config, &["reverse", remove_socket.as_str()]); - let filename = android_ca_filename(CA_COMMON_NAME); - let _ = adb_shell_root(config, &format!("rm -f /system/etc/security/cacerts/{filename}")); - let _ = adb_shell_root(config, "umount /system/etc/security/cacerts"); - println!("proxy settings cleared and CA removed from the device"); + revert_device_state(config); Ok(()) } @@ -824,7 +845,7 @@ commands: setup generate the CA and inject it into BlueStacks run full flow: start BlueStacks if needed, proxy, game launch, garage capture replay re-issue the stored garage request without the emulator - clean remove proxy settings and the injected CA + clean revert the device: clear the proxy, remove the injected CA and staging files configuration is read from {CONFIG_FILE} in the working directory" ); diff --git a/src/output/mod.rs b/src/output/mod.rs index de045bb..160a75b 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -2228,7 +2228,8 @@ mod tests { assert_eq!(written, 2, "low-value car should be filtered out"); let content = fs::read_to_string(dir.path().join("protect.csv")).unwrap(); let rows: Vec<&str> = content.lines().skip(1).collect(); - let rqs: Vec = rows.iter().filter_map(|r| r.split(',').next()?.parse().ok()).collect(); + let rqs: Vec = + rows.iter().filter_map(|r| r.split(',').next()?.parse::().ok()).collect(); assert_eq!(rqs, vec![25, 85], "only cars above the band thresholds, got: {content}"); } @@ -2457,7 +2458,8 @@ mod tests { let content = fs::read_to_string(dir.path().join("trash.csv")).unwrap(); let rows: Vec<&str> = content.lines().skip(1).collect(); - let rqs: Vec = rows.iter().filter_map(|r| r.split(',').next()?.parse().ok()).collect(); + let rqs: Vec = + rows.iter().filter_map(|r| r.split(',').next()?.parse::().ok()).collect(); assert_eq!( rqs, vec![15, 25, 35],