diff --git a/.husky/pre-commit b/.husky/pre-commit index 5a182ef..3ab2e13 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -yarn lint-staged +yarn lint-staged --allow-empty diff --git a/README.md b/README.md index 5116585..d64b896 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,14 @@ EN | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | -# Client Patching Notice - -For game versions 2.8 and above, Cultivation automatically makes a small patch to your game client when launching using Grasscutter, and restores it upon closing the game. In theory, you should still be totally safe, however it would be dishonest to not explicitly state that **modifying the game client could, theoretically, lead to a ban if you connect to official servers with it**. It is extremely unlikely AND there are no instances known of it happening, but the possibility exists. - # Cultivation A game launcher designed to easily proxy traffic from anime game to private servers. -While the Cultivation repository is **open**. This does **not** mean it has released. -Please do **NOT install, download, or use pre-compiled versions of Cultivation found elsewhere**. Only use releases from this GitHub repository. - # Table Of Contents +- [Client Patching Notice](#client-patching-notice) - [Download](#download) +- [Setup](#setup) - [Developer Quick-start](#developer-quickstart) - [Setup](#setup) - [Building](#building) @@ -23,13 +18,45 @@ Please do **NOT install, download, or use pre-compiled versions of Cultivation f - [Screenshots](#screenshots) - [Credits](#credits) +# Client Patching Notice + +For game versions 2.8 and above, Cultivation automatically makes a small patch to your game client when launching using Grasscutter, and restores it upon closing the game. In theory, you should still be totally safe, however it would be dishonest to not explicitly state that **modifying the game client could, theoretically, lead to a ban if you connect to official servers with it**. It is extremely unlikely AND there are no instances known of it happening, but the possibility exists. + # Download [Find release builds here!](https://github.com/Grasscutters/Cultivation/releases) -Once downloaded, extract somewhere and open as administrator. +Download and open the MSI, and once installed, run Cultivation as administrator. Refer below for more [detailed setup instructions](#setup). -**Windows 7 Users:** You will need to download [WebView](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) manually. +**Windows 7 Users:** You will need to download [WebView](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) manually, and download the `.zip` instead of the `.msi`. + +# Setup + +5-minute video for those who don't like to/cannot read: https://youtu.be/e0irOYbQe7I + +* Download Cultivation + * If you are on Windows 10 or 11, use the MSI + * If you are on Windows 7, or the MSI doesn't work, use the zip and download [WebView](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) + * If you are on Linux or MacOS, [help us port Windows-specific system calls to Linux/MacOS!](https://github.com/Grasscutters/Cultivation/issues/7) +* Install or extract Cultivation +* Open Cultivation ***as administrator*** +* Before clicking randomly on stuff, in options (top right cog icon), set your Game Install Path. + * If you are using an existing server installation from somewhere else, you can set the `.jar` file in settings as well. All downloads made through Culti will automatically use that path, no additional config needed. + * If you use multiple Java versions, you can set the Java path to your Java 17 installation (only required if you are running your own server) +* Decide if you want to download your own server, or just join a public one + * If joining a public one, you're done. Just click "Connect with Grasscutter" and input the address and port. You do not have to continue these instructions. + * If you are getting System Error, or 4214, ask the [Discord support channels](https://discord.gg/grasscutter) +* Open the "Downloads" menu (top right) + * Download "latest grasscutter" (second from the top) + * Download "resources" (very bottom) +* Once all of that is done, click the icon next to "Launch" +* To play on your new server: + * Click "Connect with Grasscutter" + * Input `localhost` as the address, and `443` as the port + * Ensure HTTPS is disabled +* Any generic "I am getting XYZ error!" should go in the [Discord support channels](https://discord.gg/grasscutter) +* Any specific Cultivation issues should go in [the issues section](/issues) +* Any Grasscutter server related issues should go in [the Grasscutter issues section](https://github.com/Grasscutters/Grasscutter) # Developer Quickstart diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 448c924..b40776c 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,9 +3,13 @@ windows_subsystem = "windows" )] +use file_helpers::dir_exists; use once_cell::sync::Lazy; +use std::fs; use std::io::Write; use std::{collections::HashMap, sync::Mutex}; +use system_helpers::is_elevated; +use tauri::api::path::data_dir; use tauri::async_runtime::block_on; use std::thread; @@ -44,6 +48,17 @@ async fn arg_handler(args: &Vec) { } fn main() { + if !is_elevated() { + println!("==============================================================================="); + println!("You running as a non-elevated user. Some stuff will almost definitely not work."); + println!("==============================================================================="); + } + + // Setup datadir/cultivation just in case something went funky and it wasn't made + if !dir_exists(&data_dir().unwrap().join("cultivation").to_str().unwrap()) { + fs::create_dir_all(&data_dir().unwrap().join("cultivation")).unwrap(); + } + // Always set CWD to the location of the executable. let mut exe_path = std::env::current_exe().unwrap(); exe_path.pop(); @@ -55,6 +70,7 @@ fn main() { // For disabled GUI ctrlc::set_handler(|| { + disconnect(); std::process::exit(0); }) .unwrap_or(()); diff --git a/src-tauri/src/proxy.rs b/src-tauri/src/proxy.rs index f9018fe..f4c0f44 100644 --- a/src-tauri/src/proxy.rs +++ b/src-tauri/src/proxy.rs @@ -3,8 +3,9 @@ * https://github.com/omjadas/hudsucker/blob/main/examples/log.rs */ +use crate::system_helpers::run_command; use once_cell::sync::Lazy; -use std::{str::FromStr, sync::Mutex}; +use std::{path::PathBuf, str::FromStr, sync::Mutex}; use hudsucker::{ async_trait::async_trait, @@ -19,7 +20,7 @@ use std::net::SocketAddr; use std::path::Path; use rustls_pemfile as pemfile; -use tauri::http::Uri; +use tauri::{api::path::data_dir, http::Uri}; #[cfg(windows)] use registry::{Data, Hive, Security}; @@ -75,11 +76,32 @@ impl HttpHandler for ProxyHandler { * Starts an HTTP(S) proxy server. */ pub async fn create_proxy(proxy_port: u16, certificate_path: String) { + let cert_path = PathBuf::from(certificate_path); + let pk_path = cert_path.join("private.key"); + let ca_path = cert_path.join("cert.crt"); + // Get the certificate and private key. - let mut private_key_bytes: &[u8] = - &fs::read(format!("{}\\private.key", certificate_path)).expect("Could not read private key"); - let mut ca_cert_bytes: &[u8] = - &fs::read(format!("{}\\cert.crt", certificate_path)).expect("Could not read certificate"); + let mut private_key_bytes: &[u8] = &match fs::read(&pk_path) { + // Try regenerating the CA stuff and read it again. If that doesn't work, quit. + Ok(b) => b, + Err(e) => { + println!("Encountered {}. Regenerating CA cert and retrying...", e); + generate_ca_files(&data_dir().unwrap().join("cultivation")); + + fs::read(&pk_path).expect("Could not read private key") + } + }; + + let mut ca_cert_bytes: &[u8] = &match fs::read(&ca_path) { + // Try regenerating the CA stuff and read it again. If that doesn't work, quit. + Ok(b) => b, + Err(e) => { + println!("Encountered {}. Regenerating CA cert and retrying...", e); + generate_ca_files(&data_dir().unwrap().join("cultivation")); + + fs::read(&ca_path).expect("Could not read certificate") + } + }; // Parse the private key and certificate. let private_key = rustls::PrivateKey( @@ -138,9 +160,29 @@ pub fn connect_to_proxy(proxy_port: u16) { println!("Connected to the proxy."); } -#[cfg(not(windows))] +#[cfg(unix)] +pub fn connect_to_proxy(proxy_port: u16) { + // Edit /etc/environment to set $http_proxy and $https_proxy + let mut env_file = match fs::read_to_string("/etc/environment") { + Ok(f) => f, + Err(e) => { + println!("Error opening /etc/environment: {}", e); + return; + } + }; + + // Append the proxy configuration. + // We will not remove the current proxy config if it exists, so we can just remove these last lines when we disconnect + env_file += format!("\nhttps_proxy=127.0.0.1:{}", proxy_port).as_str(); + env_file += format!("\nhttp_proxy=127.0.0.1:{}", proxy_port).as_str(); + + // Save + fs::write("/etc/environment", env_file).unwrap(); +} + +#[cfg(macos)] pub fn connect_to_proxy(_proxy_port: u16) { - println!("Connecting to the proxy is not implemented on this platform."); + println!("No Mac support yet. Someone mail me a Macbook and I will do it B)") } /** @@ -162,7 +204,26 @@ pub fn disconnect_from_proxy() { println!("Disconnected from proxy."); } -#[cfg(not(windows))] +#[cfg(target_os = "linux")] +pub fn disconnect_from_proxy() { + println!("Re-writing environment variables"); + + let regexp = regex::Regex::new( + // This has to be specific as possible or we risk fuckin up their environment LOL + r"(https|http)_proxy=.*127.0.0.1:.*", + ) + .unwrap(); + let environment = &fs::read_to_string("/etc/environment").expect("Failed to open environment"); + + 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")] pub fn disconnect_from_proxy() {} /* @@ -260,11 +321,27 @@ pub fn install_ca_files(cert_path: &Path) { "/Library/Keychains/System.keychain", cert_path.to_str().unwrap(), ], + None, ); println!("Installed certificate."); } -#[cfg(not(any(windows, target_os = "macos")))] +// If this is borked on non-debian platforms, so be it +#[cfg(target_os = "linux")] +pub fn install_ca_files(cert_path: &Path) { + let usr_certs = PathBuf::from("/usr/local/share/ca-certificates"); + let usr_cert_path = usr_certs.join("cultivation.crt"); + + // Create dir if it doesn't exist + fs::create_dir_all(&usr_certs).expect("Unable to create local certificate directory"); + + fs::copy(cert_path, &usr_cert_path).expect("Unable to copy cert to local certificate directory"); + run_command("update-ca-certificates", vec![], None); + + println!("Installed certificate."); +} + +#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] pub fn install_ca_files(_cert_path: &Path) { println!("Certificate installation is not supported on this platform."); } diff --git a/src-tauri/src/system_helpers.rs b/src-tauri/src/system_helpers.rs index 5a72302..ab2f160 100644 --- a/src-tauri/src/system_helpers.rs +++ b/src-tauri/src/system_helpers.rs @@ -130,6 +130,7 @@ pub fn set_migoto_target(path: String, migoto_path: String) -> bool { } } +#[cfg(windows)] #[tauri::command] pub fn wipe_registry(exec_name: String) { // Fetch the 'Internet Settings' registry key. @@ -152,6 +153,10 @@ pub fn wipe_registry(exec_name: String) { } } +#[cfg(unix)] +#[tauri::command] +pub fn wipe_registry(_exec_name: String) {} + #[cfg(windows)] #[tauri::command] pub fn is_elevated() -> bool {