From d17f860f0722078aa7564ba7ce0fc8823ca848cd Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Tue, 25 Oct 2022 11:03:05 -0400 Subject: [PATCH] Allow naive repairing of version files Currently, we not handle repairing version files well, so I sought to seek a solution to the problem. This is one of part of the puzzle, by implementing a simple "game repair". This is not IndexedZiPatch support, but simply checking if we can restore version files if possible, or otherwise wipe the directory. --- src/common.rs | 7 ++++ src/gamedata.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++- src/repository.rs | 13 ++---- 3 files changed, 109 insertions(+), 11 deletions(-) diff --git a/src/common.rs b/src/common.rs index cc04f6d..70dc7a4 100755 --- a/src/common.rs +++ b/src/common.rs @@ -1,3 +1,5 @@ +use std::fs; +use std::path::Path; use binrw::binrw; #[binrw] @@ -43,3 +45,8 @@ pub fn get_language_code(lang: &Language) -> &'static str { pub enum Region { Global = -1, // TODO: find patch codes for other regions :-) } + +/// Reads a version file. +pub fn read_version(p: &Path) -> Option { + fs::read_to_string(p).ok() +} diff --git a/src/gamedata.rs b/src/gamedata.rs index 7197d13..0f49283 100755 --- a/src/gamedata.rs +++ b/src/gamedata.rs @@ -1,4 +1,4 @@ -use crate::common::Language; +use crate::common::{Language, read_version}; use crate::dat::DatFile; use crate::exd::EXD; use crate::exh::EXH; @@ -31,6 +31,16 @@ fn is_valid(path: &str) -> bool { true } +#[derive(Debug)] +pub enum RepairAction { + VersionFileMissing, + VersionFileCanRestore +} + +pub enum RepairError<'a> { + FailedRepair(&'a Repository) +} + pub type MemoryBuffer = Vec; impl GameData { @@ -229,6 +239,94 @@ impl GameData { pub fn apply_patch(&self, patch_path: &str) -> Result<(), PatchError> { apply_patch(&self.game_directory, patch_path) } + + /// Detects whether or not the game files need a repair, right now it only checks for invalid + /// version files. + /// If the repair is needed, a list of invalid repositories is given. + pub fn needs_repair(&self) -> Option> { + let mut repositories : Vec<(&Repository, RepairAction)> = Vec::new(); + for repository in &self.repositories { + if repository.version.is_none() { + // Check to see if a .bck file is created, as we might be able to use that + let ver_bak_path: PathBuf = [ + self.game_directory.clone(), + "sqpack".to_string(), + repository.name.clone(), + format!("{}.bck", repository.name), + ] + .iter() + .collect(); + + let repair_action = if read_version(&ver_bak_path).is_some() { + RepairAction::VersionFileCanRestore + } else { + RepairAction::VersionFileMissing + }; + + repositories.push((repository, repair_action)); + } + } + + if repositories.is_empty() { + None + } else { + Some(repositories) + } + } + + /// Performs the repair, assuming any damaging effects it may have + /// Returns true only if all actions were taken are successful. + /// NOTE: This is a destructive operation, especially for InvalidVersion errors. + pub fn perform_repair<'a>(&self, repositories: &Vec<(&'a Repository, RepairAction)>) -> Result<(), RepairError<'a>> { + for (repository, action) in repositories { + let ver_path: PathBuf = [ + self.game_directory.clone(), + "sqpack".to_string(), + repository.name.clone(), + format!("{}.ver", repository.name), + ] + .iter() + .collect(); + + let new_version : String = match action { + RepairAction::VersionFileMissing => { + let repo_path: PathBuf = [ + self.game_directory.clone(), + "sqpack".to_string(), + repository.name.clone() + ] + .iter() + .collect(); + + std::fs::remove_dir_all(&repo_path).ok() + .ok_or(RepairError::FailedRepair(repository))?; + + std::fs::create_dir_all(&repo_path).ok() + .ok_or(RepairError::FailedRepair(repository))?; + + "2012.01.01.0000.0000".to_string() // TODO: is this correct for expansions? + } + RepairAction::VersionFileCanRestore => { + let ver_bak_path: PathBuf = [ + self.game_directory.clone(), + "sqpack".to_string(), + repository.name.clone(), + format!("{}.bck", repository.name), + ] + .iter() + .collect(); + + read_version(&ver_bak_path) + .ok_or(RepairError::FailedRepair(repository))? + } + }; + + std::fs::write(&ver_path, new_version).ok() + .ok_or(RepairError::FailedRepair(repository))?; + } + + Ok(()) + } } #[cfg(test)] diff --git a/src/repository.rs b/src/repository.rs index 5bb229e..cc6b6eb 100755 --- a/src/repository.rs +++ b/src/repository.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use std::cmp::Ordering::{Greater, Less}; use std::fs; use std::path::{Path, PathBuf}; +use crate::common::read_version; /// The type of repository, discerning game data from expansion data. #[derive(Debug, PartialEq, Eq, Copy, Clone)] @@ -26,7 +27,7 @@ pub struct Repository { /// The type of repository, such as "base game" or "expansion". pub repo_type: RepositoryType, /// The version of the game data. - pub version: String, + pub version: Option, } impl Eq for Repository {} @@ -59,10 +60,6 @@ impl PartialOrd for Repository { } } -fn read_version(p: &Path) -> Option { - fs::read_to_string(p).ok() -} - /// This refers to the specific root directory a file is located in. /// This is a fixed list of directories, and all of them are known. #[derive(Debug, PartialEq, Eq)] @@ -155,14 +152,10 @@ impl Repository { read_version(d.as_path()) }; - if version == None { - return None; - } - Some(Repository { name, repo_type, - version: version.unwrap(), + version, }) }