diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..53e875e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "rust-analyzer.linkedProjects": [".\\src-tauri\\Cargo.toml"] +} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 3a89e50..dcd6b4c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -76,6 +76,16 @@ version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +[[package]] +name = "args" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b7432c65177b8d5c032d56e020dd8d407e939468479fc8c300e2d93e6d970b" +dependencies = [ + "getopts", + "log", +] + [[package]] name = "asn1-rs" version = "0.3.1" @@ -780,11 +790,13 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" name = "cultivation" version = "0.1.0" dependencies = [ + "args", "cc", "ctrlc", "duct", "file_diff", "futures-util", + "getopts", "http", "hudsucker", "is_elevated", @@ -1387,6 +1399,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -4624,6 +4645,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "unicode-xid" version = "0.2.3" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1832063..59f8012 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,6 +26,10 @@ sudo = "0.6.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.0.7", features = ["api-all"] } +# Arg parsing +args = "2.0" +getopts = "0.2" + # Access system process info. sysinfo = "0.24.6" diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs new file mode 100644 index 0000000..d51c440 --- /dev/null +++ b/src-tauri/src/config.rs @@ -0,0 +1,44 @@ +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::string::String; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Configuration { + pub toggle_grasscutter: bool, + pub game_install_path: String, + pub grasscutter_with_game: bool, + pub grasscutter_path: String, + pub java_path: String, + pub close_action: u64, + pub startup_launch: bool, + pub last_ip: String, + pub last_port: String, + pub language: String, + pub customBackground: String, + pub cert_generated: bool, + pub theme: String, + pub https_enabled: bool, + pub debug_enabled: bool, + pub patch_rsa: bool, + pub use_internal_proxy: bool, + pub wipe_login: bool, + pub horny_mode: bool, + pub auto_mongodb: bool, + pub un_elevated: bool, +} + +pub fn config_path() -> PathBuf { + let mut path = tauri::api::path::data_dir().unwrap(); + path.push("cultivation"); + path.push("configuration.json"); + + path +} + +pub fn get_config() -> Configuration { + let path = config_path(); + let config = std::fs::read_to_string(path).unwrap(); + let config: Configuration = serde_json::from_str(&config).unwrap(); + + config +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 3715e31..a4d22fc 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,8 +3,11 @@ windows_subsystem = "windows" )] +use args::{Args, ArgsError}; use file_helpers::dir_exists; + use once_cell::sync::Lazy; +use proxy::set_proxy_addr; use std::fs; use std::io::Write; use std::{collections::HashMap, sync::Mutex}; @@ -18,10 +21,12 @@ use sysinfo::{Pid, ProcessExt, System, SystemExt}; use crate::admin::reopen_as_admin; mod admin; +mod config; mod downloader; mod file_helpers; mod gamebanana; mod lang; +mod patch; mod proxy; mod release; mod system_helpers; @@ -36,24 +41,116 @@ fn try_flush() { std::io::stdout().flush().unwrap_or(()) } -fn has_arg(args: &[String], arg: &str) -> bool { - args.contains(&arg.to_string()) -} +async fn parse_args(inp: &Vec) -> Result { + let mut args = Args::new( + "Cultivation", + "Private server helper program for an Anime Game", + ); + args.flag("h", "help", "Print various CLI args"); + args.flag("p", "proxy", "Start the proxy server"); + args.flag("G", "launch-game", "Launch the game"); + args.flag( + "A", + "no-admin", + "Launch without requiring admin permissions", + ); + args.flag( + "g", + "no-gui", + "Run in CLI mode. Requires -A to be passed as well.", + ); + args.flag("s", "server", "Launch the configured GC server"); + args.flag( + "P", + "patch", + "Patch your game before launching, with whatever your game version needs", + ); + args.flag( + "N", + "non-elevated-game", + "Launch the game without admin permissions", + ); + args.option( + "H", + "host", + "Set host to connect to (eg. 'localhost:443' or 'my.awesomeserver.com:6969)", + "SERVER_HOST", + getopts::Occur::Optional, + None, + ); + args.option( + "a", + "game-args", + "Arguments to pass to the game process, if launching it", + r#""-opt-one -opt-two""#, + getopts::Occur::Optional, + None, + ); -async fn arg_handler(args: &[String]) { - if has_arg(args, "--proxy") { + args.parse(inp).unwrap(); + + let config = config::get_config(); + + if args.value_of("help")? { + println!("{}", args.full_usage()); + std::process::exit(0); + } + + if args.value_of("launch-game")? { + let game_path = config.game_install_path; + let game_args: String = args.value_of("game-args").unwrap_or(String::new()); + + // Patch if needed + if args.value_of("patch")? { + patch::patch_game().await; + } + + if args.value_of("non-elevated-game")? { + system_helpers::run_un_elevated(game_path.to_string(), Some(game_args)) + } else { + system_helpers::run_program(game_path.to_string(), Some(game_args)) + } + } + + if args.value_of("server")? { + let server_jar = config.grasscutter_path; + let mut server_path = server_jar.clone(); + // Strip jar name from path + if server_path.contains('/') { + // Can never panic because of if + let len = server_jar.rfind('/').unwrap(); + server_path.truncate(len); + } else if server_path.contains('\\') { + let len = server_jar.rfind('\\').unwrap(); + server_path.truncate(len); + } + let java_path = config.java_path; + + system_helpers::run_jar(server_jar, server_path.to_string(), java_path); + } + + if args.value_of::("host").is_ok() && !args.value_of::("host")?.is_empty() { + let host = args.value_of::("host")?; + set_proxy_addr(host); + } + + if args.value_of("proxy")? { + println!("Starting proxy server..."); let mut pathbuf = tauri::api::path::data_dir().unwrap(); pathbuf.push("cultivation"); pathbuf.push("ca"); connect(8035, pathbuf.to_str().unwrap().to_string()).await; } + + Ok(args) } -fn main() { +fn main() -> Result<(), ArgsError> { let args: Vec = std::env::args().collect(); + let parsed_args = block_on(parse_args(&args)).unwrap(); - if !is_elevated() && !has_arg(&args, "--no-admin") { + if !is_elevated() && !parsed_args.value_of("no-admin")? { println!("==============================================================================="); println!("You running as a non-elevated user. Some stuff will almost definitely not work."); println!("==============================================================================="); @@ -71,16 +168,15 @@ fn main() { exe_path.pop(); std::env::set_current_dir(&exe_path).unwrap(); - block_on(arg_handler(&args)); - // For disabled GUI ctrlc::set_handler(|| { disconnect(); + block_on(patch::unpatch_game()); std::process::exit(0); }) .unwrap_or(()); - if !has_arg(&args, "--no-gui") { + if !parsed_args.value_of("no-gui")? { tauri::Builder::default() .invoke_handler(tauri::generate_handler![ enable_process_watcher, @@ -142,6 +238,11 @@ fn main() { // Always disconnect upon closing the program disconnect(); + + // Always unpatch game upon closing the program + block_on(patch::unpatch_game()); + + Ok(()) } #[tauri::command] diff --git a/src-tauri/src/patch.rs b/src-tauri/src/patch.rs new file mode 100644 index 0000000..6399c5e --- /dev/null +++ b/src-tauri/src/patch.rs @@ -0,0 +1,59 @@ +use crate::config; +use crate::file_helpers; +use crate::system_helpers; +use std::path::PathBuf; + +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 +} + +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 +} + +pub async fn get_game_rsa_path() -> Option { + let config = config::get_config(); + let mut game_folder = PathBuf::from(config.game_install_path); + game_folder.pop(); + + Some(format!("{}/", game_folder.to_str().unwrap()).replace('\\', "/")) +} diff --git a/src-tauri/src/proxy.rs b/src-tauri/src/proxy.rs index 87283ee..3aa6666 100644 --- a/src-tauri/src/proxy.rs +++ b/src-tauri/src/proxy.rs @@ -47,6 +47,8 @@ pub fn set_proxy_addr(addr: String) { } else { *SERVER.lock().unwrap() = addr; } + + println!("Set server to {}", SERVER.lock().unwrap()); } #[async_trait] diff --git a/src-tauri/src/system_helpers.rs b/src-tauri/src/system_helpers.rs index 50eec24..ff8bddf 100644 --- a/src-tauri/src/system_helpers.rs +++ b/src-tauri/src/system_helpers.rs @@ -11,7 +11,13 @@ use registry::{Data, Hive, Security}; #[tauri::command] pub fn run_program(path: String, args: Option) { // Without unwrap_or, this can crash when UAC prompt is denied - open::that(format!("{} {}", &path, &args.unwrap_or_else(|| "".into()))).unwrap_or(()); + match open::with( + format!("{} {}", path, args.unwrap_or_else(|| "".into())), + path.clone(), + ) { + Ok(_) => (), + Err(e) => println!("Failed to open program ({}): {}", &path, e), + }; } #[tauri::command] @@ -67,6 +73,8 @@ pub fn run_jar(path: String, execute_in: String, java_path: String) { format!("\"{}\" -jar \"{}\"", java_path, path) }; + println!("Launching .jar with command: {}", &command); + // Open the program from the specified path. match open::with( format!("/k cd /D \"{}\" & {}", &execute_in, &command), @@ -78,12 +86,13 @@ pub fn run_jar(path: String, execute_in: String, java_path: String) { } #[tauri::command] -pub fn run_un_elevated(path: String) { +pub fn run_un_elevated(path: String, args: Option) { // Open the program non-elevated. match open::with( format!( - "cmd /min /C \"set __COMPAT_LAYER=RUNASINVOKER && start \"\" \"{}\"\"", - path + "cmd /min /C \"set __COMPAT_LAYER=RUNASINVOKER && start \"\" \"{}\"\" {}", + path, + args.unwrap_or_else(|| "".into()) ), "C:\\Windows\\System32\\cmd.exe", ) {