diff --git a/src/options.rs b/src/options.rs index a126128..e222e34 100644 --- a/src/options.rs +++ b/src/options.rs @@ -89,6 +89,9 @@ pub struct Args { #[arg(long, default_value = "", help = "Asset version for overridden asset hashes.")] pub asset_version: String, + #[arg(long, default_value = "", help = "Global asset version for overridden asset hashes.")] + pub en_asset_version: String, + #[arg(long, default_value_t = false, help = "Force an application update for asset versions not marked as latest")] pub force_updates: bool, diff --git a/src/router/global.rs b/src/router/global.rs index 141cf69..d2067c8 100644 --- a/src/router/global.rs +++ b/src/router/global.rs @@ -68,6 +68,13 @@ static ASSET_VERSIONS: &[AssetVersion] = &[ ]; impl AssetVersion { + fn accepts_override(&self) -> bool { + // The retired GL clients can still use a custom version/hash, but they are not + // current releases. Keep override eligibility separate from update policy. + self.latest || (self.region == "GL" + && self.version == "5260ff15dff8ba0c00ad91400f515f55") + } + fn stock_hash(&self) -> String { if self.latest && self.platform == "Android" && get_easter_mode() { if let Some((_, easter)) = EASTER_HASHES.iter().find(|(r, _)| *r == self.region) { @@ -79,7 +86,11 @@ impl AssetVersion { fn override_pair(&self) -> Option<(String, String)> { let args = crate::get_args(); - let ov = args.asset_version.as_str(); + let ov = if self.region == "GL" { + args.en_asset_version.as_str() + } else { + args.asset_version.as_str() + }; let oh = match (self.region, self.platform) { ("JP", "Windows") => args.windows_asset_hash.as_str(), ("JP", "Linux") => args.linux_asset_hash.as_str(), @@ -111,7 +122,7 @@ fn valid_hashes(asset_version: &str, platform: &str) -> Vec { out.push(entry.stock_hash()); } } - if entry.latest { + if entry.accepts_override() { if let Some((ov, oh)) = entry.override_pair() { if ov == asset_version { out.push(oh); @@ -128,7 +139,7 @@ fn preferred_hash(asset_version: &str, platform: &str) -> Option { if entry.platform != platform { continue; } - if entry.latest { + if entry.accepts_override() { if let Some((ov, oh)) = entry.override_pair() { if ov == asset_version { return Some(oh); @@ -168,7 +179,7 @@ pub fn get_player_region(asset_version: &str) -> Option { if entry.version == asset_version { return Some(entry.region.to_string()); } - if entry.latest { + if entry.accepts_override() { if let Some((ov, _)) = entry.override_pair() { if ov == asset_version { return Some(entry.region.to_string()); @@ -501,6 +512,22 @@ pub(crate) fn get_cards(arr: JsonValue, user: &JsonValue) -> JsonValue { mod platform_tests { use super::*; + #[test] + fn host_version_overrides_keep_jp_and_global_distinct() { + crate::runtime::apply_config_json( + r#"{"assetVersion":"jp-test-version","enAssetVersion":"gl-test-version","jpAndroidAssetHash":"jp-test-hash","enAndroidAssetHash":"gl-test-hash"}"#, + ); + + assert_eq!(get_player_region("jp-test-version").as_deref(), Some("JP")); + assert_eq!(get_player_region("gl-test-version").as_deref(), Some("GL")); + assert_eq!(preferred_hash("jp-test-version", "Android").as_deref(), Some("jp-test-hash")); + assert_eq!(preferred_hash("gl-test-version", "Android").as_deref(), Some("gl-test-hash")); + assert!(valid_hashes("gl-test-version", "Android").contains(&"gl-test-hash".to_string())); + assert!(!ASSET_VERSIONS.iter().any(|entry| entry.region == "GL" && entry.latest)); + + crate::runtime::apply_config_json("{}"); + } + #[test] fn standalone_player_platforms_are_known() { assert_eq!(parse_platform("WindowsPlayer"), "Windows"); diff --git a/src/runtime.rs b/src/runtime.rs index 56f0005..90fb7be 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -23,11 +23,31 @@ pub struct HostConfig { pub port: u16, pub jp_android_asset_hash: String, pub en_android_asset_hash: String, + pub asset_version: String, + pub en_asset_version: String, + pub jp_ios_asset_hash: String, + pub en_ios_asset_hash: String, + pub windows_asset_hash: String, pub enable_custom_songs: bool, pub enable_custom_cards: bool, pub enable_custom_3dmv: bool, pub enable_arcade: bool, pub nerf_custom_cards: bool, + pub owner: Vec, + pub hidden: bool, + pub force_updates: bool, + pub disable_imports: bool, + pub disable_exports: bool, + pub linux_asset_hash: String, + pub macos_asset_hash: String, + pub global_android: String, + pub japan_android: String, + pub global_ios: String, + pub japan_ios: String, + pub arcade_machine_ttl: u64, + pub arcade_session_ttl: u64, + pub image_asset_path: String, + pub masterdata: String, } // Lets an embedding app (or the tests) enable the opt-in custom songs feature @@ -162,6 +182,38 @@ pub fn apply_config_json(json: &str) { cfg.port = parsed["port"].as_u64().unwrap_or(0) as u16; cfg.jp_android_asset_hash = s("jpAndroidAssetHash"); cfg.en_android_asset_hash = s("enAndroidAssetHash"); + cfg.asset_version = s("assetVersion"); + cfg.en_asset_version = s("enAssetVersion"); + cfg.jp_ios_asset_hash = s("jpIosAssetHash"); + cfg.en_ios_asset_hash = s("enIosAssetHash"); + cfg.windows_asset_hash = s("windowsAssetHash"); + cfg.linux_asset_hash = s("linuxAssetHash"); + cfg.macos_asset_hash = s("macosAssetHash"); + cfg.global_android = s("globalAndroid"); + cfg.japan_android = s("japanAndroid"); + cfg.global_ios = s("globalIos"); + cfg.japan_ios = s("japanIos"); + cfg.arcade_machine_ttl = parsed["arcadeMachineTtl"].as_u64().unwrap_or(90); + cfg.arcade_session_ttl = parsed["arcadeSessionTtl"].as_u64().unwrap_or(30); + cfg.image_asset_path = s("imageAssetPath"); + cfg.masterdata = s("masterdata"); + cfg.owner = parsed["ownerUids"].members().filter_map(|v| v.as_i64()).collect(); + cfg.hidden = parsed["hidden"].as_bool().unwrap_or(false); + cfg.force_updates = parsed["forceUpdates"].as_bool().unwrap_or(false); + cfg.disable_imports = parsed["disableImports"].as_bool().unwrap_or(false); + cfg.disable_exports = parsed["disableExports"].as_bool().unwrap_or(false); + cfg.enable_custom_songs = parsed["enableCustomSongs"].as_bool().unwrap_or(false); + cfg.enable_custom_cards = parsed["enableCustomCards"].as_bool().unwrap_or(false); + cfg.nerf_custom_cards = parsed["nerfCustomCards"].as_bool().unwrap_or(false); + cfg.enable_custom_3dmv = parsed["enableCustom3dmv"].as_bool().unwrap_or(false); + cfg.enable_arcade = parsed["enableArcade"].as_bool().unwrap_or(false); + let owners = cfg.owner.clone(); + let masterdata = cfg.masterdata.clone(); + let nerf_custom_cards = cfg.nerf_custom_cards; + drop(cfg); + update_owners(&owners); + update_masterdata_path(&masterdata); + set_nerf_custom_cards(nerf_custom_cards); } pub fn overlay_args(args: &mut crate::options::Args) { @@ -183,6 +235,30 @@ pub fn overlay_args(args: &mut crate::options::Args) { } overlay_str!(jp_android_asset_hash); overlay_str!(en_android_asset_hash); + overlay_str!(asset_version); + overlay_str!(en_asset_version); + overlay_str!(jp_ios_asset_hash); + overlay_str!(en_ios_asset_hash); + overlay_str!(windows_asset_hash); + overlay_str!(linux_asset_hash); + overlay_str!(macos_asset_hash); + overlay_str!(global_android); + overlay_str!(japan_android); + overlay_str!(global_ios); + overlay_str!(japan_ios); + overlay_str!(image_asset_path); + overlay_str!(masterdata); + if cfg.arcade_machine_ttl != 0 { + args.arcade_machine_ttl = cfg.arcade_machine_ttl; + } + if cfg.arcade_session_ttl != 0 { + args.arcade_session_ttl = cfg.arcade_session_ttl; + } + args.owner = cfg.owner.clone(); + args.hidden = cfg.hidden; + args.force_updates = cfg.force_updates; + args.disable_imports = cfg.disable_imports; + args.disable_exports = cfg.disable_exports; // Overlay only ever enables the features; a command-line --enable-custom-* // flag is never overridden back to off if cfg.enable_custom_songs { @@ -226,3 +302,32 @@ pub fn lock_test_data_path() -> std::sync::MutexGuard<'static, ()> { set_enable_arcade(true); guard } + +#[cfg(test)] +#[test] +fn android_host_config_updates_owner_and_feature_options() { + use clap::Parser; + let _guard = lock_test_data_path(); + apply_config_json(r#"{"ownerUids":[42,84],"hidden":true,"forceUpdates":true,"disableImports":true,"disableExports":true,"enableCustomSongs":true,"enableCustomCards":true,"nerfCustomCards":true,"enableCustom3dmv":true,"enableArcade":true,"linuxAssetHash":"linux-hash","macosAssetHash":"mac-hash","globalAndroid":"https://example.com/gl.apk","arcadeMachineTtl":45,"arcadeSessionTtl":15}"#); + let mut args = crate::options::Args::parse_from(["ew"]); + overlay_args(&mut args); + assert_eq!(get_owners(), vec![42, 84]); + assert_eq!(args.owner, vec![42, 84]); + assert!(args.hidden && args.force_updates && args.disable_imports && args.disable_exports); + assert!(args.enable_custom_songs && args.enable_custom_cards && args.nerf_custom_cards); + assert!(get_nerf_custom_cards()); + assert!(args.enable_custom_3dmv && args.enable_arcade); + assert_eq!(args.linux_asset_hash, "linux-hash"); + assert_eq!(args.macos_asset_hash, "mac-hash"); + assert_eq!(args.global_android, "https://example.com/gl.apk"); + assert_eq!(args.arcade_machine_ttl, 45); + assert_eq!(args.arcade_session_ttl, 15); + + apply_config_json(r#"{"ownerUids":[],"enableCustomSongs":false}"#); + let mut reset = crate::options::Args::parse_from(["ew"]); + overlay_args(&mut reset); + assert!(get_owners().is_empty()); + assert!(!reset.enable_custom_songs); + assert!(!reset.hidden); + assert!(!get_nerf_custom_cards()); +}