diff --git a/Cargo.lock b/Cargo.lock index 5bf9466..2000500 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,6 +122,7 @@ dependencies = [ "tempfile", "thiserror 2.0.18", "tokio", + "twox-hash", "yaml_serde", ] @@ -2797,6 +2798,15 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" +dependencies = [ + "rand", +] + [[package]] name = "typenum" version = "1.19.0" diff --git a/Cargo.toml b/Cargo.toml index 248067f..4ec32bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,4 +33,5 @@ uuid = "1.23.0" lazy_static = "1.5.0" structopt = "0.3.26" tokio-util = "0.7.18" -futures-util = "0.3.32" \ No newline at end of file +futures-util = "0.3.32" +twox-hash = "2.1.2" \ No newline at end of file diff --git a/assets-updater/Cargo.toml b/assets-updater/Cargo.toml index df9f997..15aa5f0 100644 --- a/assets-updater/Cargo.toml +++ b/assets-updater/Cargo.toml @@ -22,3 +22,4 @@ cridecoder = { workspace = true } thiserror = { workspace = true } log = { workspace = true } cipher = { workspace = true, features = ["block-padding"] } +twox-hash = { workspace = true, features = ["xxhash3_128"] } diff --git a/assets-updater/src/core/asset_execution.rs b/assets-updater/src/core/asset_execution.rs index d1887d8..525e87c 100644 --- a/assets-updater/src/core/asset_execution.rs +++ b/assets-updater/src/core/asset_execution.rs @@ -262,13 +262,17 @@ impl AssetExecutionContext { } RegionProviderConfig::Nuverse { asset_version_url, - app_version, asset_info_url_template, .. } => { // For nuverse, always fetch the version from asset_version_url. // The incoming request.asset_version is intentionally ignored here // to match Go reference behavior. + let app_version = self.sync_context.app_version.as_ref().ok_or_else(|| { + AssetExecutionError::MissingAppVersion { + region: self.region_name.clone(), + } + })?; let version_url = asset_version_url.replace("{app_version}", app_version); let resolved_version = String::from_utf8_lossy(&self.get_with_retry(&version_url).await?) @@ -319,9 +323,13 @@ impl AssetExecutionContext { } RegionProviderConfig::Nuverse { asset_bundle_url_template, - app_version, .. } => { + let app_version = self.sync_context.app_version.as_ref().ok_or_else(|| { + AssetExecutionError::MissingAppVersion { + region: self.region_name.clone(), + } + })?; let asset_version = self .resolved_asset_version .as_deref() @@ -435,6 +443,7 @@ impl AssetExecutionContext { &self.region, &temp_file, &task.bundle_path, + &task.download_path, category, ) .await; diff --git a/assets-updater/src/core/config.rs b/assets-updater/src/core/config.rs index 457a596..cb9cea9 100644 --- a/assets-updater/src/core/config.rs +++ b/assets-updater/src/core/config.rs @@ -150,7 +150,6 @@ pub enum RegionProviderConfig { }, Nuverse { asset_version_url: String, - app_version: String, asset_info_url_template: String, asset_bundle_url_template: String, #[serde(default)] diff --git a/assets-updater/src/core/errors.rs b/assets-updater/src/core/errors.rs index d066526..8294774 100644 --- a/assets-updater/src/core/errors.rs +++ b/assets-updater/src/core/errors.rs @@ -107,6 +107,8 @@ pub enum AssetExecutionError { HttpStatus { url: String, status: u16 }, #[error("region `{region}` is missing asset_save_dir")] MissingAssetSaveDir { region: String }, + #[error("nuverse region `{region}` requires asset_version and asset_hash")] + MissingAppVersion { region: String }, #[error("colorful_palette region `{region}` requires asset_version and asset_hash")] MissingAssetVersionOrHash { region: String }, #[error("colorful_palette region `{region}` is missing profile hash for `{profile}`")] diff --git a/assets-updater/src/core/export_pipeline.rs b/assets-updater/src/core/export_pipeline.rs index aa332d7..b4080cc 100644 --- a/assets-updater/src/core/export_pipeline.rs +++ b/assets-updater/src/core/export_pipeline.rs @@ -54,18 +54,26 @@ pub fn get_export_group(export_path: &str) -> &'static str { "container" } +pub fn get_hex_index(input: &str) -> String { + let hash_val = twox_hash::XxHash3_128::oneshot(input.as_bytes()); + format!("{:032x}", hash_val) +} + pub async fn extract_unity_asset_bundle( app_config: &AppConfig, sync_context: &SyncContext, region: &RegionConfig, asset_bundle_file: &Path, export_path: &str, + download_path: &str, category: &str, ) -> Result<(PathBuf, bool), ExportPipelineError> { + let hash = get_hex_index(export_path); let output_dir = std::env::temp_dir() .join("sekai-updater") .join("extract") - .join(&sync_context.region); + .join(&sync_context.region) + .join(hash); let Some(asset_studio_cli_path) = app_config.tools.asset_studio_cli_path.as_deref() else { return Ok((asset_bundle_file.parent().unwrap().to_path_buf(), true)); }; @@ -82,7 +90,7 @@ pub async fn extract_unity_asset_bundle( }; let actual_export_path = if sync_context.export.by_category { - output_dir.join(category.to_lowercase()).join(export_path) + output_dir.join(category.to_lowercase()) } else { output_dir.join(export_path) }; @@ -126,10 +134,23 @@ pub async fn extract_unity_asset_bundle( .await?; let mut count = 0; - walk(&actual_export_path, &mut |_| count += 1)?; + let mut is_bundle_name_file = true; + let download_name = match download_path.rsplit_once("/") { + None => download_path, + Some((_, name)) => name, + }; + walk(&output_dir, &mut |f| { + count += 1; + let file_name = f.file_name().unwrap().to_string_lossy(); + is_bundle_name_file = is_bundle_name_file + && match file_name.rsplit_once(".") { + Some((name, _)) => name.eq_ignore_ascii_case(download_name), + None => file_name.eq_ignore_ascii_case(download_name), + } + })?; - post_process_exported_files(app_config, sync_context, region, &actual_export_path).await?; - Ok((actual_export_path, count <= 1)) + post_process_exported_files(app_config, sync_context, region, &output_dir).await?; + Ok((output_dir, is_bundle_name_file && count <= 1)) } pub async fn post_process_exported_files( diff --git a/common/src/updater.rs b/common/src/updater.rs index 80f9ea2..e7d4bcf 100644 --- a/common/src/updater.rs +++ b/common/src/updater.rs @@ -12,6 +12,8 @@ pub struct SyncContext { #[serde(default)] pub asset_hash: Option, #[serde(default)] + pub app_version: Option, + #[serde(default)] pub exact_single_file_bundle: bool, } diff --git a/sekai-unpacker-client.yaml b/sekai-unpacker-client.yaml index ab18448..f725d89 100644 --- a/sekai-unpacker-client.yaml +++ b/sekai-unpacker-client.yaml @@ -1,19 +1,20 @@ log_level: "DEBUG" -#client: -# - url: "127.0.0.1:3333" -# token: abc -server: - - url: 127.0.0.1:3333 +client: + - url: "127.0.0.1:3333" token: abc - cert: "D:\\WorkDir\\Nginx\\cert\\_.bluemangoo.net\\_.bluemangoo.net-chain.pem" - key: "D:\\WorkDir\\Nginx\\cert\\_.bluemangoo.net\\_.bluemangoo.net-key.pem" +#server: +# - url: 127.0.0.1:3333 +# token: abc +# cert: "D:\\WorkDir\\Nginx\\cert\\_.bluemangoo.net\\_.bluemangoo.net-chain.pem" +# key: "D:\\WorkDir\\Nginx\\cert\\_.bluemangoo.net\\_.bluemangoo.net-key.pem" profiles: cn: region: cn # interval: 3 # seconds concurrent: 50 + app_version: "6.0.0" filters: start_app: - "thumbnail" @@ -44,7 +45,7 @@ profiles: convert_to_mp3: false convert_to_flac: false remove_wav: false -# exact_single_file_bundle: true + exact_single_file_bundle: true path: "./data/cn" jp: region: jp diff --git a/sekai-unpacker-server.yaml b/sekai-unpacker-server.yaml index b813b92..18447b5 100644 --- a/sekai-unpacker-server.yaml +++ b/sekai-unpacker-server.yaml @@ -1,12 +1,12 @@ log_level: "DEBUG" -client: - - url: "127.0.0.1:3333" - token: abc - host: "local.bluemangoo.net" -#server: -# - url: 127.0.0.1:3333 +#client: +# - url: "127.0.0.1:3333" # token: abc +# host: "local.bluemangoo.net" +server: + - url: 127.0.0.1:3333 + token: abc execution: proxy: "" @@ -49,7 +49,6 @@ regions: provider: kind: nuverse asset_version_url: "https://lf3-mkcncdn-tos.dailygn.com/obj/rt-game-lf/gdl_app_5236/Mainland/{app_version}/Release/cn_online/ios/version" - app_version: "5.2.0" asset_info_url_template: "https://lf3-mkcncdn-tos.dailygn.com/obj/sf-game-lf/gdl_app_5236/AssetBundle/{app_version}/Release/cn_online/ios{asset_version}/AssetBundleInfoNew.json" asset_bundle_url_template: "https://lf3-mkcncdn-tos.dailygn.com/obj/sf-game-lf/gdl_app_5236/AssetBundle/{app_version}/Release/cn_online/{bundle_path}" required_cookies: false diff --git a/server/src/router/download.rs b/server/src/router/download.rs index eb56465..b29626a 100644 --- a/server/src/router/download.rs +++ b/server/src/router/download.rs @@ -7,6 +7,7 @@ use communicator::http::{json_from_request, send, send_error}; use h2::RecvStream; use h2::server::SendResponse; use http::{Request, Response}; +use log::debug; pub async fn download( mut request: Request, @@ -43,58 +44,37 @@ pub async fn download( } let (dir, single_file) = dir.unwrap(); - let dir_base = std::env::temp_dir() - .join("sekai-updater") - .join("extract") - .join(&context.sync_context.region); - - let files = if !dir.is_dir() { - if context.sync_context.filters.file_ext.is_empty() - || dir.extension().is_some_and(|t| { - context - .sync_context - .filters - .file_ext - .contains(&t.to_str().unwrap().to_lowercase()) - }) - { - vec![( - dir.clone(), - dir.strip_prefix(&dir_base) - .unwrap() - .to_string_lossy() - .to_string(), - )] - } else { - vec![] - } + let files = if context.sync_context.filters.file_ext.is_empty() { + find_files(&dir) } else { - let files = if context.sync_context.filters.file_ext.is_empty() { - find_files(&dir) - } else { - find_files_by_extensions(&dir, &context.sync_context.filters.file_ext) - }; - if let Err(error) = files { - send_error(send_response, error.into()); - return Ok(()); - } - let base = dir.strip_prefix(&dir_base).unwrap(); - let base_str = if single_file { - base.parent().unwrap().to_str().unwrap() - } else { - base.to_str().unwrap() - }; - files - .unwrap() - .iter() - .map(|p| { - let raw = p.clone(); - let name = p.strip_prefix(&dir).unwrap(); - let path = format!("{}/{}", base_str, name.to_string_lossy()); - (raw, path) - }) - .collect::>() + find_files_by_extensions(&dir, &context.sync_context.filters.file_ext) }; + if let Err(error) = files { + send_error(send_response, error.into()); + return Ok(()); + } + let files = files + .unwrap() + .iter() + .map(|p| { + let raw = p.clone(); + let name = p.strip_prefix(&dir).unwrap(); + debug!("{} {}", p.to_string_lossy(), single_file); + let path = if context.sync_context.exact_single_file_bundle + && single_file + { + format!( + "{}/{}", + name.parent().unwrap().parent().unwrap().to_string_lossy(), + p.file_name().unwrap().to_string_lossy() + ) + } else { + name.to_string_lossy().to_string() + }; + + (raw, path) + }) + .collect::>(); let response = Response::builder() .status(200)