diff --git a/adapters/src/official/game_main_config.rs b/adapters/src/official/game_main_config.rs index 39f914e..c6096d2 100644 --- a/adapters/src/official/game_main_config.rs +++ b/adapters/src/official/game_main_config.rs @@ -77,8 +77,12 @@ fn parse_legacy_game_main_config(bytes: &[u8]) -> Result Result { - serde_json::from_str(value) - .map_err(|error| format!("Failed to parse decrypted GameMainConfig JSON: {error}")) + let config: YostarJpGameMainConfig = serde_json::from_str(value) + .map_err(|error| format!("Failed to parse decrypted GameMainConfig JSON: {error}"))?; + if !game_main_config_has_known_field(&config) { + return Err("Decrypted GameMainConfig JSON did not contain any known fields".to_string()); + } + Ok(config) } fn parse_obfuscated_game_main_config(bytes: &[u8]) -> Result { @@ -113,6 +117,13 @@ fn parse_obfuscated_game_main_config(bytes: &[u8]) -> Result bool { + config.skip_tutorial.is_some() + || config.language.is_some() + || config.default_connection_group.is_some() + || config.server_info_data_url.is_some() +} + fn decrypt_inferred_ascii_json(bytes: &[u8]) -> Result { if !bytes.len().is_multiple_of(2) { return Err("Obfuscated GameMainConfig byte length is not UTF-16LE aligned".to_string()); @@ -362,9 +373,9 @@ fn round(acc: u32, input: u32) -> u32 { fn avalanche(mut hash: u32) -> u32 { hash ^= hash >> 15; - hash = hash.wrapping_mul(0x85EB_CA6B); + hash = hash.wrapping_mul(0x85EB_CA77); hash ^= hash >> 13; - hash = hash.wrapping_mul(0xC2B2_AE35); + hash = hash.wrapping_mul(0xC2B2_AE3D); hash ^= hash >> 16; hash } @@ -616,6 +627,18 @@ mod tests { ); } + #[test] + fn rejects_legacy_json_without_known_fields() { + let error = parse_game_main_config_json(r#"{"unknown":"value"}"#).unwrap_err(); + + assert!(error.contains("did not contain any known fields")); + } + + #[test] + fn uses_standard_xxhash32_seed_for_game_main_config_key() { + assert_eq!(xxhash32(b"GameMainConfig"), 2131083068); + } + #[test] #[ignore] fn extracts_game_main_config_bytes_from_serialized_assets() { diff --git a/infrastructure/src/official_download.rs b/infrastructure/src/official_download.rs index 7fa4028..43c77ab 100644 --- a/infrastructure/src/official_download.rs +++ b/infrastructure/src/official_download.rs @@ -1684,9 +1684,9 @@ fn xxhash32_round(acc: u32, input: u32) -> u32 { fn xxhash32_avalanche(mut hash: u32) -> u32 { hash ^= hash >> 15; - hash = hash.wrapping_mul(0x85EB_CA6B); + hash = hash.wrapping_mul(0x85EB_CA77); hash ^= hash >> 13; - hash = hash.wrapping_mul(0xC2B2_AE35); + hash = hash.wrapping_mul(0xC2B2_AE3D); hash ^= hash >> 16; hash } @@ -2148,8 +2148,8 @@ exit 22 report.verified_hashes[0].algorithm, OfficialResourceHashAlgorithm::XxHash32Decimal ); - assert_eq!(report.verified_hashes[0].expected, "3223500437"); - assert_eq!(report.verified_hashes[0].actual, "3223500437"); + assert_eq!(report.verified_hashes[0].expected, "2044170421"); + assert_eq!(report.verified_hashes[0].actual, "2044170421"); let manifest = service.read_download_manifest().unwrap(); assert_eq!(manifest.version, DOWNLOAD_MANIFEST_VERSION); @@ -2328,7 +2328,7 @@ exit 22 let bytes = if url.ends_with("/TableBundles/TableCatalog.bytes") { b"ExcelDB.db".to_vec() } else if url.ends_with("/TableBundles/TableCatalog.hash") { - b"3223500437".to_vec() + b"2044170421".to_vec() } else if url.ends_with(".zip") { one_file_zip(b"fixture.txt", b"ok") } else { @@ -2514,9 +2514,11 @@ exit 22 #[test] fn verifies_known_real_seed_catalog_hash_samples() { - assert_eq!(xxhash32(b"ExcelDB.db"), 3223500437); - assert_eq!(xxhash32(b"FullPatch_000.zip"), 3628251239); - assert_eq!(xxhash32(b"JP_Airi_Win.zip"), 876690720); + assert_eq!(xxhash32(b""), 46947589); + assert_eq!(xxhash32(b"ExcelDB.db"), 2044170421); + assert_eq!(xxhash32(b"FullPatch_000.zip"), 4038880697); + assert_eq!(xxhash32(b"JP_Airi_Win.zip"), 1416778215); + assert_eq!(xxhash32(b"ExcelDB.db ExcelDB.db"), 2147704569); } #[test] diff --git a/infrastructure/tests/official_game_main_config_bootstrap.rs b/infrastructure/tests/official_game_main_config_bootstrap.rs index cbee3dd..9792315 100644 --- a/infrastructure/tests/official_game_main_config_bootstrap.rs +++ b/infrastructure/tests/official_game_main_config_bootstrap.rs @@ -787,9 +787,9 @@ fn round(acc: u32, input: u32) -> u32 { fn avalanche(mut hash: u32) -> u32 { hash ^= hash >> 15; - hash = hash.wrapping_mul(0x85EB_CA6B); + hash = hash.wrapping_mul(0x85EB_CA77); hash ^= hash >> 13; - hash = hash.wrapping_mul(0xC2B2_AE35); + hash = hash.wrapping_mul(0xC2B2_AE3D); hash ^= hash >> 16; hash }