mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-25 08:27:44 +00:00
Add version checks in the patch server for game and boot components
As 7.2 is releasing next week, it would be nice for the patch server to double check the user has the correct version of the game. Now the patch server rejects clients that have too new of a version.
This commit is contained in:
parent
65500d15ad
commit
1e343d0f10
5 changed files with 158 additions and 3 deletions
|
@ -7,6 +7,8 @@ use axum::response::IntoResponse;
|
||||||
use axum::routing::post;
|
use axum::routing::post;
|
||||||
use axum::{Router, routing::get};
|
use axum::{Router, routing::get};
|
||||||
use kawari::config::get_config;
|
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};
|
use physis::patchlist::{PatchEntry, PatchList, PatchListType};
|
||||||
|
|
||||||
fn list_patch_files(dir_path: &str) -> Vec<String> {
|
fn list_patch_files(dir_path: &str) -> Vec<String> {
|
||||||
|
@ -73,6 +75,7 @@ fn check_valid_patch_client(headers: &HeaderMap) -> bool {
|
||||||
async fn verify_session(
|
async fn verify_session(
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Path((platform, channel, game_version, sid)): Path<(String, String, String, String)>,
|
Path((platform, channel, game_version, sid)): Path<(String, String, String, String)>,
|
||||||
|
body: String,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
if !check_valid_patch_client(&headers) {
|
if !check_valid_patch_client(&headers) {
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
|
@ -83,6 +86,45 @@ async fn verify_session(
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tracing::info!("Verifying game components for {platform} {channel} {game_version} {body}...");
|
||||||
|
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
|
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert("X-Patch-Unique-Id", sid.parse().unwrap());
|
headers.insert("X-Patch-Unique-Id", sid.parse().unwrap());
|
||||||
|
|
||||||
|
@ -104,8 +146,20 @@ async fn verify_boot(
|
||||||
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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];
|
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
|
// check if we need any patching
|
||||||
let patches = list_patch_files(&config.patch.patches_location);
|
let patches = list_patch_files(&config.patch.patches_location);
|
||||||
|
@ -115,7 +169,7 @@ async fn verify_boot(
|
||||||
// not up to date!
|
// not up to date!
|
||||||
let patch_list = PatchList {
|
let patch_list = PatchList {
|
||||||
id: "477D80B1_38BC_41d4_8B48_5273ADB89CAC".to_string(),
|
id: "477D80B1_38BC_41d4_8B48_5273ADB89CAC".to_string(),
|
||||||
requested_version: boot_version.clone(),
|
requested_version: boot_version.to_string().clone(),
|
||||||
patch_length: todo!(),
|
patch_length: todo!(),
|
||||||
content_location: todo!(),
|
content_location: todo!(),
|
||||||
patches: vec![PatchEntry {
|
patches: vec![PatchEntry {
|
||||||
|
|
|
@ -122,7 +122,7 @@ pub struct PatchConfig {
|
||||||
/// For example, "patch-dl.ffxiv.localhost". Patch files must be served so they're accessible as: "http://patch-dl.ffxiv.localhost/game/ex4/somepatchfilename.patch"
|
/// For example, "patch-dl.ffxiv.localhost". Patch files must be served so they're accessible as: "http://patch-dl.ffxiv.localhost/game/ex4/somepatchfilename.patch"
|
||||||
pub patch_dl_url: String,
|
pub patch_dl_url: String,
|
||||||
/// Location of the patches directory on disk. Must be setup like so:
|
/// Location of the patches directory on disk. Must be setup like so:
|
||||||
/// ```
|
/// ```ignore
|
||||||
/// <channel> (e.g. ffxivneo_release_game) /
|
/// <channel> (e.g. ffxivneo_release_game) /
|
||||||
/// game/
|
/// game/
|
||||||
/// ex1/
|
/// ex1/
|
||||||
|
|
25
src/lib.rs
25
src/lib.rs
|
@ -1,6 +1,9 @@
|
||||||
//! A server replacement for a certain MMO.
|
//! A server replacement for a certain MMO.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use minijinja::Environment;
|
use minijinja::Environment;
|
||||||
|
use patch::Version;
|
||||||
|
|
||||||
/// The blowfish implementation used for packet encryption.
|
/// The blowfish implementation used for packet encryption.
|
||||||
pub mod blowfish;
|
pub mod blowfish;
|
||||||
|
@ -26,6 +29,28 @@ pub mod packet;
|
||||||
/// Logic server-specific code.
|
/// Logic server-specific code.
|
||||||
pub mod login;
|
pub mod login;
|
||||||
|
|
||||||
|
/// Patch server-specific code.
|
||||||
|
pub mod patch;
|
||||||
|
|
||||||
|
/// Supported boot version.
|
||||||
|
pub const SUPPORTED_BOOT_VERSION: Version = Version("2025.01.10.0000.0001");
|
||||||
|
|
||||||
|
/// Supported game version.
|
||||||
|
pub const SUPPORTED_GAME_VERSION: Version = Version("2025.02.27.0000.0000");
|
||||||
|
|
||||||
|
const SUPPORTED_EXPAC_VERSIONS: [(&str, Version); 5] = [
|
||||||
|
("ex1", Version("2025.01.09.0000.0000")),
|
||||||
|
("ex2", Version("2025.01.14.0000.0000")),
|
||||||
|
("ex3", Version("2025.02.27.0000.0000")),
|
||||||
|
("ex4", Version("2025.02.27.0000.0000")),
|
||||||
|
("ex5", Version("2025.02.27.0000.0000")),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Supported expansion versions.
|
||||||
|
pub fn get_supported_expac_versions() -> HashMap<&'static str, Version<'static>> {
|
||||||
|
HashMap::from(SUPPORTED_EXPAC_VERSIONS)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setup_default_environment() -> Environment<'static> {
|
pub fn setup_default_environment() -> Environment<'static> {
|
||||||
let mut env = Environment::new();
|
let mut env = Environment::new();
|
||||||
env.add_template("admin.html", include_str!("../templates/admin.html"))
|
env.add_template("admin.html", include_str!("../templates/admin.html"))
|
||||||
|
|
2
src/patch/mod.rs
Normal file
2
src/patch/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
mod version;
|
||||||
|
pub use version::Version;
|
74
src/patch/version.rs
Normal file
74
src/patch/version.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
fmt::{self, Display, Formatter},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, PartialOrd)]
|
||||||
|
pub struct Version<'a>(pub &'a str);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Ord, PartialOrd)]
|
||||||
|
struct VersionParts {
|
||||||
|
year: i32,
|
||||||
|
month: i32,
|
||||||
|
day: i32,
|
||||||
|
patch1: i32,
|
||||||
|
patch2: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionParts {
|
||||||
|
fn new(version: &str) -> Self {
|
||||||
|
let parts: Vec<&str> = version.split('.').collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
year: parts[0].parse::<i32>().unwrap(),
|
||||||
|
month: parts[1].parse::<i32>().unwrap(),
|
||||||
|
day: parts[2].parse::<i32>().unwrap(),
|
||||||
|
patch1: parts[3].parse::<i32>().unwrap(),
|
||||||
|
patch2: parts[4].parse::<i32>().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Version<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Version<'_> {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
let our_version_parts = VersionParts::new(self.0);
|
||||||
|
let their_version_parts = VersionParts::new(other.0);
|
||||||
|
|
||||||
|
our_version_parts.cmp(&their_version_parts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_eq() {
|
||||||
|
assert!(Version("2025.02.27.0000.0000") == Version("2025.02.27.0000.0000"));
|
||||||
|
assert!(Version("2025.01.20.0000.0000") != Version("2025.02.27.0000.0000"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ordering() {
|
||||||
|
// year
|
||||||
|
assert!(Version("2025.02.27.0000.0000") > Version("2024.02.27.0000.0000"));
|
||||||
|
|
||||||
|
// month
|
||||||
|
assert!(Version("2025.03.27.0000.0000") > Version("2025.02.27.0000.0000"));
|
||||||
|
|
||||||
|
// day
|
||||||
|
assert!(Version("2025.02.28.0000.0000") > Version("2025.02.27.0000.0000"));
|
||||||
|
|
||||||
|
// patch1
|
||||||
|
assert!(Version("2025.02.27.1000.0000") > Version("2025.02.27.0000.0000"));
|
||||||
|
|
||||||
|
// patch2
|
||||||
|
assert!(Version("2025.02.27.0000.1000") > Version("2025.02.27.0000.0000"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue