From 7ccd132129bc9afde8d07231f9c55585c463e7c0 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Fri, 4 Jul 2025 11:56:12 -0400 Subject: [PATCH] Unify version and file checking behind one config parameter And make it affect the patch server as well, if you really want to use an unsupported version. --- src/bin/kawari-lobby.rs | 2 +- src/bin/kawari-patch.rs | 179 ++++++++++++++++++++++------------------ src/config.rs | 11 ++- 3 files changed, 108 insertions(+), 84 deletions(-) diff --git a/src/bin/kawari-lobby.rs b/src/bin/kawari-lobby.rs index 405435a..9aef22a 100644 --- a/src/bin/kawari-lobby.rs +++ b/src/bin/kawari-lobby.rs @@ -224,7 +224,7 @@ async fn main() { let config = get_config(); // The lobby server does its own version check as well, but it can be turned off if desired. - if config.lobby.do_version_checks + if config.enforce_validity_checks && !do_game_version_check(version_info) { // "A version update is required." diff --git a/src/bin/kawari-patch.rs b/src/bin/kawari-patch.rs index 6721911..77b42d7 100644 --- a/src/bin/kawari-patch.rs +++ b/src/bin/kawari-patch.rs @@ -88,48 +88,56 @@ async fn verify_session( return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } - tracing::info!("Verifying game components for {platform} {channel} {game_version} {body}..."); + if config.enforce_validity_checks { + tracing::info!( + "Verifying game components for {platform} {channel} {game_version} {body}..." + ); - let body_parts: Vec<&str> = body.split('\n').collect(); + let body_parts: Vec<&str> = body.split('\n').collect(); - let _hashes = body_parts[0]; - let expansion_versions = &body_parts[1..body_parts.len() - 1]; // last part is empty + let _hashes = body_parts[0]; + let expansion_versions = &body_parts[1..body_parts.len() - 1]; // last part is empty - let game_version = Version(&game_version); + let game_version = Version(&game_version); - let supported_expac_versions = get_supported_expac_versions(); + let supported_expac_versions = get_supported_expac_versions(); - for expansion_version in expansion_versions { - let expac_version_parts: Vec<&str> = expansion_version.split('\t').collect(); - let expansion_name = expac_version_parts[0]; // e.g. ex1 - let expansion_version = expac_version_parts[1]; + for expansion_version in expansion_versions { + let expac_version_parts: Vec<&str> = expansion_version.split('\t').collect(); + let expansion_name = expac_version_parts[0]; // e.g. ex1 + let expansion_version = expac_version_parts[1]; - if Version(expansion_version) > supported_expac_versions[expansion_name] { + if Version(expansion_version) > supported_expac_versions[expansion_name] { + tracing::warn!( + "{expansion_name} {expansion_version} is above supported version {}!", + supported_expac_versions[expansion_name] + ); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + } + + // Their version is too new + if game_version > SUPPORTED_GAME_VERSION { tracing::warn!( - "{expansion_name} {expansion_version} is above supported version {}!", - supported_expac_versions[expansion_name] + "{game_version} is above supported game version {SUPPORTED_GAME_VERSION}!" ); return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } - } - // Their version is too new - if game_version > SUPPORTED_GAME_VERSION { - tracing::warn!("{game_version} is above supported game version {SUPPORTED_GAME_VERSION}!"); - return StatusCode::INTERNAL_SERVER_ERROR.into_response(); - } + if game_version < SUPPORTED_GAME_VERSION { + tracing::warn!( + "{game_version} is below supported game version {SUPPORTED_GAME_VERSION}!" + ); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } - if game_version < SUPPORTED_GAME_VERSION { - tracing::warn!("{game_version} is below supported game version {SUPPORTED_GAME_VERSION}!"); - return StatusCode::INTERNAL_SERVER_ERROR.into_response(); - } + // If we are up to date, yay! + if game_version == SUPPORTED_GAME_VERSION { + let mut headers = HeaderMap::new(); + headers.insert("X-Patch-Unique-Id", sid.parse().unwrap()); - // If we are up to date, yay! - if game_version == SUPPORTED_GAME_VERSION { - let mut headers = HeaderMap::new(); - headers.insert("X-Patch-Unique-Id", sid.parse().unwrap()); - - return (headers).into_response(); + return (headers).into_response(); + } } let mut headers = HeaderMap::new(); @@ -147,66 +155,75 @@ async fn verify_boot( return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } - tracing::info!("Verifying boot components for {platform} {channel} {boot_version}..."); - let config = get_config(); if !config.supports_platform(&platform) { - tracing::warn!("Invalid platform! {platform}"); return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } - let actual_boot_version = boot_version.split("?time").collect::>()[0]; - let boot_version = Version(actual_boot_version); + if config.enforce_validity_checks { + tracing::info!("Verifying boot components for {platform} {channel} {boot_version}..."); - // If we are up to date, yay! - if boot_version == SUPPORTED_BOOT_VERSION { - let headers = HeaderMap::new(); - return (headers).into_response(); - } - - // Their version is too new - if boot_version > SUPPORTED_BOOT_VERSION { - tracing::warn!("{boot_version} is above supported boot version {SUPPORTED_BOOT_VERSION}!"); - return StatusCode::INTERNAL_SERVER_ERROR.into_response(); - } - - // check if we need any patching - let mut send_patches = Vec::new(); - let patches = list_patch_files(&format!("{}/boot", &config.patch.patches_location)); - for patch in patches { - let patch_str: &str = &patch; - if actual_boot_version.partial_cmp(patch_str).unwrap() == Ordering::Less { - let file = std::fs::File::open(&*format!( - "{}/boot/{}.patch", - &config.patch.patches_location, patch_str - )) - .unwrap(); - let metadata = file.metadata().unwrap(); - - send_patches.push(PatchEntry { - url: format!("http://{}/boot/{}.patch", config.patch.patch_dl_url, patch) - .to_string(), - version: patch_str.to_string(), - hash_block_size: 50000000, - length: metadata.len() as i64, - size_on_disk: metadata.len() as i64, // NOTE: wrong but it should be fine to lie - hashes: vec![], - unknown_a: 0, - unknown_b: 0, - }); + let config = get_config(); + if !config.supports_platform(&platform) { + tracing::warn!("Invalid platform! {platform}"); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); } - } - if !send_patches.is_empty() { - let patch_list = PatchList { - id: "477D80B1_38BC_41d4_8B48_5273ADB89CAC".to_string(), - requested_version: boot_version.to_string().clone(), - content_location: format!("ffxivpatch/boot/metainfo/{}.http", boot_version.0), // FIXME: i think this is actually supposed to be the target version - patch_length: 0, - patches: send_patches, - }; - let patch_list_str = patch_list.to_string(PatchListType::Boot); - return patch_list_str.into_response(); + let actual_boot_version = boot_version.split("?time").collect::>()[0]; + let boot_version = Version(actual_boot_version); + + // If we are up to date, yay! + if boot_version == SUPPORTED_BOOT_VERSION { + let headers = HeaderMap::new(); + return (headers).into_response(); + } + + // Their version is too new + if boot_version > SUPPORTED_BOOT_VERSION { + tracing::warn!( + "{boot_version} is above supported boot version {SUPPORTED_BOOT_VERSION}!" + ); + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); + } + + // check if we need any patching + let mut send_patches = Vec::new(); + let patches = list_patch_files(&format!("{}/boot", &config.patch.patches_location)); + for patch in patches { + let patch_str: &str = &patch; + if actual_boot_version.partial_cmp(patch_str).unwrap() == Ordering::Less { + let file = std::fs::File::open(&*format!( + "{}/boot/{}.patch", + &config.patch.patches_location, patch_str + )) + .unwrap(); + let metadata = file.metadata().unwrap(); + + send_patches.push(PatchEntry { + url: format!("http://{}/boot/{}.patch", config.patch.patch_dl_url, patch) + .to_string(), + version: patch_str.to_string(), + hash_block_size: 50000000, + length: metadata.len() as i64, + size_on_disk: metadata.len() as i64, // NOTE: wrong but it should be fine to lie + hashes: vec![], + unknown_a: 0, + unknown_b: 0, + }); + } + } + + if !send_patches.is_empty() { + let patch_list = PatchList { + id: "477D80B1_38BC_41d4_8B48_5273ADB89CAC".to_string(), + requested_version: boot_version.to_string().clone(), + content_location: format!("ffxivpatch/boot/metainfo/{}.http", boot_version.0), // FIXME: i think this is actually supposed to be the target version + patch_length: 0, + patches: send_patches, + }; + let patch_list_str = patch_list.to_string(PatchListType::Boot); + return patch_list_str.into_response(); + } } let headers = HeaderMap::new(); diff --git a/src/config.rs b/src/config.rs index 563a9a5..e6a35fa 100644 --- a/src/config.rs +++ b/src/config.rs @@ -66,7 +66,6 @@ impl FrontierConfig { pub struct LobbyConfig { pub port: u16, pub listen_address: String, - pub do_version_checks: bool, } impl Default for LobbyConfig { @@ -74,7 +73,6 @@ impl Default for LobbyConfig { Self { port: 7000, listen_address: "0.0.0.0".to_string(), - do_version_checks: true, } } } @@ -401,6 +399,10 @@ pub struct Config { /// Enable various packet debug functions. This will clutter your working directory! #[serde(default)] pub packet_debugging: bool, + + /// Enable various validity checks for version and file hashes that emulate retail. + #[serde(default = "Config::default_enforce_validity_checks")] + pub enforce_validity_checks: bool, } impl Default for Config { @@ -418,6 +420,7 @@ impl Default for Config { launcher: LauncherConfig::default(), save_data_bank: SaveDataBankConfig::default(), packet_debugging: false, + enforce_validity_checks: Self::default_enforce_validity_checks(), } } } @@ -426,6 +429,10 @@ impl Config { pub fn supports_platform(&self, platform: &String) -> bool { self.supported_platforms.contains(platform) } + + fn default_enforce_validity_checks() -> bool { + true + } } fn default_supported_platforms() -> Vec {