From a2225c0a6898861fe3f06e26a4c1617d26d5dab0 Mon Sep 17 00:00:00 2001 From: hippoz Date: Wed, 24 Mar 2021 19:03:16 +0200 Subject: [PATCH] allow launching the studio and the player from the browser --- Cargo.lock | 11 ++++++ Cargo.toml | 1 + src/config.rs | 46 ++++++++++++++++++++++--- src/installctl.rs | 86 +++++++++++++++++++++++++++++++++++++++++++---- src/main.rs | 37 ++++++++++++++------ src/winectl.rs | 14 +++----- 6 files changed, 164 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85479f7..b4c0573 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -508,6 +508,7 @@ dependencies = [ "reqwest", "serde", "which", + "whoami", ] [[package]] @@ -1193,6 +1194,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "whoami" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e296f550993cba2c5c3eba5da0fb335562b2fa3d97b7a8ac9dc91f40a3abc70" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 8e8f1d0..b5686be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +whoami = "1.1.1" serde = { version = "1.0.125", features = ["derive"] } dirs = "3.0.1" confy = "0.4.0" diff --git a/src/config.rs b/src/config.rs index 8fdd6d1..3147ac4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,27 +1,63 @@ extern crate dirs; extern crate confy; extern crate serde; +extern crate whoami; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] pub struct MustConfig { pub wine_prefix_path: String, - pub wine_binary_path: String + pub wine_binary_path: String, + pub launcher_path: String, + pub studio_launcher_path: String +} + +pub fn get_must_default_dir() -> std::path::PathBuf { + let mut prefix_directory = dirs::data_dir().expect("err: conf: failed to get data directory"); + prefix_directory.push("must/"); + prefix_directory +} + +pub fn get_must_prefix() -> std::path::PathBuf { + let mut prefix_directory = get_must_default_dir(); + prefix_directory.push("prefix/"); + + prefix_directory } impl std::default::Default for MustConfig { fn default() -> Self { - let mut home = dirs::data_dir().expect("err: conf: failed to get home folder"); - home.push("must-prefix"); + let prefix_directory = get_must_prefix(); + + let mut launcher_path = get_must_default_dir(); + launcher_path.push("vendor/"); + launcher_path.push("dl/"); + launcher_path.push("RobloxPlayerLauncher.exe"); + + let mut studio_launcher_path = get_must_prefix(); + studio_launcher_path.push("drive_c/"); + studio_launcher_path.push("users/"); + studio_launcher_path.push(whoami::username()); + studio_launcher_path.push("Local Settings/"); + studio_launcher_path.push("Application Data/"); + studio_launcher_path.push("Roblox/"); + studio_launcher_path.push("Versions/"); + studio_launcher_path.push("RobloxStudioLauncherBeta.exe"); Self { - wine_prefix_path: home.to_str().expect("err: conf: failed to get home folder").to_string(), + studio_launcher_path: studio_launcher_path.to_str().expect("err: conf: failed to get studio launcher path").to_string(), + launcher_path: launcher_path.to_str().expect("err: conf: failed to get launcher path directory").to_string(), + wine_prefix_path: prefix_directory.to_str().expect("err: conf: failed to get data directory").to_string(), wine_binary_path: which::which("wine").expect("err: conf: failed to get wine binary path").to_str().unwrap().to_string() } } } pub fn get_config() -> Result { - let cfg = confy::load("must-config")?; + let mut cfg_path = get_must_default_dir(); + cfg_path.push("cfg/"); + cfg_path.push("must-config.toml"); + + let cfg = confy::load_path(cfg_path).expect("err: conf: failed to load config"); Ok(cfg) } \ No newline at end of file diff --git a/src/installctl.rs b/src/installctl.rs index 70348f1..0b2f49a 100644 --- a/src/installctl.rs +++ b/src/installctl.rs @@ -1,20 +1,92 @@ use std::fs::File; use std::io::Write; -pub fn download_game_binary() { +#[path = "config.rs"] +mod config; + +pub fn get_studio_launcher_binary() -> String { + let studio_launcher_path = config::get_config().expect("err: dl: failed to get config").studio_launcher_path; + studio_launcher_path +} + +pub fn get_game_launcher_binary() -> String { println!("info: dl: downloading game binary"); + let launcher_path = config::get_config().expect("err: dl: failed to get config").launcher_path; + if std::fs::metadata(&launcher_path).is_ok() { + println!("info: dl: game binary already exists"); + return launcher_path.to_string() + } + let body = reqwest::blocking::get("http://setup.roblox.com/Roblox.exe") .expect("err: dl: failed to send request") .bytes() - .expect("err: dl: failed to process data"); + .expect("err: dl: failed to process request data"); - let mut dir = std::path::PathBuf::new(); - dir.push("/tmp/"); - dir.push("RobloxPlayerLauncher.exe"); - let mut file = File::create(dir).expect("err: dl: failed to create output file inside temporary directory"); + let path = std::path::Path::new(&launcher_path); + let parent = path.parent().expect("err: dl: invalid path"); + std::fs::create_dir_all(parent).expect("err: dl: failed to create folder"); + let mut file = File::create(&launcher_path).expect("err: dl: failed to create output file"); file.write_all(&body).expect("err: dl: failed to write downloaded data to file"); - println!("info: dl: game binary downloaded to temporary directory"); + println!("info: dl: game binary downloaded"); + + launcher_path.to_string() +} + +fn install_association_file(scheme: &str, name: &str, content: &str) { + let mut player_desktop_path = dirs::data_dir().expect("err: inst: failed to get data directory"); + player_desktop_path.push("applications/"); + player_desktop_path.push(name); + + println!("info: installing handler {:?} for {:?} in {:?}", name, scheme, player_desktop_path.to_str()); + + let mut player_desktop_file = std::fs::OpenOptions::new() + .create(true) + .write(true) + .open(player_desktop_path) + .expect("err: inst: failed to create desktop file"); + + player_desktop_file.write_all(content.as_bytes()).expect("err: inst: failed to write desktop file contents"); + + std::process::Command::new("xdg-mime") + .arg("default") + .arg(name) + .arg(scheme) + .output() + .expect("err: inst: failed to set default protocol handler"); +} + +pub fn deploy_protocol_associations() { + println!("info: deploy: deploying protocol associations"); + + let player_desktop_file_contents = " + [Desktop Entry] + Version=0.0.1 + Type=Application + Name=Roblox Player (must launcher) + NoDisplay=true + OnlyShowIn=X-None; + Comment=The Roblox Game Client + Exec=/usr/bin/env must player %u + MimeType=x-scheme-handler/roblox-player; + Categories=Game; + StartupWMClass=RobloxPlayerLauncher.exe + "; + + let studio_desktop_file_contents = " + [Desktop Entry] + Version=0.0.1 + Type=Application + Name=Roblox Studio (must launcher) + Comment=The Roblox Sudio IDE + Exec=/usr/bin/env must studio %u%f + Categories=Development; + StartupWMClass=RobloxStudioLauncherBeta.exe + MimeType=x-scheme-handler/roblox-studio;application/x-roblox-rbxl;application/x-roblox-rbxlx + "; + + install_association_file("x-scheme-handler/roblox-player", "must-roblox-player.desktop", player_desktop_file_contents); + install_association_file("x-scheme-handler/roblox-studio", "must-roblox-studio.desktop", studio_desktop_file_contents); } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5730562..17b49b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,16 +8,33 @@ mod installctl; fn main() { let matches = App::new("must") - .version("0.1.0") - .author("hippoz ") - .about("Simplifies the process of installing and running Roblox on Linux using WINE") - .subcommand(SubCommand::with_name("up") - .about("Installs required software and prepares environment") - ) - .get_matches(); + .version("0.1.0") + .author("hippoz ") + .about("Simplifies the process of installing and running Roblox on Linux using WINE") + .subcommand(SubCommand::with_name("up") + .about("Installs required software and prepares environment") + ) + .subcommand(SubCommand::with_name("player") + .about("Runs the game client") + .arg_from_usage(" The protocol joinstring") + ) + .subcommand(SubCommand::with_name("studio") + .about("Runs the studio") + .arg_from_usage(" The protocol joinstring") + ) + .get_matches(); - if let Some(matches) = matches.subcommand_matches("up") { - installctl::download_game_binary(); - winectl::wine_launch("/tmp/RobloxPlayerLauncher.exe"); + if let Some(_matches) = matches.subcommand_matches("up") { + println!("info: deploying must"); + installctl::deploy_protocol_associations(); + winectl::wine_launch(installctl::get_game_launcher_binary(), "".to_string()); + } else if let Some(matches) = matches.subcommand_matches("player") { + let joinstring = matches.value_of("JOINSTRING").expect("err: launcher: failed to get joinstring"); + println!("info: launching player"); + winectl::wine_launch(installctl::get_game_launcher_binary(), joinstring.to_string()) + } else if let Some(matches) = matches.subcommand_matches("studio") { + let joinstring = matches.value_of("JOINSTRING").expect("err: launcher: failed to get joinstring"); + println!("info: launching studio"); + winectl::wine_launch(installctl::get_studio_launcher_binary(), joinstring.to_string()) } } \ No newline at end of file diff --git a/src/winectl.rs b/src/winectl.rs index 5e3b27a..9f2e34f 100644 --- a/src/winectl.rs +++ b/src/winectl.rs @@ -1,19 +1,15 @@ -use std::process::Command; - #[path = "config.rs"] mod config; -pub fn wine_launch(program_path: &str) { +pub fn wine_launch(program_path: String, arg: String) { println!("info: wine_launch: looking for wine binary"); let cfg = config::get_config().expect("err: wine_launch: failed to read config"); let wine_prefix = cfg.wine_prefix_path; let wine_binary = cfg.wine_binary_path; - println!("info: wine_launch: launching program {:?} with wine {:?} in {:?}", program_path, wine_binary.to_str(), wine_prefix.to_string()); + println!("info: wine_launch: launching program {:?} with wine {:?} in {:?}", program_path, wine_binary, wine_prefix.to_string()); std::fs::create_dir_all(&wine_prefix).expect("err: wine_launch: failed to create wineprefix"); - Command::new(wine_binary) - .env("WINEPREFIX", wine_prefix) - .arg(program_path) - .spawn() - .expect("err: wine_launch: failed to launch wine"); + let mut cmd = std::process::Command::new(wine_binary); + cmd.env("WINEPREFIX", wine_prefix).arg(program_path).arg(arg); + cmd.spawn().expect("err: wine_launch: failed to launch wine"); println!("info: wine_launch: done"); } \ No newline at end of file