diff --git a/src-tauri/src/admin.rs b/src-tauri/src/admin.rs index 66daa49..203eb8a 100644 --- a/src-tauri/src/admin.rs +++ b/src-tauri/src/admin.rs @@ -1,5 +1,7 @@ #[cfg(windows)] pub fn reopen_as_admin() { + use std::process::{exit, Command}; + let install = std::env::current_exe().unwrap(); println!("Opening as admin: {}", install.to_str().unwrap()); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 17fc885..c2e0fe7 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -13,7 +13,7 @@ use tauri::api::path::data_dir; use tauri::async_runtime::block_on; use std::thread; -use sysinfo::{System, SystemExt}; +use sysinfo::{Pid, ProcessExt, System, SystemExt}; use crate::admin::reopen_as_admin; @@ -28,6 +28,8 @@ mod unzip; mod web; static WATCH_GAME_PROCESS: Lazy> = Lazy::new(|| Mutex::new(String::new())); +static WATCH_GRASSCUTTER_PROCESS: Lazy> = Lazy::new(|| Mutex::new(String::new())); +static GC_PID: std::sync::Mutex = Mutex::new(696969); fn try_flush() { std::io::stdout().flush().unwrap_or(()) @@ -81,10 +83,13 @@ fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![ enable_process_watcher, + enable_grasscutter_watcher, connect, disconnect, req_get, is_game_running, + is_grasscutter_running, + restart_grasscutter, get_theme_list, system_helpers::run_command, system_helpers::run_program, @@ -188,6 +193,104 @@ fn enable_process_watcher(window: tauri::Window, process: String) { }); } +#[tauri::command] +fn is_grasscutter_running() -> bool { + // Grab the grasscutter process name + let proc = WATCH_GRASSCUTTER_PROCESS.lock().unwrap().to_string(); + + !proc.is_empty() +} + +#[tauri::command] +fn restart_grasscutter(window: tauri::Window) -> bool { + let pid: usize = *GC_PID.lock().unwrap(); + let system = System::new_all(); + // Get the process + if let Some(process) = system.process(Pid::from(pid)) { + // Kill it + if process.kill() { + // Also kill the cmd it was open in + if let Some(parent) = system.process(Pid::from(process.parent().unwrap())) { + parent.kill(); + } + for process_gc in system.processes_by_name("java") { + if process_gc.cmd().last().unwrap().contains(&"grasscutter") { + process_gc.kill(); + } + } + window.emit("disable_grasscutter_watcher", &()).unwrap(); + thread::sleep(std::time::Duration::from_secs(2)); + // Start again + window.emit("start_grasscutter", &()).unwrap(); + true + } else { + false + } + } else { + false + } +} + +#[tauri::command] +fn enable_grasscutter_watcher(window: tauri::Window, process: String) { + let grasscutter_name = process.clone(); + let mut gc_pid = Pid::from(696969); + + *WATCH_GRASSCUTTER_PROCESS.lock().unwrap() = process; + + window.listen("disable_grasscutter_watcher", |_e| { + *WATCH_GRASSCUTTER_PROCESS.lock().unwrap() = "".to_string(); + }); + + println!("Starting grasscutter watcher..."); + + thread::spawn(move || { + // Initial sleep for 1 second while Grasscutter opens + std::thread::sleep(std::time::Duration::from_secs(3)); + + let mut system = System::new_all(); + + for process_gc in system.processes_by_name("java") { + if process_gc.cmd().last().unwrap().contains(&grasscutter_name) { + gc_pid = process_gc.pid(); + *GC_PID.lock().unwrap() = gc_pid.into(); + window + .emit("grasscutter_started", gc_pid.to_string()) + .unwrap(); + } + } + + loop { + // Shorten loop timer to avoid user closing Cultivation before automatic stuff + thread::sleep(std::time::Duration::from_secs(2)); + + // Refresh system info + system.refresh_all(); + + // Grab the grasscutter process name + let proc = WATCH_GRASSCUTTER_PROCESS.lock().unwrap().to_string(); + + if !proc.is_empty() { + let mut exists = true; + + if system.process(gc_pid).is_none() { + exists = false; + } + + // If the grasscutter process closes. + if !exists { + println!("Grasscutter closed"); + + *WATCH_GRASSCUTTER_PROCESS.lock().unwrap() = "".to_string(); + + window.emit("grasscutter_closed", &()).unwrap(); + break; + } + } + } + }); +} + #[tauri::command] async fn connect(port: u16, certificate_path: String) { // Log message to console. diff --git a/src-tauri/src/system_helpers.rs b/src-tauri/src/system_helpers.rs index 15cd450..dbebd8b 100644 --- a/src-tauri/src/system_helpers.rs +++ b/src-tauri/src/system_helpers.rs @@ -155,6 +155,7 @@ pub fn wipe_registry(exec_name: String) { } } +#[cfg(windows)] #[tauri::command] pub fn service_status(service: String) -> bool { let manager = match ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT) { @@ -179,6 +180,7 @@ pub fn service_status(service: String) -> bool { } } +#[cfg(windows)] #[tauri::command] pub fn start_service(service: String) -> bool { println!("Starting service: {}", service); @@ -197,6 +199,7 @@ pub fn start_service(service: String) -> bool { true } +#[cfg(windows)] #[tauri::command] pub fn stop_service(service: String) -> bool { println!("Stopping service: {}", service); diff --git a/src/ui/Main.tsx b/src/ui/Main.tsx index 2e5ecd4..37c50ce 100644 --- a/src/ui/Main.tsx +++ b/src/ui/Main.tsx @@ -68,7 +68,6 @@ export class Main extends React.Component { // Emitted for rsa replacing-purposes listen('game_closed', async () => { const wasPatched = await getConfigOption('patch_rsa') - const autoService = await getConfigOption('auto_mongodb') if (wasPatched) { const unpatched = await unpatchGame() @@ -77,6 +76,11 @@ export class Main extends React.Component { alert(`Could not unpatch game! (Delete version.dll in your game folder)`) } } + }) + + // Emitted for automatic processes + listen('grasscutter_closed', async () => { + const autoService = await getConfigOption('auto_mongodb') if (autoService) { await invoke('stop_service', { service: 'MongoDB' }) diff --git a/src/ui/components/ServerLaunchSection.tsx b/src/ui/components/ServerLaunchSection.tsx index baeb379..d5b5663 100644 --- a/src/ui/components/ServerLaunchSection.tsx +++ b/src/ui/components/ServerLaunchSection.tsx @@ -12,8 +12,9 @@ import Plus from '../../resources/icons/plus.svg' import './ServerLaunchSection.css' import { dataDir } from '@tauri-apps/api/path' -import { getGameExecutable, getGameVersion } from '../../utils/game' +import { getGameExecutable, getGameVersion, getGrasscutterJar } from '../../utils/game' import { patchGame, unpatchGame } from '../../utils/rsa' +import { listen } from '@tauri-apps/api/event' interface IProps { openExtras: (playGame: () => void) => void @@ -25,6 +26,7 @@ interface IState { checkboxLabel: string ip: string port: string + launchServer: (proc_name?: string) => void ipPlaceholder: string portPlaceholder: string @@ -54,6 +56,9 @@ export default class ServerLaunchSection extends React.Component portHelpText: '', httpsLabel: '', httpsEnabled: false, + launchServer: () => { + alert('Error launching grasscutter') + }, swag: false, akebiSet: false, migotoSet: false, @@ -64,6 +69,11 @@ export default class ServerLaunchSection extends React.Component this.setIp = this.setIp.bind(this) this.setPort = this.setPort.bind(this) this.toggleHttps = this.toggleHttps.bind(this) + this.launchServer = this.launchServer.bind(this) + + listen('start_grasscutter', async () => { + this.launchServer() + }) } async componentDidMount() { @@ -182,12 +192,20 @@ export default class ServerLaunchSection extends React.Component else alert('Game not found! At: ' + (exe || config.game_install_path)) } - async launchServer() { + async launchServer(proc_name?: string) { + if (await invoke('is_grasscutter_running')) { + alert('Grasscutter already running!') + return + } const config = await getConfig() if (!config.grasscutter_path) return alert('Grasscutter not installed or set!') if (config.auto_mongodb) { + const grasscutter_jar = await getGrasscutterJar() + await invoke('enable_grasscutter_watcher', { + process: proc_name || grasscutter_jar, + }) // Check if MongoDB is running and start it if not await invoke('service_status', { service: 'MongoDB' }) } diff --git a/src/ui/components/menu/Downloads.tsx b/src/ui/components/menu/Downloads.tsx index d8ab0b3..b85f38d 100644 --- a/src/ui/components/menu/Downloads.tsx +++ b/src/ui/components/menu/Downloads.tsx @@ -13,8 +13,7 @@ import { invoke } from '@tauri-apps/api' import { listen } from '@tauri-apps/api/event' import HelpButton from '../common/HelpButton' -const FULL_BUILD_DOWNLOAD = - 'https://cdn.discordapp.com/attachments/615655311960965130/1091457240373919814/GrasscutterCulti3.5.zip' +const FULL_BUILD_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterCulti.zip' // Change to link that can be updated without modifying here const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip' const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip' const STABLE_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/stable/Grasscutter.zip' diff --git a/src/ui/components/menu/Options.tsx b/src/ui/components/menu/Options.tsx index 04964f6..1d0a9b3 100644 --- a/src/ui/components/menu/Options.tsx +++ b/src/ui/components/menu/Options.tsx @@ -267,7 +267,11 @@ export default class Options extends React.Component { ), }) - alert('Restart Grasscutter to apply encryption settings! If it is already closed, just start as normal.') + // Check if Grasscutter is running, and restart if so to apply changes + if (await invoke('is_grasscutter_running')) { + alert('Automatically restarting Grasscutter to apply encryption changes!') + await invoke('restart_grasscutter') + } } async removeRSA() { diff --git a/src/utils/game.ts b/src/utils/game.ts index 748c034..9d933ee 100644 --- a/src/utils/game.ts +++ b/src/utils/game.ts @@ -12,6 +12,17 @@ export async function getGameExecutable() { return pathArr[pathArr.length - 1] } +export async function getGrasscutterJar() { + const config = await getConfig() + + if (!config.grasscutter_path) { + return null + } + + const pathArr = config.grasscutter_path.replace(/\\/g, '/').split('/') + return pathArr[pathArr.length - 1] +} + export async function getGameFolder() { const config = await getConfig()