Add an option that allows binding port 443

Kinda everts some changes of
"Implement MongoDB autostart and GC watching".
This commit makes launching MongoDB async
This commit is contained in:
fnrir 2023-08-18 13:57:50 +02:00
parent 2c07cf90bd
commit 62b54f33df
10 changed files with 256 additions and 11 deletions

12
src-tauri/Cargo.lock generated
View File

@ -940,6 +940,7 @@ dependencies = [
"tokio-tungstenite 0.17.2",
"tracing",
"unrar",
"which",
"windows-service",
"zip 0.6.2",
"zip-extract",
@ -5341,6 +5342,17 @@ dependencies = [
"cc",
]
[[package]]
name = "which"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269"
dependencies = [
"either",
"libc",
"once_cell",
]
[[package]]
name = "widestring"
version = "1.0.2"

View File

@ -25,6 +25,7 @@ sudo = "0.6.0"
[target.'cfg(target_os = "linux")'.dependencies]
anyhow = "1.0.58"
term-detect = "0.1.7"
which = "4.4"
[dependencies]
serde = { version = "1.0", features = ["derive"] }

View File

@ -34,7 +34,8 @@
"auto_mongodb": "Automatically Start MongoDB",
"un_elevated": "Run the game non-elevated (no admin)",
"redirect_more": "Also redirect other MHY games",
"check_aagl": "For more options, check the other launcher"
"check_aagl": "For more options, check the other launcher",
"grasscutter_elevation": "Method of running GC on restricted ports"
},
"downloads": {
"grasscutter_fullbuild": "Download Grasscutter All-in-One",
@ -86,7 +87,8 @@
"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.",
"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": {
"akebi_name": "Akebi",

View File

@ -34,7 +34,8 @@
"auto_mongodb": "Automatycznie uruchamiaj MongoDB",
"un_elevated": "Uruchamiaj grę bez uprawnień administratora/roota",
"redirect_more": "Przekieruj też inne gry MHY",
"check_aagl": "Więcej opcji znajdziesz w drugim launcherze"
"check_aagl": "Więcej opcji znajdziesz w drugim launcherze",
"grasscutter_elevation": "Sposób uruchomienia GC na ograniczonym porcie"
},
"downloads": {
"grasscutter_fullbuild": "Pobierz Grasscutter (wszystko w jednym)",
@ -86,7 +87,8 @@
"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.",
"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": {
"akebi_name": "Akebi",

View File

@ -214,6 +214,7 @@ fn main() -> Result<(), ArgsError> {
system_helpers::service_status,
system_helpers::stop_service,
system_helpers::run_jar,
system_helpers::run_jar_root,
system_helpers::open_in_browser,
system_helpers::install_location,
system_helpers::is_elevated,
@ -222,6 +223,8 @@ fn main() -> Result<(), ArgsError> {
system_helpers::wipe_registry,
system_helpers::get_platform,
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,

View File

@ -30,6 +30,56 @@ fn guess_user_terminal() -> String {
"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<()>;
@ -159,6 +209,38 @@ pub fn run_jar(path: String, execute_in: String, java_path: String) {
});
}
#[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]
pub fn run_un_elevated(path: String, args: Option<String>) {
@ -476,3 +558,55 @@ pub fn is_elevated() -> bool {
pub fn get_platform() -> &'static str {
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()
}

View File

@ -4,7 +4,7 @@ import React from 'react'
import TopBar from './components/TopBar'
import ServerLaunchSection from './components/ServerLaunchSection'
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 DownloadList from './components/common/DownloadList'
import Downloads from './components/menu/Downloads'
@ -15,7 +15,7 @@ import { ExtrasMenu } from './components/menu/ExtrasMenu'
import Notification from './components/common/Notification'
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 { getVersion } from '@tauri-apps/api/app'
import { listen } from '@tauri-apps/api/event'
@ -102,10 +102,28 @@ export class Main extends React.Component<IProps, IState> {
// Emitted for automatic processes
listen('grasscutter_closed', async () => {
const autoService = await getConfigOption('auto_mongodb')
const config = await getConfig()
if (autoService) {
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

View File

@ -12,6 +12,7 @@ import Plus from '../../resources/icons/plus.svg'
import './ServerLaunchSection.css'
import { dataDir } from '@tauri-apps/api/path'
import { GrasscutterElevation } from './menu/Options'
import { getGameExecutable, getGameVersion, getGrasscutterJar } from '../../utils/game'
import { patchGame, unpatchGame } from '../../utils/rsa'
import { listen } from '@tauri-apps/api/event'
@ -210,11 +211,12 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
if (!config.grasscutter_path) return alert('Grasscutter not installed or set!')
const grasscutter_jar = await getGrasscutterJar()
await invoke('enable_grasscutter_watcher', {
process: proc_name || grasscutter_jar,
})
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
invoke('service_status', { service: 'MongoDB' })
}
@ -227,8 +229,31 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
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
await invoke('run_jar', {
await invoke(cmd, {
path: config.grasscutter_path,
executeIn: jarFolder,
javaPath: config.java_path || '',

View File

@ -17,6 +17,12 @@ import * as meta from '../../../utils/rsa'
import HelpButton from '../common/HelpButton'
import SmallButton from '../common/SmallButton'
export enum GrasscutterElevation {
None = 'None',
Capability = 'Capability',
Root = 'Root',
}
interface IProps {
closeFn: () => void
downloadManager: DownloadHandler
@ -44,6 +50,9 @@ interface IState {
un_elevated: boolean
redirect_more: boolean
// Linux stuff
grasscutter_elevation: string
// Swag stuff
akebi_path: string
migoto_path: string
@ -76,6 +85,9 @@ export default class Options extends React.Component<IProps, IState> {
un_elevated: false,
redirect_more: false,
// Linux stuff
grasscutter_elevation: GrasscutterElevation.None,
// Swag stuff
akebi_path: '',
migoto_path: '',
@ -132,6 +144,9 @@ export default class Options extends React.Component<IProps, IState> {
un_elevated: config.un_elevated || false,
redirect_more: config.redirect_more || false,
// Linux stuff
grasscutter_elevation: config.grasscutter_elevation || GrasscutterElevation.None,
// Swag stuff
akebi_path: config.akebi_path || '',
migoto_path: config.migoto_path || '',
@ -297,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() {
await meta.unpatchGame()
}
@ -437,6 +460,25 @@ export default class Options extends React.Component<IProps, IState> {
{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" />

View File

@ -28,6 +28,9 @@ let defaultConfig: Configuration
auto_mongodb: false,
un_elevated: false,
redirect_more: false,
// Linux stuff
grasscutter_elevation: 'None',
}
})()
@ -60,6 +63,9 @@ export interface Configuration {
un_elevated: boolean
redirect_more: boolean
// Linux stuff
grasscutter_elevation: string
// Swag stuff
akebi_path?: string
migoto_path?: string