mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2026-03-22 07:42:24 +01:00
552
src-tauri/Cargo.lock
generated
552
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,12 @@ registry = "1.2.1"
|
|||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
sudo = "0.6.0"
|
sudo = "0.6.0"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
anyhow = "1.0.58"
|
||||||
|
os_type = "2.6"
|
||||||
|
term-detect = "0.1.7"
|
||||||
|
which = "4.4"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tauri = { version = "1.0.9", features = ["api-all"] }
|
tauri = { version = "1.0.9", features = ["api-all"] }
|
||||||
@@ -53,7 +59,7 @@ serde_json = "1"
|
|||||||
|
|
||||||
# Dependencies for the HTTP(S) proxy.
|
# Dependencies for the HTTP(S) proxy.
|
||||||
http = "0.2"
|
http = "0.2"
|
||||||
hudsucker = "0.19.1"
|
hudsucker = "0.19.2"
|
||||||
tracing = "0.1.21"
|
tracing = "0.1.21"
|
||||||
tokio-rustls = "0.23.0"
|
tokio-rustls = "0.23.0"
|
||||||
tokio-tungstenite = "0.17.0"
|
tokio-tungstenite = "0.17.0"
|
||||||
@@ -71,6 +77,12 @@ file_diff = "1.0.0"
|
|||||||
rust-ini = "0.18.0"
|
rust-ini = "0.18.0"
|
||||||
ctrlc = "3.2.3"
|
ctrlc = "3.2.3"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies.anime-launcher-sdk]
|
||||||
|
git = "https://github.com/an-anime-team/anime-launcher-sdk.git"
|
||||||
|
tag = "1.11.1"
|
||||||
|
default-features = false
|
||||||
|
features = ["all", "genshin"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
"horny_mode": "Horny Mode",
|
"horny_mode": "Horny Mode",
|
||||||
"auto_mongodb": "Automatically Start MongoDB",
|
"auto_mongodb": "Automatically Start MongoDB",
|
||||||
"un_elevated": "Run the game non-elevated (no admin)",
|
"un_elevated": "Run the game non-elevated (no admin)",
|
||||||
"redirect_more": "Also redirect other MHY games"
|
"redirect_more": "Also redirect other MHY games",
|
||||||
|
"check_aagl": "For more options, check the other launcher",
|
||||||
|
"grasscutter_elevation": "Method of running GC on restricted ports"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"grasscutter_fullbuild": "Download Grasscutter All-in-One",
|
"grasscutter_fullbuild": "Download Grasscutter All-in-One",
|
||||||
@@ -85,7 +87,8 @@
|
|||||||
"use_proxy": "Use the Cultivation internal proxy. You should have this enabled unless you use something like Fiddler",
|
"use_proxy": "Use the Cultivation internal proxy. You should have this enabled unless you use something like Fiddler",
|
||||||
"patch_rsa": "Patch and unpatch your game RSA automatically. Unless playing with old/non-official versions (3.0 and older), this should be enabled.",
|
"patch_rsa": "Patch and unpatch your game RSA automatically. Unless playing with old/non-official versions (3.0 and older), this should be enabled.",
|
||||||
"add_delay": "Set delay in 3dmigoto loader! \nThis should fix loading issues, but will add a small delay to when 3dmigoto is loaded upon launching the game. \nYou can now launch with 3dmigoto again.",
|
"add_delay": "Set delay in 3dmigoto loader! \nThis should fix loading issues, but will add a small delay to when 3dmigoto is loaded upon launching the game. \nYou can now launch with 3dmigoto again.",
|
||||||
"migoto": "For importing models from GameBanana"
|
"migoto": "For importing models from GameBanana",
|
||||||
|
"grasscutter_elevation_help_text": "The method used to allow Grasscutter to bind port 443 (which is not allowed for regular users on Linux)\nAvailable methods:\n Capability - grant the Java Virtual Machine the capability to bind ports below 1024. This also allows all other programs running on that JVM to bind these ports.\n Root - run GC as root. This also allows the GC server, its plugins and the JVM to do pretty much anything, including sending your nudes to the NSA, CIA, and the alphabet boys.\n None - for no method. This requires you to change the GC Dispatch port."
|
||||||
},
|
},
|
||||||
"swag": {
|
"swag": {
|
||||||
"akebi_name": "Akebi",
|
"akebi_name": "Akebi",
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
"horny_mode": "Tryb 34",
|
"horny_mode": "Tryb 34",
|
||||||
"auto_mongodb": "Automatycznie uruchamiaj MongoDB",
|
"auto_mongodb": "Automatycznie uruchamiaj MongoDB",
|
||||||
"un_elevated": "Uruchamiaj grę bez uprawnień administratora/roota",
|
"un_elevated": "Uruchamiaj grę bez uprawnień administratora/roota",
|
||||||
"redirect_more": "Przekieruj też inne gry MHY"
|
"redirect_more": "Przekieruj też inne gry MHY",
|
||||||
|
"check_aagl": "Więcej opcji znajdziesz w drugim launcherze",
|
||||||
|
"grasscutter_elevation": "Sposób uruchomienia GC na ograniczonym porcie"
|
||||||
},
|
},
|
||||||
"downloads": {
|
"downloads": {
|
||||||
"grasscutter_fullbuild": "Pobierz Grasscutter (wszystko w jednym)",
|
"grasscutter_fullbuild": "Pobierz Grasscutter (wszystko w jednym)",
|
||||||
@@ -85,7 +87,8 @@
|
|||||||
"use_proxy": "Używaj wewnętrznego proxy Cultivation. To powinno być włączone, chyba że używasz czegoś jak np. Fiddler",
|
"use_proxy": "Używaj wewnętrznego proxy Cultivation. To powinno być włączone, chyba że używasz czegoś jak np. Fiddler",
|
||||||
"patch_rsa": "Patchuj i odpatchuj RSA gry automatycznie. Jeżeli nie grasz w starą lub nieoficjalną wersję (3.0 lub starszą), to powinno być włączone.",
|
"patch_rsa": "Patchuj i odpatchuj RSA gry automatycznie. Jeżeli nie grasz w starą lub nieoficjalną wersję (3.0 lub starszą), to powinno być włączone.",
|
||||||
"add_delay": "Ustaw opóźnienie 3dmigoto loadera! \nTo powinno naprawić problemy z ładowaniem, ale doda małe opóźnienie do czasu ładowania 3dmigoto do gry. \nTeraz możecie uruchamiać grę z 3dmigoto.",
|
"add_delay": "Ustaw opóźnienie 3dmigoto loadera! \nTo powinno naprawić problemy z ładowaniem, ale doda małe opóźnienie do czasu ładowania 3dmigoto do gry. \nTeraz możecie uruchamiać grę z 3dmigoto.",
|
||||||
"migoto": "Do importowania modeli z GameBanana"
|
"migoto": "Do importowania modeli z GameBanana",
|
||||||
|
"grasscutter_elevation_help_text": "Metoda używana przez Grasscuttera do zbindowania portu 443 (co nie jest dozwolone dla zywkłych użytkowników w Linuxie)\nDostępne metody:\n Capability - daje wirtualnej maszynie Javy możliwość zbindowania portów poniżej 1024. To też pozwala wszystkim innym programom odpalonym na tej maszynie JVM zbindować te porty.\n Root - uruchamia GC jako root. To pozwala serwerowi GC, jego pluginom i maszynie JVM zrobić praktycznie wszystko, wliczając w to wysyłanie twoich nudesów do ABW, CBŚ, i innych trzyliterowych służb.\n None - czyli żadna metoda. Ta opcja wymaga zmiana portu Dispatch serwera GC."
|
||||||
},
|
},
|
||||||
"swag": {
|
"swag": {
|
||||||
"akebi_name": "Akebi",
|
"akebi_name": "Akebi",
|
||||||
|
|||||||
@@ -18,8 +18,5 @@ pub fn reopen_as_admin() {
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub fn reopen_as_admin() {}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn reopen_as_admin() {}
|
pub fn reopen_as_admin() {}
|
||||||
|
|||||||
@@ -11,14 +11,22 @@ use proxy::set_proxy_addr;
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::{collections::HashMap, sync::Mutex};
|
use std::{collections::HashMap, sync::Mutex};
|
||||||
use system_helpers::is_elevated;
|
|
||||||
use tauri::api::path::data_dir;
|
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::{Pid, ProcessExt, System, SystemExt};
|
use sysinfo::{Pid, ProcessExt, System, SystemExt};
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
use crate::admin::reopen_as_admin;
|
use crate::admin::reopen_as_admin;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use system_helpers::is_elevated;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use std::{
|
||||||
|
thread::{sleep, JoinHandle},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
mod admin;
|
mod admin;
|
||||||
mod config;
|
mod config;
|
||||||
@@ -37,6 +45,9 @@ static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String:
|
|||||||
static WATCH_GRASSCUTTER_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);
|
static GC_PID: std::sync::Mutex<usize> = Mutex::new(696969);
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub static AAGL_THREAD: Lazy<Mutex<Option<JoinHandle<()>>>> = Lazy::new(|| Mutex::new(None));
|
||||||
|
|
||||||
fn try_flush() {
|
fn try_flush() {
|
||||||
std::io::stdout().flush().unwrap_or(())
|
std::io::stdout().flush().unwrap_or(())
|
||||||
}
|
}
|
||||||
@@ -157,6 +168,7 @@ fn main() -> Result<(), ArgsError> {
|
|||||||
let args: Vec<String> = std::env::args().collect();
|
let args: Vec<String> = std::env::args().collect();
|
||||||
let parsed_args = block_on(parse_args(&args)).unwrap();
|
let parsed_args = block_on(parse_args(&args)).unwrap();
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
if !is_elevated() && !parsed_args.value_of("no-admin")? {
|
if !is_elevated() && !parsed_args.value_of("no-admin")? {
|
||||||
println!("===============================================================================");
|
println!("===============================================================================");
|
||||||
println!("You running as a non-elevated user. Some stuff will almost definitely not work.");
|
println!("You running as a non-elevated user. Some stuff will almost definitely not work.");
|
||||||
@@ -202,6 +214,7 @@ fn main() -> Result<(), ArgsError> {
|
|||||||
system_helpers::service_status,
|
system_helpers::service_status,
|
||||||
system_helpers::stop_service,
|
system_helpers::stop_service,
|
||||||
system_helpers::run_jar,
|
system_helpers::run_jar,
|
||||||
|
system_helpers::run_jar_root,
|
||||||
system_helpers::open_in_browser,
|
system_helpers::open_in_browser,
|
||||||
system_helpers::install_location,
|
system_helpers::install_location,
|
||||||
system_helpers::is_elevated,
|
system_helpers::is_elevated,
|
||||||
@@ -210,6 +223,10 @@ fn main() -> Result<(), ArgsError> {
|
|||||||
system_helpers::wipe_registry,
|
system_helpers::wipe_registry,
|
||||||
system_helpers::get_platform,
|
system_helpers::get_platform,
|
||||||
system_helpers::run_un_elevated,
|
system_helpers::run_un_elevated,
|
||||||
|
system_helpers::jvm_add_cap,
|
||||||
|
system_helpers::jvm_remove_cap,
|
||||||
|
patch::patch_game,
|
||||||
|
patch::unpatch_game,
|
||||||
proxy::set_proxy_addr,
|
proxy::set_proxy_addr,
|
||||||
proxy::generate_ca_files,
|
proxy::generate_ca_files,
|
||||||
proxy::set_redirect_more,
|
proxy::set_redirect_more,
|
||||||
@@ -267,6 +284,7 @@ fn is_game_running() -> bool {
|
|||||||
!proc.is_empty()
|
!proc.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn enable_process_watcher(window: tauri::Window, process: String) {
|
fn enable_process_watcher(window: tauri::Window, process: String) {
|
||||||
*WATCH_GAME_PROCESS.lock().unwrap() = process;
|
*WATCH_GAME_PROCESS.lock().unwrap() = process;
|
||||||
@@ -312,6 +330,41 @@ fn enable_process_watcher(window: tauri::Window, process: String) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The library takes care of it
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[tauri::command]
|
||||||
|
fn enable_process_watcher(window: tauri::Window, process: String) {
|
||||||
|
drop(process);
|
||||||
|
thread::spawn(move || {
|
||||||
|
let end_time = Instant::now() + Duration::from_secs(60);
|
||||||
|
let game_thread = loop {
|
||||||
|
let mut lock = AAGL_THREAD.lock().unwrap();
|
||||||
|
if lock.is_some() {
|
||||||
|
break lock.take().unwrap();
|
||||||
|
}
|
||||||
|
drop(lock);
|
||||||
|
if end_time < Instant::now() {
|
||||||
|
// If more than 60 seconds pass something has gone wrong
|
||||||
|
println!("Waiting for game thread timed out");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Otherwhise wait in order to not use too many CPU cycles
|
||||||
|
sleep(Duration::from_millis(128));
|
||||||
|
};
|
||||||
|
game_thread.join().unwrap();
|
||||||
|
println!("Game closed");
|
||||||
|
|
||||||
|
*WATCH_GAME_PROCESS.lock().unwrap() = "".to_string();
|
||||||
|
disconnect();
|
||||||
|
|
||||||
|
window.emit("game_closed", &()).unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[tauri::command]
|
||||||
|
fn enable_process_watcher(window: tauri::Window, process: String) {}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn is_grasscutter_running() -> bool {
|
fn is_grasscutter_running() -> bool {
|
||||||
// Grab the grasscutter process name
|
// Grab the grasscutter process name
|
||||||
@@ -361,7 +414,6 @@ fn restart_grasscutter(_window: tauri::Window) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn enable_grasscutter_watcher(window: tauri::Window, process: String) {
|
fn enable_grasscutter_watcher(window: tauri::Window, process: String) {
|
||||||
let grasscutter_name = process.clone();
|
let grasscutter_name = process.clone();
|
||||||
@@ -422,13 +474,6 @@ fn enable_grasscutter_watcher(window: tauri::Window, process: String) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
#[tauri::command]
|
|
||||||
fn enable_grasscutter_watcher(_window: tauri::Window, _process: String) {
|
|
||||||
let gc_pid = Pid::from(696969);
|
|
||||||
*GC_PID.lock().unwrap() = gc_pid.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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.
|
||||||
|
|||||||
@@ -3,6 +3,53 @@ use crate::file_helpers;
|
|||||||
use crate::system_helpers;
|
use crate::system_helpers;
|
||||||
use std::path::PathBuf;
|
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<Arc<Mutex<Option<PatchState>>>> = 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 {
|
pub async fn patch_game() -> bool {
|
||||||
let patch_path = PathBuf::from(system_helpers::install_location()).join("patch/version.dll");
|
let patch_path = PathBuf::from(system_helpers::install_location()).join("patch/version.dll");
|
||||||
|
|
||||||
@@ -37,6 +84,82 @@ pub async fn patch_game() -> bool {
|
|||||||
true
|
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 {
|
pub async fn unpatch_game() -> bool {
|
||||||
// Just delete patch since it's not replacing any existing file
|
// Just delete patch since it's not replacing any existing file
|
||||||
let deleted = file_helpers::delete_file(
|
let deleted = file_helpers::delete_file(
|
||||||
@@ -50,6 +173,51 @@ pub async fn unpatch_game() -> bool {
|
|||||||
deleted
|
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<String> {
|
pub async fn get_game_rsa_path() -> Option<String> {
|
||||||
let config = config::get_config();
|
let config = config::get_config();
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::config::get_config;
|
use crate::config::get_config;
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
use crate::system_helpers::run_command;
|
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::{path::PathBuf, str::FromStr, sync::Mutex};
|
use std::{path::PathBuf, str::FromStr, sync::Mutex};
|
||||||
@@ -28,6 +26,13 @@ use tauri::{api::path::data_dir, http::Uri};
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use registry::{Data, Hive, Security};
|
use registry::{Data, Hive, Security};
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use crate::system_helpers::{AsRoot, SpawnItsFineReally};
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use anime_launcher_sdk::{config::ConfigExt, genshin::config::Config};
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use std::{fs::File, io::Write, process::Command};
|
||||||
|
|
||||||
async fn shutdown_signal() {
|
async fn shutdown_signal() {
|
||||||
tokio::signal::ctrl_c()
|
tokio::signal::ctrl_c()
|
||||||
.await
|
.await
|
||||||
@@ -283,24 +288,23 @@ pub fn connect_to_proxy(proxy_port: u16) {
|
|||||||
println!("Connected to the proxy.");
|
println!("Connected to the proxy.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn connect_to_proxy(proxy_port: u16) {
|
pub fn connect_to_proxy(proxy_port: u16) {
|
||||||
// Edit /etc/environment to set $http_proxy and $https_proxy
|
let mut config = Config::get().unwrap();
|
||||||
let mut env_file = match fs::read_to_string("/etc/environment") {
|
let proxy_addr = format!("127.0.0.1:{}", proxy_port);
|
||||||
Ok(f) => f,
|
if !config.game.environment.contains_key("http_proxy") {
|
||||||
Err(e) => {
|
config
|
||||||
println!("Error opening /etc/environment: {}", e);
|
.game
|
||||||
return;
|
.environment
|
||||||
|
.insert("http_proxy".to_string(), proxy_addr.clone());
|
||||||
}
|
}
|
||||||
};
|
if !config.game.environment.contains_key("https_proxy") {
|
||||||
|
config
|
||||||
// Append the proxy configuration.
|
.game
|
||||||
// We will not remove the current proxy config if it exists, so we can just remove these last lines when we disconnect
|
.environment
|
||||||
env_file += format!("\nhttps_proxy=127.0.0.1:{}", proxy_port).as_str();
|
.insert("https_proxy".to_string(), proxy_addr);
|
||||||
env_file += format!("\nhttp_proxy=127.0.0.1:{}", proxy_port).as_str();
|
}
|
||||||
|
Config::update(config);
|
||||||
// Save
|
|
||||||
fs::write("/etc/environment", env_file).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_od = "macos")]
|
#[cfg(target_od = "macos")]
|
||||||
@@ -329,21 +333,14 @@ pub fn disconnect_from_proxy() {
|
|||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn disconnect_from_proxy() {
|
pub fn disconnect_from_proxy() {
|
||||||
println!("Re-writing environment variables");
|
let mut config = Config::get().unwrap();
|
||||||
|
if config.game.environment.contains_key("http_proxy") {
|
||||||
let regexp = regex::Regex::new(
|
config.game.environment.remove("http_proxy");
|
||||||
// This has to be specific as possible or we risk fuckin up their environment LOL
|
}
|
||||||
r"(https|http)_proxy=.*127.0.0.1:.*",
|
if config.game.environment.contains_key("https_proxy") {
|
||||||
)
|
config.game.environment.remove("https_proxy");
|
||||||
.unwrap();
|
}
|
||||||
let environment = &fs::read_to_string("/etc/environment").expect("Failed to open environment");
|
Config::update(config);
|
||||||
|
|
||||||
let new_environment = regexp.replace_all(environment, "").to_string();
|
|
||||||
|
|
||||||
// Write new environment
|
|
||||||
fs::write("/etc/environment", new_environment.trim_end()).expect(
|
|
||||||
"Could not write environment, remove proxy declarations manually if they are still set",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -449,18 +446,72 @@ pub fn install_ca_files(cert_path: &Path) {
|
|||||||
println!("Installed certificate.");
|
println!("Installed certificate.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is borked on non-debian platforms, so be it
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn install_ca_files(cert_path: &Path) {
|
pub fn install_ca_files(cert_path: &Path) {
|
||||||
|
let platform = os_type::current_platform();
|
||||||
|
use os_type::OSType::*;
|
||||||
|
// TODO: Add more distros
|
||||||
|
match &platform.os_type {
|
||||||
|
// Debian-based
|
||||||
|
Debian | Ubuntu | Kali => {
|
||||||
let usr_certs = PathBuf::from("/usr/local/share/ca-certificates");
|
let usr_certs = PathBuf::from("/usr/local/share/ca-certificates");
|
||||||
let usr_cert_path = usr_certs.join("cultivation.crt");
|
let usr_cert_path = usr_certs.join("cultivation.crt");
|
||||||
|
|
||||||
// Create dir if it doesn't exist
|
// We want to execute multiple commands, but we don't want multiple pkexec prompts
|
||||||
fs::create_dir_all(&usr_certs).expect("Unable to create local certificate directory");
|
// so we have to use a script
|
||||||
|
let script = Path::new("/tmp/cultivation-inject-ca-cert.sh");
|
||||||
fs::copy(cert_path, usr_cert_path).expect("Unable to copy cert to local certificate directory");
|
let mut scriptf = File::create(script).unwrap();
|
||||||
run_command("update-ca-certificates", vec![], None);
|
#[cfg(debug_assertions)]
|
||||||
|
let setflags = "xe";
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
let setflags = "e";
|
||||||
|
write!(
|
||||||
|
scriptf,
|
||||||
|
r#"#!/usr/bin/env bash
|
||||||
|
set -{}
|
||||||
|
CERT="{}"
|
||||||
|
CERT_DIR="{}"
|
||||||
|
CERT_TARGET="{}"
|
||||||
|
# Create dir if it doesn't exist
|
||||||
|
if ! [[ -d "$CERT_DIR" ]]; then
|
||||||
|
mkdir -v "$CERT_DIR"
|
||||||
|
fi
|
||||||
|
cp -v "$CERT" "$CERT_TARGET"
|
||||||
|
update-ca-certificates
|
||||||
|
"#,
|
||||||
|
setflags,
|
||||||
|
cert_path.to_str().unwrap(),
|
||||||
|
usr_certs.to_str().unwrap(),
|
||||||
|
usr_cert_path.to_str().unwrap()
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
scriptf.flush().unwrap();
|
||||||
|
drop(scriptf);
|
||||||
|
let _ = Command::new("bash")
|
||||||
|
.arg(script)
|
||||||
|
.as_root_gui()
|
||||||
|
.spawn_its_fine_really("Unable to install certificate");
|
||||||
|
if let Err(e) = fs::remove_file(script) {
|
||||||
|
println!("Unable to remove certificate install script: {}", e);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// RedHat-based
|
||||||
|
//Redhat | CentOS |
|
||||||
|
// Arch-based
|
||||||
|
Arch | Manjaro => {
|
||||||
|
let _ = Command::new("trust")
|
||||||
|
.arg("anchor")
|
||||||
|
.arg("--store")
|
||||||
|
.arg(cert_path)
|
||||||
|
.as_root_gui()
|
||||||
|
.spawn_its_fine_really("Unable to install certificate");
|
||||||
|
}
|
||||||
|
OSX => unreachable!(),
|
||||||
|
_ => {
|
||||||
|
println!("Unsupported Linux distribution.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
println!("Installed certificate.");
|
println!("Installed certificate.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::ffi::OsStr;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use {
|
use {
|
||||||
registry::{Data, Hive, Security},
|
registry::{Data, Hive, Security},
|
||||||
@@ -10,6 +11,101 @@ use {
|
|||||||
windows_service::service_manager::{ServiceManager, ServiceManagerAccess},
|
windows_service::service_manager::{ServiceManager, ServiceManagerAccess},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use crate::AAGL_THREAD;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use anime_launcher_sdk::{
|
||||||
|
config::ConfigExt, genshin::config::Config, genshin::game, genshin::states::LauncherState,
|
||||||
|
wincompatlib::prelude::*,
|
||||||
|
};
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use std::{path::Path, process::Stdio, thread};
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use term_detect::get_terminal;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn guess_user_terminal() -> String {
|
||||||
|
if let Ok(term) = get_terminal() {
|
||||||
|
return term.0;
|
||||||
|
}
|
||||||
|
eprintln!("Could not guess default terminal. Try setting the $TERMINAL environment variable.");
|
||||||
|
// If everything fails, default to xterm
|
||||||
|
"xterm".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn rawstrcmd(cmd: &Command) -> String {
|
||||||
|
format!("{:?}", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn strcmd(cmd: &Command) -> String {
|
||||||
|
format!("bash -c {:?}", rawstrcmd(cmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub trait AsRoot {
|
||||||
|
fn as_root(&self) -> Self;
|
||||||
|
fn as_root_gui(&self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
impl AsRoot for Command {
|
||||||
|
fn as_root(&self) -> Self {
|
||||||
|
let mut cmd = Command::new("sudo");
|
||||||
|
cmd.arg("--").arg("bash").arg("-c").arg(rawstrcmd(self));
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
fn as_root_gui(&self) -> Self {
|
||||||
|
let mut cmd = Command::new("pkexec");
|
||||||
|
cmd.arg("bash").arg("-c").arg(rawstrcmd(self));
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
trait InTerminalEmulator {
|
||||||
|
fn in_terminal(&self) -> Self;
|
||||||
|
fn in_terminal_noclose(&self) -> Self;
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
impl InTerminalEmulator for Command {
|
||||||
|
fn in_terminal(&self) -> Self {
|
||||||
|
let mut cmd = Command::new(guess_user_terminal());
|
||||||
|
cmd.arg("-e").arg(strcmd(self));
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
fn in_terminal_noclose(&self) -> Self {
|
||||||
|
let mut cmd = Command::new(guess_user_terminal());
|
||||||
|
cmd.arg("--noclose");
|
||||||
|
cmd.arg("-e").arg(strcmd(self));
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub trait SpawnItsFineReally {
|
||||||
|
fn spawn_its_fine_really(&mut self, msg: &str) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
impl SpawnItsFineReally for Command {
|
||||||
|
fn spawn_its_fine_really(&mut self, msg: &str) -> anyhow::Result<()> {
|
||||||
|
let res = self.status();
|
||||||
|
let Ok(status) = res else {
|
||||||
|
let error = res.unwrap_err();
|
||||||
|
println!("{}: {}", msg, &error);
|
||||||
|
return Err(error.into());
|
||||||
|
};
|
||||||
|
if !status.success() {
|
||||||
|
println!("{}: {}", msg, status);
|
||||||
|
Err(anyhow::anyhow!("{}: {}", msg, status))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn run_program(path: String, args: Option<String>) {
|
pub fn run_program(path: String, args: Option<String>) {
|
||||||
// Without unwrap_or, this can crash when UAC prompt is denied
|
// Without unwrap_or, this can crash when UAC prompt is denied
|
||||||
@@ -22,6 +118,7 @@ pub fn run_program(path: String, args: Option<String>) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn run_program_relative(path: String, args: Option<String>) {
|
pub fn run_program_relative(path: String, args: Option<String>) {
|
||||||
// Save the current working directory
|
// Save the current working directory
|
||||||
@@ -41,6 +138,13 @@ pub fn run_program_relative(path: String, args: Option<String>) {
|
|||||||
std::env::set_current_dir(cwd).unwrap();
|
std::env::set_current_dir(cwd).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn run_program_relative(path: String, args: Option<String>) {
|
||||||
|
// This program should not run as root
|
||||||
|
run_un_elevated(path, args)
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn run_command(program: &str, args: Vec<&str>, relative: Option<bool>) {
|
pub fn run_command(program: &str, args: Vec<&str>, relative: Option<bool>) {
|
||||||
let prog = program.to_string();
|
let prog = program.to_string();
|
||||||
@@ -81,6 +185,7 @@ pub fn run_jar(path: String, execute_in: String, java_path: String) {
|
|||||||
println!("Launching .jar with command: {}", &command);
|
println!("Launching .jar with command: {}", &command);
|
||||||
|
|
||||||
// Open the program from the specified path.
|
// Open the program from the specified path.
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
match open::with(
|
match open::with(
|
||||||
format!("/k cd /D \"{}\" & {}", &execute_in, &command),
|
format!("/k cd /D \"{}\" & {}", &execute_in, &command),
|
||||||
"C:\\Windows\\System32\\cmd.exe",
|
"C:\\Windows\\System32\\cmd.exe",
|
||||||
@@ -88,8 +193,58 @@ pub fn run_jar(path: String, execute_in: String, java_path: String) {
|
|||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => println!("Failed to open jar ({} from {}): {}", &path, &execute_in, e),
|
Err(e) => println!("Failed to open jar ({} from {}): {}", &path, &execute_in, e),
|
||||||
};
|
};
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
thread::spawn(move || {
|
||||||
|
match Command::new(guess_user_terminal())
|
||||||
|
.arg("-e")
|
||||||
|
.arg(command)
|
||||||
|
.current_dir(execute_in.clone())
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(mut handler) => {
|
||||||
|
// Prevent creation of zombie processes
|
||||||
|
handler
|
||||||
|
.wait()
|
||||||
|
.expect("Grasscutter exited with non-zero exit code");
|
||||||
|
}
|
||||||
|
Err(e) => println!("Failed to open jar ({} from {}): {}", &path, &execute_in, e),
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn run_jar_root(path: String, execute_in: String, java_path: String) {
|
||||||
|
panic!("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn run_jar_root(path: String, execute_in: String, java_path: String) {
|
||||||
|
let mut command = if java_path.is_empty() {
|
||||||
|
Command::new("java")
|
||||||
|
} else {
|
||||||
|
Command::new(java_path)
|
||||||
|
};
|
||||||
|
command.arg("-jar").arg(&path).current_dir(&execute_in);
|
||||||
|
|
||||||
|
println!("Launching .jar with command: {}", strcmd(&command));
|
||||||
|
|
||||||
|
// Open the program from the specified path.
|
||||||
|
thread::spawn(move || {
|
||||||
|
match command.as_root_gui().in_terminal().spawn() {
|
||||||
|
Ok(mut handler) => {
|
||||||
|
// Prevent creation of zombie processes
|
||||||
|
handler
|
||||||
|
.wait()
|
||||||
|
.expect("Grasscutter exited with non-zero exit code");
|
||||||
|
}
|
||||||
|
Err(e) => println!("Failed to open jar ({} from {}): {}", &path, &execute_in, e),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn run_un_elevated(path: String, args: Option<String>) {
|
pub fn run_un_elevated(path: String, args: Option<String>) {
|
||||||
// Open the program non-elevated.
|
// Open the program non-elevated.
|
||||||
@@ -106,6 +261,98 @@ pub fn run_un_elevated(path: String, args: Option<String>) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn aagl_wine_run<P: AsRef<Path>>(path: P, args: Option<String>) -> Command {
|
||||||
|
let config = Config::get().unwrap();
|
||||||
|
let wine = config.get_selected_wine().unwrap().unwrap();
|
||||||
|
let wine_run = wine
|
||||||
|
.to_wine(
|
||||||
|
config.components.path,
|
||||||
|
Some(config.game.wine.builds.join(&wine.name)),
|
||||||
|
)
|
||||||
|
.with_prefix(config.game.wine.prefix)
|
||||||
|
.with_loader(WineLoader::Current)
|
||||||
|
.with_arch(WineArch::Win64);
|
||||||
|
let env: Vec<(String, String)> = config
|
||||||
|
.game
|
||||||
|
.wine
|
||||||
|
.sync
|
||||||
|
.get_env_vars()
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
|
.collect();
|
||||||
|
use anime_launcher_sdk::components::wine::UnifiedWine::*;
|
||||||
|
let wined = match wine_run {
|
||||||
|
Default(wine) => wine,
|
||||||
|
Proton(proton) => proton.wine().clone(),
|
||||||
|
};
|
||||||
|
let mut cmd = Command::new(&wined.binary);
|
||||||
|
cmd.arg(path.as_ref()).envs(wined.get_envs()).envs(env);
|
||||||
|
if let Some(args) = args {
|
||||||
|
let mut args: Vec<String> = args.split(' ').map(|x| x.to_string()).collect();
|
||||||
|
cmd.args(&mut args);
|
||||||
|
};
|
||||||
|
cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn run_un_elevated(path: String, args: Option<String>) {
|
||||||
|
let path = Path::new(&path);
|
||||||
|
let exec_name = path.file_name().unwrap().to_str().unwrap();
|
||||||
|
if exec_name == ["Yuan", "Shen", ".exe"].join("").as_str()
|
||||||
|
|| exec_name == ["Gen", "shin", "Impact", ".exe"].join("").as_str()
|
||||||
|
{
|
||||||
|
let game_thread = thread::spawn(|| {
|
||||||
|
'statechk: {
|
||||||
|
let state = LauncherState::get_from_config(|_| {});
|
||||||
|
let Ok(state) = state else {
|
||||||
|
println!("Failed to get state: {}", state.unwrap_err());
|
||||||
|
break 'statechk;
|
||||||
|
};
|
||||||
|
use anime_launcher_sdk::genshin::states::LauncherState::*;
|
||||||
|
match state {
|
||||||
|
FolderMigrationRequired { from, .. } => Err(format!(
|
||||||
|
"A folder migration is required ({:?} needs to be moved)",
|
||||||
|
from
|
||||||
|
)),
|
||||||
|
WineNotInstalled => Err("Wine is not installed".to_string()),
|
||||||
|
PrefixNotExists => Err("The Wine prefix does not exist".to_string()),
|
||||||
|
GameNotInstalled(_) => Err("The game is not installed".to_string()),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
.expect("Can't launch game. Check the other launcher.");
|
||||||
|
}
|
||||||
|
if let Err(e) = game::run() {
|
||||||
|
println!("An error occurred while running the game: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
{
|
||||||
|
let mut game_thead_lock = AAGL_THREAD.lock().unwrap();
|
||||||
|
game_thead_lock.replace(game_thread);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Run exe with wine
|
||||||
|
if path.extension().unwrap() == "exe" {
|
||||||
|
let path = path.to_owned().clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let _ = aagl_wine_run(&path, args)
|
||||||
|
.current_dir(path.parent().unwrap())
|
||||||
|
.in_terminal()
|
||||||
|
.spawn_its_fine_really(&format!(
|
||||||
|
"Failed to open program ({})",
|
||||||
|
path.to_str().unwrap()
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
println!(
|
||||||
|
"Can't run {:?}. Running this type of file is not supported yet.",
|
||||||
|
path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_in_browser(url: String) {
|
pub fn open_in_browser(url: String) {
|
||||||
// Open the URL in the default browser.
|
// Open the URL in the default browser.
|
||||||
@@ -119,10 +366,28 @@ pub fn open_in_browser(url: String) {
|
|||||||
pub fn install_location() -> String {
|
pub fn install_location() -> String {
|
||||||
let mut exe_path = std::env::current_exe().unwrap();
|
let mut exe_path = std::env::current_exe().unwrap();
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
// Get the path to the executable.
|
// Get the path to the executable.
|
||||||
exe_path.pop();
|
exe_path.pop();
|
||||||
|
|
||||||
return exe_path.to_str().unwrap().to_string();
|
return exe_path.to_str().unwrap().to_string();
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
let bin_name = exe_path.file_name().unwrap().to_str().unwrap().to_string();
|
||||||
|
exe_path.pop();
|
||||||
|
if exe_path.starts_with("/usr/bin") {
|
||||||
|
let mut path = PathBuf::from("/usr/lib");
|
||||||
|
path.push(bin_name);
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
exe_path
|
||||||
|
}
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -248,9 +513,41 @@ pub fn service_status(service: String) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(target_os = "linux")]
|
||||||
|
fn to_linux_service_name(service: &str) -> Option<String> {
|
||||||
|
Some(format!(
|
||||||
|
"{}.service",
|
||||||
|
match service {
|
||||||
|
"MongoDB" => "mongod",
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn service_status(_service: String) {}
|
pub fn service_status(service: String) -> bool {
|
||||||
|
// Change Windows service name into Linux service name
|
||||||
|
let service_lnx = to_linux_service_name(&service);
|
||||||
|
if service_lnx.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let service_lnx = service_lnx.unwrap();
|
||||||
|
let status = Command::new("systemctl")
|
||||||
|
.arg("is-active")
|
||||||
|
.arg(service_lnx)
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.status();
|
||||||
|
if status.is_err() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let status = status.unwrap().success();
|
||||||
|
if status {
|
||||||
|
status
|
||||||
|
} else {
|
||||||
|
start_service(service)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -271,10 +568,20 @@ pub fn start_service(service: String) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(target_os = "linux")]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn start_service(_service: String) {
|
pub fn start_service(service: String) -> bool {
|
||||||
let _started = OsStr::new("Started service!");
|
println!("Starting service: {}", service);
|
||||||
|
let service_lnx = to_linux_service_name(&service);
|
||||||
|
if service_lnx.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let service_lnx = service_lnx.unwrap();
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("start")
|
||||||
|
.arg(service_lnx)
|
||||||
|
.spawn_its_fine_really(&format!("Failed to stop service {}", service))
|
||||||
|
.is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -296,11 +603,39 @@ pub fn stop_service(service: String) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(target_os = "linux")]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn stop_service(_service: String) {}
|
pub fn stop_service(service: String) -> bool {
|
||||||
|
println!("Stopping service: {}", service);
|
||||||
|
let service_lnx = to_linux_service_name(&service);
|
||||||
|
if service_lnx.is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let service_lnx = service_lnx.unwrap();
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("stop")
|
||||||
|
.arg(service_lnx)
|
||||||
|
.spawn_its_fine_really(&format!("Failed to start service {}", service))
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(target_os = "linux")]
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn wipe_registry(exec_name: String) {
|
||||||
|
println!("Wiping registry");
|
||||||
|
let regpath = format!("HKCU\\Software\\miHoYo\\{}", exec_name);
|
||||||
|
let mut cmd = aagl_wine_run("reg", None);
|
||||||
|
cmd.args([
|
||||||
|
"DELETE",
|
||||||
|
®path,
|
||||||
|
"/f",
|
||||||
|
"/v",
|
||||||
|
"MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810",
|
||||||
|
]);
|
||||||
|
let _ = cmd.spawn_its_fine_really("Error wiping registry");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn wipe_registry(_exec_name: String) {}
|
pub fn wipe_registry(_exec_name: String) {}
|
||||||
|
|
||||||
@@ -320,3 +655,55 @@ pub fn is_elevated() -> bool {
|
|||||||
pub fn get_platform() -> &'static str {
|
pub fn get_platform() -> &'static str {
|
||||||
std::env::consts::OS
|
std::env::consts::OS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn jvm_add_cap(_java_path: String) -> bool {
|
||||||
|
panic!("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn jvm_remove_cap(_java_path: String) -> bool {
|
||||||
|
panic!("Not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn jvm_add_cap(java_path: String) -> bool {
|
||||||
|
let mut java_bin = if java_path.is_empty() {
|
||||||
|
which::which("java").expect("Java is not installed")
|
||||||
|
} else {
|
||||||
|
PathBuf::from(&java_path)
|
||||||
|
};
|
||||||
|
while java_bin.is_symlink() {
|
||||||
|
java_bin = java_bin.read_link().unwrap()
|
||||||
|
}
|
||||||
|
println!("Removing cap on {:?}", &java_bin);
|
||||||
|
Command::new("setcap")
|
||||||
|
.arg("CAP_NET_BIND_SERVICE=+eip")
|
||||||
|
.arg(java_bin)
|
||||||
|
.as_root_gui()
|
||||||
|
.spawn_its_fine_really(&format!("Failed to add cap to {}", java_path))
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn jvm_remove_cap(java_path: String) -> bool {
|
||||||
|
let mut java_bin = if java_path.is_empty() {
|
||||||
|
which::which("java").expect("Java is not installed")
|
||||||
|
} else {
|
||||||
|
PathBuf::from(&java_path)
|
||||||
|
};
|
||||||
|
while java_bin.is_symlink() {
|
||||||
|
java_bin = java_bin.read_link().unwrap()
|
||||||
|
}
|
||||||
|
println!("Setting cap on {:?}", &java_bin);
|
||||||
|
Command::new("setcap")
|
||||||
|
.arg("-r")
|
||||||
|
.arg(java_bin)
|
||||||
|
.as_root_gui()
|
||||||
|
.spawn_its_fine_really(&format!("Failed to remove cap from {}", java_path))
|
||||||
|
.is_ok()
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src 'self'; img-src 'self'; img-src https://* asset: https://asset.localhost; media-src https://* asset: https://asset.localhost; style-src-elem https://* asset https://asset.localhost; script-src-elem https://* asset https://asset.localhost;"
|
"csp": "default-src 'self'; img-src 'self' https://* asset: https://asset.localhost tauri://localhost; media-src https://* asset: https://asset.localhost tauri://localhost; style-src-elem https://* asset: https://asset.localhost tauri://localhost; script-src-elem https://* asset: https://asset.localhost tauri://localhost;"
|
||||||
},
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"active": false,
|
"active": false,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import React from 'react'
|
|||||||
import TopBar from './components/TopBar'
|
import TopBar from './components/TopBar'
|
||||||
import ServerLaunchSection from './components/ServerLaunchSection'
|
import ServerLaunchSection from './components/ServerLaunchSection'
|
||||||
import MainProgressBar from './components/common/MainProgressBar'
|
import MainProgressBar from './components/common/MainProgressBar'
|
||||||
import Options from './components/menu/Options'
|
import Options, { GrasscutterElevation } from './components/menu/Options'
|
||||||
import MiniDialog from './components/MiniDialog'
|
import MiniDialog from './components/MiniDialog'
|
||||||
import DownloadList from './components/common/DownloadList'
|
import DownloadList from './components/common/DownloadList'
|
||||||
import Downloads from './components/menu/Downloads'
|
import Downloads from './components/menu/Downloads'
|
||||||
@@ -15,7 +15,7 @@ import { ExtrasMenu } from './components/menu/ExtrasMenu'
|
|||||||
import Notification from './components/common/Notification'
|
import Notification from './components/common/Notification'
|
||||||
import GamePathNotify from './components/menu/GamePathNotify'
|
import GamePathNotify from './components/menu/GamePathNotify'
|
||||||
|
|
||||||
import { getConfigOption, setConfigOption } from '../utils/configuration'
|
import { getConfig, getConfigOption, setConfigOption } from '../utils/configuration'
|
||||||
import { invoke } from '@tauri-apps/api'
|
import { invoke } from '@tauri-apps/api'
|
||||||
import { getVersion } from '@tauri-apps/api/app'
|
import { getVersion } from '@tauri-apps/api/app'
|
||||||
import { listen } from '@tauri-apps/api/event'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
@@ -102,10 +102,28 @@ export class Main extends React.Component<IProps, IState> {
|
|||||||
// Emitted for automatic processes
|
// Emitted for automatic processes
|
||||||
listen('grasscutter_closed', async () => {
|
listen('grasscutter_closed', async () => {
|
||||||
const autoService = await getConfigOption('auto_mongodb')
|
const autoService = await getConfigOption('auto_mongodb')
|
||||||
|
const config = await getConfig()
|
||||||
|
|
||||||
if (autoService) {
|
if (autoService) {
|
||||||
await invoke('stop_service', { service: 'MongoDB' })
|
await invoke('stop_service', { service: 'MongoDB' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((await invoke('get_platform')) === 'linux') {
|
||||||
|
switch (config.grasscutter_elevation) {
|
||||||
|
case GrasscutterElevation.None:
|
||||||
|
break
|
||||||
|
|
||||||
|
case GrasscutterElevation.Capability:
|
||||||
|
await invoke('jvm_remove_cap', {
|
||||||
|
javaPath: config.java_path,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.error('Invalid grasscutter_elevation')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
let min = false
|
let min = false
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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 { GrasscutterElevation } from './menu/Options'
|
||||||
import { getGameExecutable, getGameVersion, getGrasscutterJar } 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'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
@@ -210,13 +211,14 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
|
|
||||||
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) {
|
|
||||||
const grasscutter_jar = await getGrasscutterJar()
|
const grasscutter_jar = await getGrasscutterJar()
|
||||||
await invoke('enable_grasscutter_watcher', {
|
await invoke('enable_grasscutter_watcher', {
|
||||||
process: proc_name || grasscutter_jar,
|
process: proc_name || grasscutter_jar,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (config.auto_mongodb) {
|
||||||
// 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' })
|
invoke('service_status', { service: 'MongoDB' })
|
||||||
}
|
}
|
||||||
|
|
||||||
let jarFolder = config.grasscutter_path
|
let jarFolder = config.grasscutter_path
|
||||||
@@ -227,8 +229,31 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
|||||||
jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('\\'))
|
jarFolder = jarFolder.substring(0, config.grasscutter_path.lastIndexOf('\\'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cmd = 'run_jar'
|
||||||
|
|
||||||
|
if ((await invoke('get_platform')) === 'linux') {
|
||||||
|
switch (config.grasscutter_elevation) {
|
||||||
|
case GrasscutterElevation.None:
|
||||||
|
break
|
||||||
|
|
||||||
|
case GrasscutterElevation.Capability:
|
||||||
|
await invoke('jvm_add_cap', {
|
||||||
|
javaPath: config.java_path,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
case GrasscutterElevation.Root:
|
||||||
|
cmd = 'run_jar_root'
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.error('Invalid grasscutter_elevation')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Launch the jar
|
// Launch the jar
|
||||||
await invoke('run_jar', {
|
await invoke(cmd, {
|
||||||
path: config.grasscutter_path,
|
path: config.grasscutter_path,
|
||||||
executeIn: jarFolder,
|
executeIn: jarFolder,
|
||||||
javaPath: config.java_path || '',
|
javaPath: config.java_path || '',
|
||||||
|
|||||||
@@ -15,9 +15,14 @@ import BigButton from '../common/BigButton'
|
|||||||
import DownloadHandler from '../../../utils/download'
|
import DownloadHandler from '../../../utils/download'
|
||||||
import * as meta from '../../../utils/rsa'
|
import * as meta from '../../../utils/rsa'
|
||||||
import HelpButton from '../common/HelpButton'
|
import HelpButton from '../common/HelpButton'
|
||||||
import TextInput from '../common/TextInput'
|
|
||||||
import SmallButton from '../common/SmallButton'
|
import SmallButton from '../common/SmallButton'
|
||||||
|
|
||||||
|
export enum GrasscutterElevation {
|
||||||
|
None = 'None',
|
||||||
|
Capability = 'Capability',
|
||||||
|
Root = 'Root',
|
||||||
|
}
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
closeFn: () => void
|
closeFn: () => void
|
||||||
downloadManager: DownloadHandler
|
downloadManager: DownloadHandler
|
||||||
@@ -45,6 +50,9 @@ interface IState {
|
|||||||
un_elevated: boolean
|
un_elevated: boolean
|
||||||
redirect_more: boolean
|
redirect_more: boolean
|
||||||
|
|
||||||
|
// Linux stuff
|
||||||
|
grasscutter_elevation: string
|
||||||
|
|
||||||
// Swag stuff
|
// Swag stuff
|
||||||
akebi_path: string
|
akebi_path: string
|
||||||
migoto_path: string
|
migoto_path: string
|
||||||
@@ -77,6 +85,9 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
un_elevated: false,
|
un_elevated: false,
|
||||||
redirect_more: false,
|
redirect_more: false,
|
||||||
|
|
||||||
|
// Linux stuff
|
||||||
|
grasscutter_elevation: GrasscutterElevation.None,
|
||||||
|
|
||||||
// Swag stuff
|
// Swag stuff
|
||||||
akebi_path: '',
|
akebi_path: '',
|
||||||
migoto_path: '',
|
migoto_path: '',
|
||||||
@@ -133,6 +144,9 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
un_elevated: config.un_elevated || false,
|
un_elevated: config.un_elevated || false,
|
||||||
redirect_more: config.redirect_more || false,
|
redirect_more: config.redirect_more || false,
|
||||||
|
|
||||||
|
// Linux stuff
|
||||||
|
grasscutter_elevation: config.grasscutter_elevation || GrasscutterElevation.None,
|
||||||
|
|
||||||
// Swag stuff
|
// Swag stuff
|
||||||
akebi_path: config.akebi_path || '',
|
akebi_path: config.akebi_path || '',
|
||||||
migoto_path: config.migoto_path || '',
|
migoto_path: config.migoto_path || '',
|
||||||
@@ -298,6 +312,14 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setGCElevation(value: string) {
|
||||||
|
setConfigOption('grasscutter_elevation', value)
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
grasscutter_elevation: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async removeRSA() {
|
async removeRSA() {
|
||||||
await meta.unpatchGame()
|
await meta.unpatchGame()
|
||||||
}
|
}
|
||||||
@@ -328,7 +350,6 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Menu closeFn={this.props.closeFn} className="Options" heading="Options">
|
<Menu closeFn={this.props.closeFn} className="Options" heading="Options">
|
||||||
{!this.state.platform || this.state.platform === 'windows' ? (
|
|
||||||
<div className="OptionSection" id="menuOptionsContainerGamePath">
|
<div className="OptionSection" id="menuOptionsContainerGamePath">
|
||||||
<div className="OptionLabel" id="menuOptionsLabelGamePath">
|
<div className="OptionLabel" id="menuOptionsLabelGamePath">
|
||||||
<Tr text="options.game_path" />
|
<Tr text="options.game_path" />
|
||||||
@@ -337,16 +358,6 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
<DirInput onChange={this.setGameExecutable} value={this.state?.game_install_path} extensions={['exe']} />
|
<DirInput onChange={this.setGameExecutable} value={this.state?.game_install_path} extensions={['exe']} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
|
||||||
<div className="OptionSection" id="menuOptionsContainerGameCommand">
|
|
||||||
<div className="OptionLabel" id="menuOptionsLabelGameCommand">
|
|
||||||
<Tr text="options.game_command" />
|
|
||||||
</div>
|
|
||||||
<div className="OptionValue" id="menuOptionsGameCommand">
|
|
||||||
<TextInput />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="OptionSection" id="menuOptionsContainermetaDownload">
|
<div className="OptionSection" id="menuOptionsContainermetaDownload">
|
||||||
<div className="OptionLabel" id="menuOptionsLabelmetaDownload">
|
<div className="OptionLabel" id="menuOptionsLabelmetaDownload">
|
||||||
<Tr text="options.recover_rsa" />
|
<Tr text="options.recover_rsa" />
|
||||||
@@ -446,6 +457,35 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
</BigButton>
|
</BigButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{this.state.platform === 'linux' && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<div className="OptionSection" id="menuOptionsContainerGCElevation">
|
||||||
|
<div className="OptionLabel" id="menuOptionsLabelGCElevation">
|
||||||
|
<Tr text="options.grasscutter_elevation" />
|
||||||
|
<HelpButton contents="help.grasscutter_elevation_help_text" />
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
value={this.state.grasscutter_elevation}
|
||||||
|
id="menuOptionsSelectGCElevation"
|
||||||
|
onChange={(event) => {
|
||||||
|
this.setGCElevation(event.target.value)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Object.keys(GrasscutterElevation).map((t) => (
|
||||||
|
<option key={t} value={t}>
|
||||||
|
{t}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="OptionSection" id="menuOptionsContainerCheckAAGL">
|
||||||
|
<div className="OptionLabel" id="menuOptionsLabelCheckAAGL">
|
||||||
|
<Tr text="options.check_aagl" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{this.state.swag && (
|
{this.state.swag && (
|
||||||
<>
|
<>
|
||||||
<Divider />
|
<Divider />
|
||||||
@@ -491,6 +531,7 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{this.state.platform !== 'linux' && (
|
||||||
<div className="OptionSection" id="menuOptionsContainerUEGame">
|
<div className="OptionSection" id="menuOptionsContainerUEGame">
|
||||||
<div className="OptionLabel" id="menuOptionsLabelUEGame">
|
<div className="OptionLabel" id="menuOptionsLabelUEGame">
|
||||||
<Tr text="options.un_elevated" />
|
<Tr text="options.un_elevated" />
|
||||||
@@ -503,6 +544,7 @@ export default class Options extends React.Component<IProps, IState> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{this.state.swag ? (
|
{this.state.swag ? (
|
||||||
<div className="OptionSection" id="menuOptionsContainerHorny">
|
<div className="OptionSection" id="menuOptionsContainerHorny">
|
||||||
<div className="OptionLabel" id="menuOptionsLabelHorny">
|
<div className="OptionLabel" id="menuOptionsLabelHorny">
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ let defaultConfig: Configuration
|
|||||||
auto_mongodb: false,
|
auto_mongodb: false,
|
||||||
un_elevated: false,
|
un_elevated: false,
|
||||||
redirect_more: false,
|
redirect_more: false,
|
||||||
|
|
||||||
|
// Linux stuff
|
||||||
|
grasscutter_elevation: 'None',
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
@@ -60,6 +63,9 @@ export interface Configuration {
|
|||||||
un_elevated: boolean
|
un_elevated: boolean
|
||||||
redirect_more: boolean
|
redirect_more: boolean
|
||||||
|
|
||||||
|
// Linux stuff
|
||||||
|
grasscutter_elevation: string
|
||||||
|
|
||||||
// Swag stuff
|
// Swag stuff
|
||||||
akebi_path?: string
|
akebi_path?: string
|
||||||
migoto_path?: string
|
migoto_path?: string
|
||||||
|
|||||||
@@ -1,50 +1,10 @@
|
|||||||
import { invoke } from '@tauri-apps/api'
|
import { invoke } from '@tauri-apps/api'
|
||||||
import { getGameFolder } from './game'
|
|
||||||
// Patch file from: https://github.com/34736384/RSAPatch/
|
// Patch file from: https://github.com/34736384/RSAPatch/
|
||||||
|
|
||||||
export async function patchGame() {
|
export async function patchGame() {
|
||||||
const patchPath = (await invoke('install_location')) + '/patch/version.dll'
|
return invoke('patch_game')
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unpatchGame() {
|
export async function unpatchGame() {
|
||||||
// Just delete patch since it's not replacing any existing file
|
return invoke('unpatch_game')
|
||||||
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, '/')
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user