diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 0af1361..116a144 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -222,6 +222,8 @@ fn main() -> Result<(), ArgsError> { system_helpers::wipe_registry, system_helpers::get_platform, system_helpers::run_un_elevated, + patch::patch_game, + patch::unpatch_game, proxy::set_proxy_addr, proxy::generate_ca_files, proxy::set_redirect_more, diff --git a/src-tauri/src/patch.rs b/src-tauri/src/patch.rs index 66c7bcc..13a38a4 100644 --- a/src-tauri/src/patch.rs +++ b/src-tauri/src/patch.rs @@ -3,6 +3,53 @@ 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"); @@ -37,6 +84,82 @@ pub async fn patch_game() -> bool { 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( @@ -50,6 +173,51 @@ pub async fn unpatch_game() -> bool { 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(); diff --git a/src/utils/rsa.ts b/src/utils/rsa.ts index 0a381be..6573310 100644 --- a/src/utils/rsa.ts +++ b/src/utils/rsa.ts @@ -1,50 +1,10 @@ import { invoke } from '@tauri-apps/api' -import { getGameFolder } from './game' // Patch file from: https://github.com/34736384/RSAPatch/ export async function patchGame() { - const patchPath = (await invoke('install_location')) + '/patch/version.dll' - // Are we already patched with mhypbase? If so, that's fine, just continue as normal - const gameIsPatched = await invoke('are_files_identical', { - path1: patchPath, - path2: (await getGameRSAPath()) + '/mhypbase.dll', - }) - - // Tell user they won't be unpatched with manual mhypbase patch - if (gameIsPatched) { - console.log('You are already patched using mhypbase, so you will not be auto patched and unpatched!') - return true - } - - // Copy the patch to game files - const replaced = await invoke('copy_file_with_new_name', { - path: patchPath, - newPath: await getGameRSAPath(), - newName: 'version.dll', - }) - - if (!replaced) { - return false - } - - return true + return invoke('patch_game') } export async function unpatchGame() { - // Just delete patch since it's not replacing any existing file - const deleted = await invoke('delete_file', { - path: (await getGameRSAPath()) + '/version.dll', - }) - - return deleted -} - -export async function getGameRSAPath() { - const gameData = await getGameFolder() - - if (!gameData) { - return null - } - - return (gameData + '\\').replace(/\\/g, '/') + return invoke('unpatch_game') }