3 Commits
Author SHA1 Message Date
Ethan O'Brien 78362608c8 Fix inability to set some options 2026-09-27 14:26:59 -05:00
Ethan O'Brien 8c58ff5b01 Bump version to 2.0.0 2026-09-27 14:06:23 -05:00
Ethan O'Brien 536e66811f Expose more settings 2026-09-27 14:05:08 -05:00
6 changed files with 154 additions and 7 deletions
Generated
+1 -1
View File
@@ -1085,7 +1085,7 @@ dependencies = [
[[package]]
name = "ew"
version = "1.1.0"
version = "2.0.0"
dependencies = [
"actix-multipart",
"actix-web",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "ew"
version = "1.1.0"
version = "2.0.0"
edition = "2024"
exclude = ["assets", "*.db"]
+1 -1
View File
@@ -32,7 +32,7 @@ services:
#EN_ANDROID_ASSET_HASH: ""
#EN_IOS_ASSET_HASH: ""
#WINDOWS_ASSET_HASH: "" # Windows asset hash (JP only; GL Windows is unused)
#FORCE_UPDATES: false # Force outdated clients to update
#FORCE_UPDATES: "true" # Force outdated clients to update
# Everything below is for the "Help" page
#ANDROID_GLOBAL: "link.to/patched/android/global.apk"
+3
View File
@@ -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,
+31 -4
View File
@@ -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<String> {
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<String> {
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<String> {
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");
+117
View File
@@ -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<i64>,
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,34 @@ 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;
}
// An embedding app may supply these options, but its default values must
// not erase flags passed by the standalone server (including Docker).
if !cfg.owner.is_empty() {
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 +306,40 @@ 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());
let mut cli = crate::options::Args::parse_from([
"ew", "--force-updates", "--hidden", "--disable-imports",
"--disable-exports", "--owner", "123",
]);
overlay_args(&mut cli);
assert!(cli.force_updates && cli.hidden && cli.disable_imports && cli.disable_exports);
assert_eq!(cli.owner, vec![123]);
}