1
Fork 0
mirror of https://github.com/redstrate/Kawari.git synced 2025-04-28 17:37:45 +00:00
kawari/src/bin/kawari-patch.rs

216 lines
7.1 KiB
Rust
Raw Normal View History

use std::cmp::Ordering;
use std::fs::read_dir;
2023-10-06 17:46:58 -04:00
2025-03-08 13:27:41 -05:00
use axum::extract::Path;
use axum::http::{HeaderMap, StatusCode};
use axum::response::IntoResponse;
2023-10-06 17:46:58 -04:00
use axum::routing::post;
use axum::{Router, routing::get};
use kawari::config::get_config;
use kawari::patch::Version;
use kawari::{SUPPORTED_BOOT_VERSION, SUPPORTED_GAME_VERSION, get_supported_expac_versions};
use physis::patchlist::{PatchEntry, PatchList, PatchListType};
fn list_patch_files(dir_path: &str) -> Vec<String> {
// If the dir doesn't exist, pretend there is no patch files
let Ok(dir) = read_dir(dir_path) else {
2025-03-08 13:27:41 -05:00
return Vec::new();
};
let mut entries: Vec<_> = dir.flatten().collect();
entries.sort_by_key(|dir| dir.path());
let mut game_patches: Vec<_> = entries
.into_iter()
.flat_map(|entry| {
let Ok(meta) = entry.metadata() else {
return vec![];
};
if meta.is_dir() {
return vec![];
}
if meta.is_file() && entry.file_name().to_str().unwrap().contains(".patch") {
return vec![entry.path()];
}
vec![]
})
.collect();
game_patches.sort_by(|a, b| {
// Ignore H/D in front of filenames
let a_path = a
2025-03-08 13:27:41 -05:00
.as_path()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
if a_path.starts_with("H") {
return Ordering::Less;
}
let b_path = b
2025-03-08 13:27:41 -05:00
.as_path()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_string();
/*if b_path.starts_with("H") {
return Ordering::Greater;
}*/
a_path.partial_cmp(&b_path).unwrap()
}); // ensure we're actually installing them in the correct order
2025-03-08 13:27:41 -05:00
game_patches
.iter()
.map(|x| x.file_stem().unwrap().to_str().unwrap().to_string())
.collect()
}
2024-05-11 13:41:00 -04:00
2025-03-23 07:25:23 -04:00
/// Check if it's a valid patch client connecting
fn check_valid_patch_client(headers: &HeaderMap) -> bool {
let Some(user_agent) = headers.get("User-Agent") else {
return false;
};
user_agent == "FFXIV PATCH CLIENT"
}
2025-03-08 13:27:41 -05:00
async fn verify_session(
2025-03-23 07:25:23 -04:00
headers: HeaderMap,
Path((platform, channel, game_version, sid)): Path<(String, String, String, String)>,
body: String,
2025-03-08 13:27:41 -05:00
) -> impl IntoResponse {
2025-03-23 07:25:23 -04:00
if !check_valid_patch_client(&headers) {
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
2024-05-11 13:41:00 -04:00
let config = get_config();
if !config.supports_platform(&platform) {
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
2023-10-06 17:46:58 -04:00
tracing::info!("Verifying game components for {platform} {channel} {game_version} {body}...");
let body_parts: Vec<&str> = body.split('\n').collect();
2025-03-23 18:17:20 -04:00
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 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];
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!("{game_version} is above 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());
return (headers).into_response();
}
2023-10-06 17:46:58 -04:00
let mut headers = HeaderMap::new();
headers.insert("X-Patch-Unique-Id", sid.parse().unwrap());
2024-05-11 13:41:00 -04:00
(headers).into_response()
2023-10-06 17:46:58 -04:00
}
2025-03-23 07:25:23 -04:00
async fn verify_boot(
headers: HeaderMap,
Path((platform, channel, boot_version)): Path<(String, String, String)>,
) -> impl IntoResponse {
if !check_valid_patch_client(&headers) {
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
tracing::info!("Verifying boot components for {platform} {channel} {boot_version}...");
2024-05-11 13:41:00 -04:00
let config = get_config();
if !config.supports_platform(&platform) {
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
2023-10-06 17:46:58 -04:00
let actual_boot_version = boot_version.split("?time").collect::<Vec<&str>>()[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
2025-03-23 07:35:11 -04:00
let patches = list_patch_files(&config.patch.patches_location);
for patch in patches {
let patch_str: &str = &patch;
if actual_boot_version.partial_cmp(patch_str).unwrap() == Ordering::Less {
// not up to date!
let patch_list = PatchList {
id: "477D80B1_38BC_41d4_8B48_5273ADB89CAC".to_string(),
requested_version: boot_version.to_string().clone(),
2025-03-23 18:17:20 -04:00
patch_length: 0,
content_location: String::new(),
2025-03-08 13:27:41 -05:00
patches: vec![PatchEntry {
2025-03-23 07:35:11 -04:00
url: format!("http://{}/{}", config.patch.patch_dl_url, patch).to_string(),
2025-03-08 13:27:41 -05:00
version: "2023.09.15.0000.0000".to_string(),
hash_block_size: 50000000,
length: 1479062470,
size_on_disk: 0,
hashes: vec![],
unknown_a: 0,
unknown_b: 0,
}],
};
let patch_list_str = patch_list.to_string(PatchListType::Boot);
return patch_list_str.into_response();
}
}
let headers = HeaderMap::new();
2024-05-11 13:41:00 -04:00
(headers).into_response()
2023-10-06 17:46:58 -04:00
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let app = Router::new()
2025-03-08 13:27:41 -05:00
.route(
2025-03-23 07:25:23 -04:00
"/http/{platform}/{channel}/{game_version}/{sid}",
2025-03-08 13:27:41 -05:00
post(verify_session),
)
.route(
2025-03-23 07:25:23 -04:00
"/http/{platform}/{channel}/{boot_version}",
2025-03-08 13:27:41 -05:00
get(verify_boot),
2025-03-23 07:25:23 -04:00
);
2023-10-06 17:46:58 -04:00
let config = get_config();
let addr = config.patch.get_socketaddr();
tracing::info!("Patch server started on {addr}");
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}