2024-06-29 14:44:13 -04:00
|
|
|
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;
|
2025-03-08 21:54:03 -05:00
|
|
|
use axum::{Router, routing::get};
|
|
|
|
use kawari::config::get_config;
|
2025-03-17 17:36:43 -04:00
|
|
|
use physis::patchlist::{PatchEntry, PatchList, PatchListType};
|
2024-06-29 14:44:13 -04:00
|
|
|
|
|
|
|
fn list_patch_files(dir_path: &str) -> Vec<String> {
|
2025-03-08 13:24:14 -05:00
|
|
|
// 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();
|
2025-03-08 13:24:14 -05:00
|
|
|
};
|
|
|
|
let mut entries: Vec<_> = dir.flatten().collect();
|
2024-06-29 14:44:13 -04:00
|
|
|
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
|
2025-03-08 21:54:03 -05:00
|
|
|
let a_path = a
|
2025-03-08 13:27:41 -05:00
|
|
|
.as_path()
|
|
|
|
.file_name()
|
|
|
|
.unwrap()
|
|
|
|
.to_str()
|
|
|
|
.unwrap()
|
|
|
|
.to_string();
|
2024-06-29 14:44:13 -04:00
|
|
|
if a_path.starts_with("H") {
|
|
|
|
return Ordering::Less;
|
|
|
|
}
|
2025-03-08 21:54:03 -05:00
|
|
|
let b_path = b
|
2025-03-08 13:27:41 -05:00
|
|
|
.as_path()
|
|
|
|
.file_name()
|
|
|
|
.unwrap()
|
|
|
|
.to_str()
|
|
|
|
.unwrap()
|
|
|
|
.to_string();
|
2024-06-29 14:44:13 -04:00
|
|
|
/*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-06-29 14:44:13 -04:00
|
|
|
}
|
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)>,
|
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
|
|
|
|
|
|
|
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-06-29 14:44:13 -04:00
|
|
|
|
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
|
|
|
|
2024-06-29 14:44:13 -04:00
|
|
|
// Turns 2019.03.12.0000.0001/?time=2024-06-29-18-30 into just 2019.03.12.0000.0001
|
|
|
|
let actual_boot_version = boot_version.split("?time").collect::<Vec<&str>>()[0];
|
|
|
|
|
|
|
|
// check if we need any patching
|
|
|
|
let patches = list_patch_files(&config.boot_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!
|
2024-06-29 15:06:48 -04:00
|
|
|
let patch_list = PatchList {
|
|
|
|
id: "477D80B1_38BC_41d4_8B48_5273ADB89CAC".to_string(),
|
|
|
|
requested_version: boot_version.clone(),
|
2025-03-17 17:36:43 -04:00
|
|
|
patch_length: todo!(),
|
|
|
|
content_location: todo!(),
|
2025-03-08 13:27:41 -05:00
|
|
|
patches: vec![PatchEntry {
|
|
|
|
url: format!("http://{}", patch).to_string(),
|
|
|
|
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,
|
|
|
|
}],
|
2024-06-29 15:06:48 -04:00
|
|
|
};
|
2025-03-17 17:36:43 -04:00
|
|
|
let patch_list_str = patch_list.to_string(PatchListType::Boot);
|
2024-06-29 15:06:48 -04:00
|
|
|
return patch_list_str.into_response();
|
2024-06-29 14:44:13 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-08 21:54:03 -05:00
|
|
|
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
|
|
|
|
2025-03-22 16:47:21 -04:00
|
|
|
let config = get_config();
|
|
|
|
|
|
|
|
let addr = config.patch.get_socketaddr();
|
|
|
|
tracing::info!("Patch server started on {addr}");
|
2025-03-22 21:44:28 -04:00
|
|
|
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
|
|
|
|
axum::serve(listener, app).await.unwrap();
|
2025-03-08 13:24:14 -05:00
|
|
|
}
|