1
Fork 0
mirror of https://github.com/redstrate/Physis.git synced 2025-06-07 23:27:45 +00:00

Add PatchList parsing from Kawari

This was originally from my Kawari project, but this belongs in here, so
we can share it between Kawari and Astra.
This commit is contained in:
Joshua Goins 2024-08-22 20:38:50 -04:00
parent 7b8c4c9c5f
commit 979b8df45d
2 changed files with 357 additions and 0 deletions

View file

@ -165,3 +165,6 @@ pub mod stm;
/// Find existing installation directories /// Find existing installation directories
pub mod existing_dirs; pub mod existing_dirs;
/// Reading patch lists
pub mod patchlist;

354
src/patchlist.rs Normal file
View file

@ -0,0 +1,354 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-or-later
/// Represents a patch to be downloaded.
pub struct PatchEntry {
/// The URL of the patch file. Usually an HTTP URL.
pub url: String,
/// The version for this patch.
pub version: String,
/// How many bytes each SHA1 hash block is considering.
pub hash_block_size: i64,
/// Length of the patch file (in bytes.)
pub length: i64,
/// New size of the (game?) when the patch is installed.
pub size_on_disk: i64,
/// The list of SHA1 hashes.
pub hashes: Vec<String>,
// TODO: figure out what this is
unknown_a: i32,
// TODO: ditto
unknown_b: i32,
}
/// A list of patch files the client is requested to download, and install.
pub struct PatchList {
/// The id of the patch list.
// FIXME: this is most likely auto-generated, not set?
pub id: String,
/// Size of all the patch files by size (in bytes.)
pub patch_length: u64,
/// The content location, usually from an HTTP URL.
pub content_location: String,
/// The version that was requested from the server.
pub requested_version: String,
/// The list of patches.
pub patches: Vec<PatchEntry>
}
/// The kind of patch list.
/// This must be the kind of patch list you're parsing, or else the fields will filled in the wrong order.
#[derive(PartialEq)]
#[repr(C)]
pub enum PatchListType {
/// A boot patch list.
Boot,
/// A game patch ist.
Game
}
impl PatchList {
pub fn from_string(patch_type: PatchListType, encoded: &str) -> Self {
let mut patches = vec![];
let mut patch_length = 0;
if let Some(patch_length_index) = encoded.find("X-Patch-Length: ") {
let rest_of_string = &encoded[patch_length_index..];
if let Some(end_of_number_index) = rest_of_string.find("\r\n") {
let patch_length_parse: Result<u64, _> = rest_of_string[0..end_of_number_index].parse();
if let Ok(p) = patch_length_parse {
patch_length = p;
}
}
};
let parts: Vec<_> = encoded.split("\r\n").collect();
for i in 5..parts.len() - 2 {
let patch_parts: Vec<_> = parts[i].split('\t').collect();
if patch_type == PatchListType::Boot {
patches.push(PatchEntry {
url: patch_parts[5].parse().unwrap(),
version: patch_parts[4].parse().unwrap(),
hash_block_size: 0,
length: patch_parts[0].parse().unwrap(),
size_on_disk: patch_parts[1].parse().unwrap(),
hashes: vec![],
unknown_a: 0,
unknown_b: 0,
});
} else {
patches.push(PatchEntry {
url: patch_parts[8].parse().unwrap(),
version: patch_parts[4].parse().unwrap(),
hash_block_size: patch_parts[6].parse().unwrap(),
length: patch_parts[0].parse().unwrap(),
size_on_disk: patch_parts[1].parse().unwrap(),
hashes: patch_parts[7].split(',').map(|x| x.to_string()).collect(),
unknown_a: 0,
unknown_b: 0,
});
}
}
Self {
id: "".to_string(),
content_location: "".to_string(),
requested_version: "".to_string(),
patch_length: patch_length,
patches,
}
}
pub fn to_string(&self, patch_type: PatchListType) -> String {
let mut str = String::new();
// header
str.push_str("--");
str.push_str(&self.id);
str.push_str("\r\n");
str.push_str("Content-Type: application/octet-stream\r\n");
str.push_str(&format!("Content-Location: {}\r\n", self.content_location));
let mut total_patch_size = 0;
for patch in &self.patches {
total_patch_size += patch.length;
}
str.push_str(&format!("X-Patch-Length: {}\r\n", total_patch_size));
str.push_str("\r\n");
for patch in &self.patches {
// length
str.push_str(&patch.length.to_string());
str.push('\t');
// TODO: unknown value, but i *suspect* is the size of the game on disk once this patch is applied.
// which would make sense for the launcher to check for
str.push_str(&patch.size_on_disk.to_string());
str.push('\t');
// TODO: totally unknown
str.push_str(&patch.unknown_a.to_string());
str.push('\t');
// TODO: unknown too
str.push_str(&patch.unknown_b.to_string());
str.push('\t');
// version (e.g. 2023.09.15.0000.0000)
str.push_str(&patch.version);
str.push('\t');
if patch_type == PatchListType::Game {
// hash type
// TODO: does this need to be configurable?
str.push_str("sha1");
str.push('\t');
// hash block size
str.push_str(&patch.hash_block_size.to_string());
str.push('\t');
// hashes
str.push_str(&patch.hashes[0]);
for hash in &patch.hashes[1..] {
str.push(',');
str.push_str(&hash);
}
str.push('\t');
}
// url
str.push_str(&patch.url);
str.push_str("\r\n");
}
str.push_str("--");
str.push_str(&self.id);
str.push_str("--\r\n");
str
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_boot_patch_parsing() {
let test_case = "--477D80B1_38BC_41d4_8B48_5273ADB89CAC\r\nContent-Type: application/octet-stream\r\nContent-Location: \
ffxivpatch/2b5cbc63/metainfo/D2023.04.28.0000.0001.http\r\nX-Patch-Length: \
22221335\r\n\r\n22221335\t69674819\t19\t18\t2023.09.14.0000.0001\thttp://patch-dl.ffxiv.com/boot/2b5cbc63/\
D2023.09.14.0000.0001.patch\r\n--477D80B1_38BC_41d4_8B48_5273ADB89CAC--\r\n";
let patch_list = PatchList::from_string(PatchListType::Boot, test_case);
assert_eq!(patch_list.patches.len(), 1);
assert_eq!(patch_list.patches[0].version, "2023.09.14.0000.0001");
assert_eq!(patch_list.patches[0].url, "http://patch-dl.ffxiv.com/boot/2b5cbc63/D2023.09.14.0000.0001.patch");
}
#[test]
fn test_game_patch_parsing() {
let test_case = "--477D80B1_38BC_41d4_8B48_5273ADB89CAC\r\nContent-Type: application/octet-stream\r\nContent-Location: \
ffxivpatch/4e9a232b/metainfo/2023.07.26.0000.0000.http\r\nX-Patch-Length: \
1664916486\r\n\r\n1479062470\t44145529682\t71\t11\t2023.09.15.0000.0000\tsha1\t50000000\t1c66becde2a8cf26a99d0fc7c06f15f8bab2d87c,\
950725418366c965d824228bf20f0496f81e0b9a,cabef48f7bf00fbf18b72843bdae2f61582ad264,53608de567b52f5fdb43fdb8b623156317e26704,\
f0bc06cabf9ff6490f36114b25f62619d594dbe8,3c5e4b962cd8445bd9ee29011ecdb331d108abd8,88e1a2a322f09de3dc28173d4130a2829950d4e0,\
1040667917dc99b9215dfccff0e458c2e8a724a8,149c7e20e9e3e376377a130e0526b35fd7f43df2,1bb4e33807355cdf46af93ce828b6e145a9a8795,\
a79daff43db488f087da8e22bb4c21fd3a390f3c,6b04fadb656d467fb8318eba1c7f5ee8f030d967,a6641e1c894db961a49b70fda2b0d6d87be487a7,\
edf419de49f42ef19bd6814f8184b35a25e9e977,c1525c4df6001b66b575e2891db0284dc3a16566,01b7628095b07fa3c9c1aed2d66d32d118020321,\
991b137ea0ebb11bd668f82149bc2392a4cbcf52,ad3f74d4fca143a6cf507fc859544a4bcd501d85,936a0f1711e273519cae6b2da0d8b435fe6aa020,\
023f19d8d8b3ecaaf865e3170e8243dd437a384c,2d9e934de152956961a849e81912ca8d848265ca,8e32f9aa76c95c60a9dbe0967aee5792b812d5ec,\
dee052b9aa1cc8863efd61afc63ac3c2d56f9acc,fa81225aea53fa13a9bae1e8e02dea07de6d7052,59b24693b1b62ea1660bc6f96a61f7d41b3f7878,\
349b691db1853f6c0120a8e66093c763ba6e3671,4561eb6f954d80cdb1ece3cc4d58cbd864bf2b50,de94175c4db39a11d5334aefc7a99434eea8e4f9,\
55dd7215f24441d6e47d1f9b32cebdb041f2157f,2ca09db645cfeefa41a04251dfcb13587418347a\thttp://patch-dl.ffxiv.com/game/4e9a232b/\
D2023.09.15.0000.0000.patch\r\n61259063\t44145955874\t71\t11\t2023.09.21.0000.0001\tsha1\t50000000\t88c9bbfe2af4eea7b56384baeeafd59afb47ddeb,\
095c26e87b4d25505845515c389dd22dd429ea7e\thttp://patch-dl.ffxiv.com/game/4e9a232b/\
D2023.09.21.0000.0001.patch\r\n63776300\t44146911186\t71\t11\t2023.09.23.0000.0001\tsha1\t50000000\tc8fc6910be12d10b39e4e6ae980d4c219cfe56a1,\
5c0199b7147a47f620a2b50654a87c9b0cbcf43b\thttp://patch-dl.ffxiv.com/game/4e9a232b/
D2023.09.23.0000.0001.patch\r\n32384649\t44146977234\t71\t11\t2023.09.26.0000.\
0000\tsha1\t50000000\t519a5e46edb67ba6edb9871df5eb3991276da254\thttp://patch-dl.ffxiv.com/game/4e9a232b/\
D2023.09.26.0000.0000.patch\r\n28434004\t44147154898\t71\t11\t2023.09.28.0000.\
0000\tsha1\t50000000\ta08e8a071b615b0babc45a09979ab6bc70affe14\thttp://patch-dl.ffxiv.com/game/4e9a232b/\
D2023.09.28.0000.0000.patch\r\n82378953\t5854598228\t30\t4\t2023.07.26.0000.0001\tsha1\t50000000\t07d9fecb3975028fdf81166aa5a4cca48bc5a4b0,\
c10677985f809df93a739ed7b244d90d37456353\thttp://patch-dl.ffxiv.com/game/ex1/6b936f08/\
D2023.07.26.0000.0001.patch\r\n29384945\t5855426508\t30\t4\t2023.09.23.0000.0001\tsha1\t50000000\tcf4970957e846e6cacfdad521252de18afc7e29b\thttp:/\
/patch-dl.ffxiv.com/game/ex1/6b936f08/\
D2023.09.23.0000.0001.patch\r\n136864\t5855426508\t30\t4\t2023.09.26.0000.0000\tsha1\t50000000\t3ca5e0160e1cedb7a2801048408b247095f432ea\thttp://\
patch-dl.ffxiv.com/game/ex1/6b936f08/\
D2023.09.26.0000.0000.patch\r\n126288\t5855426508\t30\t4\t2023.09.28.0000.0000\tsha1\t50000000\t88d201defb32366004c88b236d03278f95d9b442\thttp://\
patch-dl.ffxiv.com/game/ex1/6b936f08/\
D2023.09.28.0000.0000.patch\r\n49352444\t7620831756\t24\t4\t2023.09.23.0000.0001\tsha1\t50000000\t2a05a452281d119241f222f4eae43266a22560fe\thttp:/\
/patch-dl.ffxiv.com/game/ex2/f29a3eb2/\
D2023.09.23.0000.0001.patch\r\n301600\t7620831756\t24\t4\t2023.09.26.0000.0000\tsha1\t50000000\t215de572fe51bca45f83e19d719f52220818bc39\thttp://\
patch-dl.ffxiv.com/game/ex2/f29a3eb2/\
D2023.09.26.0000.0000.patch\r\n385096\t7620831756\t24\t4\t2023.09.28.0000.0000\tsha1\t50000000\t427c3fd61f2ca79698ecd6dc34e3457f6d8c01cd\thttp://\
patch-dl.ffxiv.com/game/ex2/f29a3eb2/\
D2023.09.28.0000.0000.patch\r\n60799419\t9737248724\t26\t4\t2023.09.23.0000.0001\tsha1\t50000000\tab064df7aec0526e8306a78e51adbbba8b129c3f,\
4e1bb58a987b3157c16f59821bc67b1464a301e5\thttp://patch-dl.ffxiv.com/game/ex3/859d0e24/\
D2023.09.23.0000.0001.patch\r\n555712\t9737248724\t26\t4\t2023.09.26.0000.0000\tsha1\t50000000\tdb6b1f34b0b58ca0d0621ff6cebcc63e7eb692c5\thttp://\
patch-dl.ffxiv.com/game/ex3/859d0e24/\
D2023.09.26.0000.0000.patch\r\n579560\t9737248724\t26\t4\t2023.09.28.0000.0000\tsha1\t50000000\t67b5d62ee8202fe045c763deea38c136c5324195\thttp://\
patch-dl.ffxiv.com/game/ex3/859d0e24/\
D2023.09.28.0000.0000.patch\r\n867821466\t12469514712\t40\t4\t2023.09.15.0000.0001\tsha1\t50000000\t08f6164685b4363d719d09a8d0ef0910b48ec4a1,\
05819ea5182885b59f0dfb981ecab159f46d7343,6ab6106ce4153cac5edb26ad0aabc6ba718ee500,3c552f54cc3c101d9f1786156c1cbd9880a7c02f,\
e0d425f6032da1ceb60ff9ca14a10e5e89f23218,245402087bf6d535cb08bbc189647e8f56301722,a3a0630bb4ddd36b532be0e0a8982dbce1bb9053,\
eca8a1394db1e84b9ec11a99bd970e6335326da5,40546e6d37cc5ea21d26c2e00a11f46a13d99a77,f41f312ad72ee65dc97645c8f7426c35970187ca,\
e5a9966528ecab5a51059de3d5cd83a921c73a1c,c03855127d135d22c65e34e615eddbe6f38769e9,befab30a77c14743f53b20910b564bb6a97dfe86,\
dcce8ea707f03606b583d51d947a4cf99b52635e,c4a33a8c51a047706b65887bed804ec6c2c29016,a17bc8bd8709c2a0c5725c134101132d3536e67d,\
d2b277de55a65697d80cfe8ee46199a8d7482c30,6b97cc2862c6f8f5d279be5f28cc13ed011763e5\thttp://patch-dl.ffxiv.com/game/ex4/1bf99b87/\
D2023.09.15.0000.0001.patch\r\n17717567\t12471821656\t40\t4\t2023.09.23.0000.0001\tsha1\t50000000\tda144d5c1c173ef1d98e4e7b558414ae53bcd392\thttp:\
//patch-dl.ffxiv.com/game/ex4/1bf99b87/\
D2023.09.23.0000.0001.patch\r\n3117253\t12471859944\t40\t4\t2023.09.26.0000.0000\tsha1\t50000000\t7acdab61e99d69ffa53e9136f65a1a1d3b33732b\thttp:/\
/patch-dl.ffxiv.com/game/ex4/1bf99b87/\
D2023.09.26.0000.0000.patch\r\n4676853\t12471859944\t40\t4\t2023.09.28.0000.0000\tsha1\t50000000\tce26ccb2115af612ccd4c42c1a27ef2ec925c81e\thttp:/\
/patch-dl.ffxiv.com/game/ex4/1bf99b87/D2023.09.28.0000.0000.patch\r\n--477D80B1_38BC_41d4_8B48_5273ADB89CAC--\r\n";
let patch_list = PatchList::from_string(PatchListType::Game, test_case);
assert_eq!(patch_list.patches.len(), 19);
assert_eq!(patch_list.patches[5].version, "2023.07.26.0000.0001");
assert_eq!(patch_list.patches[5].url, "http://patch-dl.ffxiv.com/game/ex1/6b936f08/D2023.07.26.0000.0001.patch");
}
#[test]
fn test_boot_patch_output() {
let test_case = "--477D80B1_38BC_41d4_8B48_5273ADB89CAC\r\nContent-Type: application/octet-stream\r\nContent-Location: \
ffxivpatch/2b5cbc63/metainfo/D2023.04.28.0000.0001.http\r\nX-Patch-Length: \
22221335\r\n\r\n22221335\t69674819\t19\t18\t2023.09.14.0000.0001\thttp://patch-dl.ffxiv.com/boot/2b5cbc63/\
D2023.09.14.0000.0001.patch\r\n--477D80B1_38BC_41d4_8B48_5273ADB89CAC--\r\n";
let patch_list = PatchList {
id: "477D80B1_38BC_41d4_8B48_5273ADB89CAC".to_string(),
requested_version: "D2023.04.28.0000.0001".to_string(),
content_location: "ffxivpatch/2b5cbc63/metainfo/D2023.04.28.0000.0001.http".to_string(),
patches: vec![
PatchEntry {
url: "http://patch-dl.ffxiv.com/boot/2b5cbc63/D2023.09.14.0000.0001.patch".to_string(),
version: "2023.09.14.0000.0001".to_string(),
hash_block_size: 0,
length: 22221335,
size_on_disk: 69674819,
hashes: vec![],
unknown_a: 19,
unknown_b: 18
}
],
patch_length: 22221335,
};
assert_eq!(patch_list.to_string(PatchListType::Boot), test_case);
}
#[test]
fn test_game_patch_output() {
let test_case = "--477D80B1_38BC_41d4_8B48_5273ADB89CAC\r\nContent-Type: application/octet-stream\r\nContent-Location: \
ffxivpatch/4e9a232b/metainfo/2023.07.26.0000.0000.http\r\nX-Patch-Length: \
1479062470\r\n\r\n1479062470\t44145529682\t71\t11\t2023.09.15.0000.0000\tsha1\t50000000\t1c66becde2a8cf26a99d0fc7c06f15f8bab2d87c,\
950725418366c965d824228bf20f0496f81e0b9a,cabef48f7bf00fbf18b72843bdae2f61582ad264,53608de567b52f5fdb43fdb8b623156317e26704,\
f0bc06cabf9ff6490f36114b25f62619d594dbe8,3c5e4b962cd8445bd9ee29011ecdb331d108abd8,88e1a2a322f09de3dc28173d4130a2829950d4e0,\
1040667917dc99b9215dfccff0e458c2e8a724a8,149c7e20e9e3e376377a130e0526b35fd7f43df2,1bb4e33807355cdf46af93ce828b6e145a9a8795,\
a79daff43db488f087da8e22bb4c21fd3a390f3c,6b04fadb656d467fb8318eba1c7f5ee8f030d967,a6641e1c894db961a49b70fda2b0d6d87be487a7,\
edf419de49f42ef19bd6814f8184b35a25e9e977,c1525c4df6001b66b575e2891db0284dc3a16566,01b7628095b07fa3c9c1aed2d66d32d118020321,\
991b137ea0ebb11bd668f82149bc2392a4cbcf52,ad3f74d4fca143a6cf507fc859544a4bcd501d85,936a0f1711e273519cae6b2da0d8b435fe6aa020,\
023f19d8d8b3ecaaf865e3170e8243dd437a384c,2d9e934de152956961a849e81912ca8d848265ca,8e32f9aa76c95c60a9dbe0967aee5792b812d5ec,\
dee052b9aa1cc8863efd61afc63ac3c2d56f9acc,fa81225aea53fa13a9bae1e8e02dea07de6d7052,59b24693b1b62ea1660bc6f96a61f7d41b3f7878,\
349b691db1853f6c0120a8e66093c763ba6e3671,4561eb6f954d80cdb1ece3cc4d58cbd864bf2b50,de94175c4db39a11d5334aefc7a99434eea8e4f9,\
55dd7215f24441d6e47d1f9b32cebdb041f2157f,2ca09db645cfeefa41a04251dfcb13587418347a\thttp://patch-dl.ffxiv.com/game/4e9a232b/\
D2023.09.15.0000.0000.patch\r\n--477D80B1_38BC_41d4_8B48_5273ADB89CAC--\r\n";
let patch_list = PatchList {
id: "477D80B1_38BC_41d4_8B48_5273ADB89CAC".to_string(),
requested_version: "2023.07.26.0000.0000".to_string(),
content_location: "ffxivpatch/4e9a232b/metainfo/2023.07.26.0000.0000.http".to_string(),
patches: vec![
PatchEntry {
url: "http://patch-dl.ffxiv.com/game/4e9a232b/D2023.09.15.0000.0000.patch".to_string(),
version: "2023.09.15.0000.0000".to_string(),
hash_block_size: 50000000,
length: 1479062470,
size_on_disk: 44145529682,
unknown_a: 71,
unknown_b: 11,
hashes: vec![
"1c66becde2a8cf26a99d0fc7c06f15f8bab2d87c".to_string(),
"950725418366c965d824228bf20f0496f81e0b9a".to_string(),
"cabef48f7bf00fbf18b72843bdae2f61582ad264".to_string(),
"53608de567b52f5fdb43fdb8b623156317e26704".to_string(),
"f0bc06cabf9ff6490f36114b25f62619d594dbe8".to_string(),
"3c5e4b962cd8445bd9ee29011ecdb331d108abd8".to_string(),
"88e1a2a322f09de3dc28173d4130a2829950d4e0".to_string(),
"1040667917dc99b9215dfccff0e458c2e8a724a8".to_string(),
"149c7e20e9e3e376377a130e0526b35fd7f43df2".to_string(),
"1bb4e33807355cdf46af93ce828b6e145a9a8795".to_string(),
"a79daff43db488f087da8e22bb4c21fd3a390f3c".to_string(),
"6b04fadb656d467fb8318eba1c7f5ee8f030d967".to_string(),
"a6641e1c894db961a49b70fda2b0d6d87be487a7".to_string(),
"edf419de49f42ef19bd6814f8184b35a25e9e977".to_string(),
"c1525c4df6001b66b575e2891db0284dc3a16566".to_string(),
"01b7628095b07fa3c9c1aed2d66d32d118020321".to_string(),
"991b137ea0ebb11bd668f82149bc2392a4cbcf52".to_string(),
"ad3f74d4fca143a6cf507fc859544a4bcd501d85".to_string(),
"936a0f1711e273519cae6b2da0d8b435fe6aa020".to_string(),
"023f19d8d8b3ecaaf865e3170e8243dd437a384c".to_string(),
"2d9e934de152956961a849e81912ca8d848265ca".to_string(),
"8e32f9aa76c95c60a9dbe0967aee5792b812d5ec".to_string(),
"dee052b9aa1cc8863efd61afc63ac3c2d56f9acc".to_string(),
"fa81225aea53fa13a9bae1e8e02dea07de6d7052".to_string(),
"59b24693b1b62ea1660bc6f96a61f7d41b3f7878".to_string(),
"349b691db1853f6c0120a8e66093c763ba6e3671".to_string(),
"4561eb6f954d80cdb1ece3cc4d58cbd864bf2b50".to_string(),
"de94175c4db39a11d5334aefc7a99434eea8e4f9".to_string(),
"55dd7215f24441d6e47d1f9b32cebdb041f2157f".to_string(),
"2ca09db645cfeefa41a04251dfcb13587418347a".to_string()
],
}
],
patch_length: 1479062470,
};
assert_eq!(patch_list.to_string(PatchListType::Game), test_case);
}
}