use crate::config; use crate::file_helpers; use crate::system_helpers; use std::path::PathBuf; #[cfg(target_os = "linux")] use once_cell::sync::Lazy; #[cfg(target_os = "linux")] use std::sync::Arc; #[cfg(target_os = "linux")] use tokio::sync::Mutex; #[cfg(target_os = "linux")] static PATCH_STATE: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); #[cfg(target_os = "linux")] #[derive(Debug, Clone, Copy)] enum PatchState { NotExist, Same, BakNotExist, BakExist, } #[cfg(target_os = "linux")] use PatchState::*; #[cfg(target_os = "linux")] impl PatchState { fn to_wta(self) -> WhatToUnpach { let (mhyp_renamed, game_was_patched) = match self { NotExist => (false, true), Same => (false, true), BakNotExist => (true, true), BakExist => (false, false), }; WhatToUnpach { mhyp_renamed, game_was_patched, } } } #[cfg(target_os = "linux")] #[derive(Debug, Clone)] struct WhatToUnpach { mhyp_renamed: bool, game_was_patched: bool, } #[cfg(windows)] #[tauri::command] pub async fn patch_game() -> bool { let patch_path = PathBuf::from(system_helpers::install_location()).join("patch/version.dll"); // Are we already patched with mhypbase? If so, that's fine, just continue as normal let game_is_patched = file_helpers::are_files_identical( patch_path.clone().to_str().unwrap(), PathBuf::from(get_game_rsa_path().await.unwrap()) .join("mhypbase.dll") .to_str() .unwrap(), ); // Tell user they won't be unpatched with manual mhypbase patch if game_is_patched { println!( "You are already patched using mhypbase, so you will not be auto patched and unpatched!" ); return true; } // Copy the patch to game files let replaced = file_helpers::copy_file_with_new_name( patch_path.clone().to_str().unwrap().to_string(), get_game_rsa_path().await.unwrap(), String::from("version.dll"), ); if !replaced { return false; } true } #[cfg(target_os = "linux")] #[tauri::command] pub async fn patch_game() -> bool { let mut patch_state_mutex = PATCH_STATE.lock().await; if patch_state_mutex.is_some() { println!("Game already patched!"); } let patch_path = PathBuf::from(system_helpers::install_location()).join("patch/version.dll"); let game_mhyp = PathBuf::from(get_game_rsa_path().await.unwrap()).join("mhypbase.dll"); let game_mhyp_bak = PathBuf::from(get_game_rsa_path().await.unwrap()).join("mhypbase.dll.bak"); let patch_state = if !game_mhyp.exists() { NotExist } else if file_helpers::are_files_identical( patch_path.to_str().unwrap(), game_mhyp.to_str().unwrap(), ) { Same } else if !game_mhyp_bak.exists() { BakNotExist } else { BakExist }; match patch_state { NotExist => { // No renaming needed. // Copy version.dll as mhypbase.dll file_helpers::copy_file_with_new_name( patch_path.clone().to_str().unwrap().to_string(), get_game_rsa_path().await.unwrap(), String::from("mhypbase.dll"), ); } Same => { // No renaming needed. // No copying needed. println!("The game is already patched."); } BakNotExist => { // The current mhypbase.dll is most likely the original // Rename mhypbase.dll to mhypbase.dll.bak file_helpers::rename( game_mhyp.to_str().unwrap().to_string(), game_mhyp_bak .file_name() .unwrap() .to_str() .unwrap() .to_string(), ); // Copy version.dll as mhypbase.dll file_helpers::copy_file_with_new_name( patch_path.clone().to_str().unwrap().to_string(), get_game_rsa_path().await.unwrap(), String::from("mhypbase.dll"), ); } BakExist => { // Can't rename. mhypbase.dll.bak already exists. // Can't patch. mhypbase.dll exists. // This SHOULD NOT HAPPEN println!("The game directory contains a mhypbase.dll, but it's different from the patch."); println!("Make sure you have the original mhypbase.dll."); println!("Delete any other copy, and place the original copy in the game directory with the original name."); } } patch_state_mutex.replace(patch_state); patch_state.to_wta().game_was_patched } #[cfg(windows)] #[tauri::command] pub async fn unpatch_game() -> bool { // Just delete patch since it's not replacing any existing file let deleted = file_helpers::delete_file( PathBuf::from(get_game_rsa_path().await.unwrap()) .join("version.dll") .to_str() .unwrap() .to_string(), ); deleted } #[cfg(target_os = "linux")] #[tauri::command] pub async fn unpatch_game() -> bool { // TODO: Prevent the launcher from unpatching the game two times // This might be related to redirecting calls from the ts version of // unpatchGame to the rust version let mut patch_state_mutex = PATCH_STATE.lock().await; let patch_state = patch_state_mutex.take(); if patch_state.is_none() { println!("Game not patched!"); // NOTE: true is returned since otherwhise the launcher thinks unpatching failed // NOTE: actually it should be false since delete_file always returns false return false; } let patch_state = patch_state.unwrap(); let game_mhyp = PathBuf::from(get_game_rsa_path().await.unwrap()).join("mhypbase.dll"); let game_mhyp_bak = PathBuf::from(get_game_rsa_path().await.unwrap()).join("mhypbase.dll.bak"); let WhatToUnpach { mhyp_renamed, game_was_patched, } = patch_state.to_wta(); // If the current mhypbase.dll is the patch, then delete it. let deleted = if game_was_patched { file_helpers::delete_file(game_mhyp.to_str().unwrap().to_string()); true } else { false }; // If we renamed the original mhypbase.dll to mhypbase.dll.bak // rename mhypbase.dll.bak back to mhypbase.dll if mhyp_renamed { file_helpers::rename( game_mhyp_bak.to_str().unwrap().to_string(), game_mhyp.to_str().unwrap().to_string(), ) } // NOTE: As mentioned in a note above, false should be returned if the function succeded // and true if it failed !deleted } pub async fn get_game_rsa_path() -> Option { let config = config::get_config(); config.game_install_path.as_ref()?; let mut game_folder = PathBuf::from(config.game_install_path.unwrap()); game_folder.pop(); Some(format!("{}/", game_folder.to_str().unwrap()).replace('\\', "/")) }