mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-14 16:14:48 +01:00
Automation
- MongoDB tied to Grasscutter rather than game - Grasscutter restarts when changing encryption if it is running - AIO link updates without needing manual edits
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn reopen_as_admin() {
|
pub fn reopen_as_admin() {
|
||||||
|
use std::process::{exit, Command};
|
||||||
|
|
||||||
let install = std::env::current_exe().unwrap();
|
let install = std::env::current_exe().unwrap();
|
||||||
|
|
||||||
println!("Opening as admin: {}", install.to_str().unwrap());
|
println!("Opening as admin: {}", install.to_str().unwrap());
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use tauri::api::path::data_dir;
|
|||||||
use tauri::async_runtime::block_on;
|
use tauri::async_runtime::block_on;
|
||||||
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use sysinfo::{System, SystemExt};
|
use sysinfo::{Pid, ProcessExt, System, SystemExt};
|
||||||
|
|
||||||
use crate::admin::reopen_as_admin;
|
use crate::admin::reopen_as_admin;
|
||||||
|
|
||||||
@@ -28,6 +28,8 @@ mod unzip;
|
|||||||
mod web;
|
mod web;
|
||||||
|
|
||||||
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||||
|
static WATCH_GRASSCUTTER_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||||
|
static GC_PID: std::sync::Mutex<usize> = Mutex::new(696969);
|
||||||
|
|
||||||
fn try_flush() {
|
fn try_flush() {
|
||||||
std::io::stdout().flush().unwrap_or(())
|
std::io::stdout().flush().unwrap_or(())
|
||||||
@@ -81,10 +83,13 @@ fn main() {
|
|||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
enable_process_watcher,
|
enable_process_watcher,
|
||||||
|
enable_grasscutter_watcher,
|
||||||
connect,
|
connect,
|
||||||
disconnect,
|
disconnect,
|
||||||
req_get,
|
req_get,
|
||||||
is_game_running,
|
is_game_running,
|
||||||
|
is_grasscutter_running,
|
||||||
|
restart_grasscutter,
|
||||||
get_theme_list,
|
get_theme_list,
|
||||||
system_helpers::run_command,
|
system_helpers::run_command,
|
||||||
system_helpers::run_program,
|
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]
|
#[tauri::command]
|
||||||
async fn connect(port: u16, certificate_path: String) {
|
async fn connect(port: u16, certificate_path: String) {
|
||||||
// Log message to console.
|
// Log message to console.
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ pub fn wipe_registry(exec_name: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn service_status(service: String) -> bool {
|
pub fn service_status(service: String) -> bool {
|
||||||
let manager = match ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT) {
|
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]
|
#[tauri::command]
|
||||||
pub fn start_service(service: String) -> bool {
|
pub fn start_service(service: String) -> bool {
|
||||||
println!("Starting service: {}", service);
|
println!("Starting service: {}", service);
|
||||||
@@ -197,6 +199,7 @@ pub fn start_service(service: String) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn stop_service(service: String) -> bool {
|
pub fn stop_service(service: String) -> bool {
|
||||||
println!("Stopping service: {}", service);
|
println!("Stopping service: {}", service);
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ export class Main extends React.Component<IProps, IState> {
|
|||||||
// Emitted for rsa replacing-purposes
|
// Emitted for rsa replacing-purposes
|
||||||
listen('game_closed', async () => {
|
listen('game_closed', async () => {
|
||||||
const wasPatched = await getConfigOption('patch_rsa')
|
const wasPatched = await getConfigOption('patch_rsa')
|
||||||
const autoService = await getConfigOption('auto_mongodb')
|
|
||||||
|
|
||||||
if (wasPatched) {
|
if (wasPatched) {
|
||||||
const unpatched = await unpatchGame()
|
const unpatched = await unpatchGame()
|
||||||
@@ -77,6 +76,11 @@ export class Main extends React.Component<IProps, IState> {
|
|||||||
alert(`Could not unpatch game! (Delete version.dll in your game folder)`)
|
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) {
|
if (autoService) {
|
||||||
await invoke('stop_service', { service: 'MongoDB' })
|
await invoke('stop_service', { service: 'MongoDB' })
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import Plus from '../../resources/icons/plus.svg'
|
|||||||
|
|
||||||
import './ServerLaunchSection.css'
|
import './ServerLaunchSection.css'
|
||||||
import { dataDir } from '@tauri-apps/api/path'
|
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 { patchGame, unpatchGame } from '../../utils/rsa'
|
||||||
|
import { listen } from '@tauri-apps/api/event'
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
openExtras: (playGame: () => void) => void
|
openExtras: (playGame: () => void) => void
|
||||||
@@ -25,6 +26,7 @@ interface IState {
|
|||||||
checkboxLabel: string
|
checkboxLabel: string
|
||||||
ip: string
|
ip: string
|
||||||
port: string
|
port: string
|
||||||
|
launchServer: (proc_name?: string) => void
|
||||||
|
|
||||||
ipPlaceholder: string
|
ipPlaceholder: string
|
||||||
portPlaceholder: string
|
portPlaceholder: string
|
||||||
@@ -54,6 +56,9 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
portHelpText: '',
|
portHelpText: '',
|
||||||
httpsLabel: '',
|
httpsLabel: '',
|
||||||
httpsEnabled: false,
|
httpsEnabled: false,
|
||||||
|
launchServer: () => {
|
||||||
|
alert('Error launching grasscutter')
|
||||||
|
},
|
||||||
swag: false,
|
swag: false,
|
||||||
akebiSet: false,
|
akebiSet: false,
|
||||||
migotoSet: false,
|
migotoSet: false,
|
||||||
@@ -64,6 +69,11 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
this.setIp = this.setIp.bind(this)
|
this.setIp = this.setIp.bind(this)
|
||||||
this.setPort = this.setPort.bind(this)
|
this.setPort = this.setPort.bind(this)
|
||||||
this.toggleHttps = this.toggleHttps.bind(this)
|
this.toggleHttps = this.toggleHttps.bind(this)
|
||||||
|
this.launchServer = this.launchServer.bind(this)
|
||||||
|
|
||||||
|
listen('start_grasscutter', async () => {
|
||||||
|
this.launchServer()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
async componentDidMount() {
|
||||||
@@ -182,12 +192,20 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
else alert('Game not found! At: ' + (exe || config.game_install_path))
|
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()
|
const config = await getConfig()
|
||||||
|
|
||||||
if (!config.grasscutter_path) return alert('Grasscutter not installed or set!')
|
if (!config.grasscutter_path) return alert('Grasscutter not installed or set!')
|
||||||
|
|
||||||
if (config.auto_mongodb) {
|
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
|
// Check if MongoDB is running and start it if not
|
||||||
await invoke('service_status', { service: 'MongoDB' })
|
await invoke('service_status', { service: 'MongoDB' })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ import { invoke } from '@tauri-apps/api'
|
|||||||
import { listen } from '@tauri-apps/api/event'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
import HelpButton from '../common/HelpButton'
|
import HelpButton from '../common/HelpButton'
|
||||||
|
|
||||||
const FULL_BUILD_DOWNLOAD =
|
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
|
||||||
'https://cdn.discordapp.com/attachments/615655311960965130/1091457240373919814/GrasscutterCulti3.5.zip'
|
|
||||||
const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip'
|
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 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'
|
const STABLE_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/stable/Grasscutter.zip'
|
||||||
|
|||||||
@@ -267,7 +267,11 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
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() {
|
async removeRSA() {
|
||||||
|
|||||||
@@ -12,6 +12,17 @@ export async function getGameExecutable() {
|
|||||||
return pathArr[pathArr.length - 1]
|
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() {
|
export async function getGameFolder() {
|
||||||
const config = await getConfig()
|
const config = await getConfig()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user