diff --git a/Cargo.lock b/Cargo.lock index 5e73ae1..0920bf3 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,6 +310,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hmac-sha512" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a928b002dff1780b7fa21056991d395770ab9359154b8c1724c4d0511dad0a65" + [[package]] name = "indexmap" version = "1.9.1" @@ -464,12 +470,14 @@ dependencies = [ "glam", "half 2.1.0", "hard-xml", + "hmac-sha512", "libz-sys", "paste", "serde", "serde_json", "sha1_smol", "texpresso", + "walkdir", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 28aa7e6..8505893 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,10 +16,13 @@ name = "physis_benchmark" harness = false [dev-dependencies] +walkdir = "2" +hmac-sha512 = "1.1.2" criterion = "0.4.0" [features] retail_game_testing = [] +patch_testing = [] [dependencies] # used for jamcrc implementation, should eventually move away from it diff --git a/README.md b/README.md index 83d60a1..521f963 100755 --- a/README.md +++ b/README.md @@ -43,6 +43,22 @@ Some tests and benchmarks require the environment variable `FFXIV_GAME_DIR` to b since they require a legitimate copy of the retail game data. These tests can be turned on via the `retail_game_testing` feature. +### Game Patch Testing + +Patching is an extremely sensitive operation since it is not easily reversible if done wrong. Repairing the game files +is also an option, but it's time-consuming and not yet implemented in physis. To prevent regressions in patching the +game, I have set up a testing bed for cross-checking our implementation with others. Currently, this is limited to XIVLauncher's implementation, +but I will eventually adopt a way to test the retail patch installer as well. + +1. Enable the `patch_testing` feature. +2. Set a couple of environment variables: + * `FFXIV_PATCH_DIR` is the directory of patches to install. It should be structured as `$FFXIV_PATCH_DIR/game/D2017.07.11.0000.0001.patch`. + * `FFXIV_XIV_LAUNCHER_PATCHER` should be the path to the patcher executable. If you're running on Linux, we will handle running Wine for you. + * `FFXIV_INSTALLER` is the path to the installer executable. This will be installed using the usual InstallShield emulation physis already includes. + +As you can see, you must have the previous patches downloaded first as well as the installer before running the tests. +This is left up to the developer to figure out how to download them legally. + ## Contributing & Support The best way you can help is by [monetarily supporting me](https://redstrate.com/about/) or by submitting patches to help fix bugs or add functionality! diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 007409f..7f43faa 100755 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,5 +1,10 @@ +use std::collections::HashMap; use physis::index; use std::env; +use std::process::Command; +use physis::installer::install_game; +use physis::patch::apply_patch; +use walkdir::{DirEntry, WalkDir}; #[test] #[cfg_attr(not(feature = "retail_game_testing"), ignore)] @@ -23,3 +28,116 @@ fn test_gamedata_extract() { assert!(gamedata.extract("exd/root.exl").is_some()); } + +#[test] +#[cfg_attr(not(feature = "patch_testing"), ignore)] +fn test_patching() { + let patch_dir = env::var("FFXIV_PATCH_DIR").unwrap(); + let patcher_exe = env::var("FFXIV_XIV_LAUNCHER_PATCHER").unwrap(); + let installer_exe = env::var("FFXIV_INSTALLER").unwrap(); + + println!("Beginning game installation..."); + + // FIXME: lmao what is going on here + let mut game_dir = env::temp_dir(); + game_dir.push("game_test"); + + if std::fs::read_dir(&game_dir).ok().is_some() { + std::fs::remove_dir_all(&game_dir).unwrap(); + } + + std::fs::create_dir_all(&game_dir).unwrap(); + + install_game(&installer_exe, game_dir.as_path().to_str().unwrap()).ok().unwrap(); + + let mut xivlauncher_game_dir = env::temp_dir(); + xivlauncher_game_dir.push("game_test_xivlauncher"); + + if std::fs::read_dir(&xivlauncher_game_dir).ok().is_some() { + std::fs::remove_dir_all(&xivlauncher_game_dir).unwrap(); + } + std::fs::create_dir_all(&xivlauncher_game_dir).unwrap(); + + install_game(&installer_exe, xivlauncher_game_dir.as_path().to_str().unwrap()).ok().unwrap(); + + let xivlauncher_game_dir = xivlauncher_game_dir.as_path(); + let physis_game_dir = game_dir.as_path(); + + // TODO: only the first two patches are checked + let patches = ["game/D2017.07.11.0000.0001.patch", + "game/D2017.09.24.0000.0001.patch"]; + + println!("The game installation is now complete. Now testing game patching..."); + + // run it on xiv's patchinstaller first + // TODO: check for windows systems + for patch in patches { + let patch_path = format!("Z:\\{}\\{}", patch_dir, &patch); + let game_dir = format!("Z:\\{}\\game", xivlauncher_game_dir.to_str().unwrap()); + + let output = Command::new("/usr/bin/wine") + .args([&patcher_exe, + "install", + &patch_path, + &game_dir]) + .output() + .unwrap(); + + println!("{:#?}", output); + } + + // now run it on physis + for patch in patches { + let patch_path = format!("{}/{}", patch_dir, &patch); + let data_dir = format!("{}/{}", physis_game_dir.to_str().unwrap(), "game"); + + apply_patch(&data_dir, &patch_path).unwrap(); + } + + println!("Game patching is now complete. Proceeding to checksum matching..."); + + let mut xivlauncher_files : HashMap = HashMap::new(); + + println!("{:#?}", xivlauncher_game_dir); + + // TODO: consolidate into a closure or generic function + WalkDir::new(xivlauncher_game_dir) + .into_iter() + .filter_map(Result::ok) + .filter(|e| !e.file_type().is_dir()) + .for_each(|x| { + let file = std::fs::read(x.path()).unwrap(); + + let mut hash = hmac_sha512::Hash::new(); + hash.update(&file); + let sha = hash.finalize(); + + let mut rel_path = x.path(); + rel_path = rel_path.strip_prefix(xivlauncher_game_dir).unwrap(); + + xivlauncher_files.insert(rel_path.to_str().unwrap().to_string(), sha); + }); + + let mut physis_files : HashMap = HashMap::new(); + + WalkDir::new(physis_game_dir) + .into_iter() + .filter_map(Result::ok) + .filter(|e| !e.file_type().is_dir()) + .for_each(|x| { + if !x.file_type().is_dir() { + let file = std::fs::read(x.path()).unwrap(); + + let mut hash = hmac_sha512::Hash::new(); + hash.update(&file); + let sha = hash.finalize(); + + let mut rel_path = x.path(); + rel_path = rel_path.strip_prefix(physis_game_dir).unwrap(); + + physis_files.insert(rel_path.to_str().unwrap().to_string(), sha); + } + }); + + assert_eq!(physis_files, xivlauncher_files); +} \ No newline at end of file