A native webfishing installer for macos
1mod utils; 2mod patches; 3 4use std::fs::File; 5use std::io::{Write}; 6use std::path::Path; 7use std::process::Command; 8use std::time::Duration; 9use asky::Confirm; 10use async_std::fs::create_dir; 11use steamlocate::SteamDir; 12use sudo::RunningAs; 13use sysinfo::ProcessesToUpdate; 14 15static WEBFISHING_APPID: u32 = 3146520; 16 17 18 19async fn install_webfishing(location: &SteamDir) { 20 let steam_location = location.path(); 21 let acf_path = steam_location.join("steamapps").join(format!("appmanifest_{}.acf", WEBFISHING_APPID)); 22 23 println!("Creating Webfishing ACF"); 24 File::create(acf_path).unwrap().write(include_str!("../res/webfishing.acf").as_bytes()).expect("could not write acf"); 25 26 println!("Waiting for steam to close"); 27 let mut system = sysinfo::System::new_all(); 28 while system.processes_by_name("steam_osx".as_ref()).count() > 0 { 29 println!("Steam is still running..."); 30 async_std::task::sleep(Duration::from_secs(5)).await; 31 system.refresh_processes(ProcessesToUpdate::All, true); 32 } 33 34 println!("Steam is now closed, please launch it back and wait for webfishing to install"); 35 36 while location.find_app(WEBFISHING_APPID).is_err() { 37 println!("Steam is not launched..."); 38 async_std::task::sleep(Duration::from_secs(10)).await; 39 } 40 41 println!("Steam launched, downloading webfishing"); 42 let download_path = steam_location.join("steamapps").join("downloading").join(format!("{}", WEBFISHING_APPID)); 43 44 while Path::exists(download_path.as_path()) { 45 println!("Downloading webfishing..."); 46 async_std::task::sleep(Duration::from_secs(10)).await; 47 } 48} 49 50async fn download_godot_steam_template() { 51 println!("Downloading GodotSteam template..."); 52 let res = reqwest::get("https://github.com/GodotSteam/GodotSteam/releases/download/v3.27/macos-g36-s160-gs327.zip").await.expect("Could not download godotsteam template"); 53 let body = res.bytes().await.expect("Could not read body"); 54 55 let mut file = File::create("build/godot_steam_template.zip").expect("Could not create godotsteam template"); 56 file.write_all(&body).expect("Could not write godotsteam template"); 57} 58 59async fn download_gd_decomp() { 60 println!("Download Godot Decompiler..."); 61 let res = reqwest::get("https://github.com/GDRETools/gdsdecomp/releases/download/v0.8.0/GDRE_tools-v0.8.0-macos.zip").await.expect("Could not download decompiler"); 62 let body = res.bytes().await.expect("Could not read body"); 63 64 println!("Unzipping GodotSteam Decompiler..."); 65 let mut file = File::create("build/decompiler.zip").expect("Could not create decompiler"); 66 file.write_all(&body).expect("Could not write decompiler"); 67 68 Command::new("unzip") 69 .arg("decompiler.zip") 70 .current_dir("build") 71 .output().expect("Could not unzip godotsteam template"); 72} 73 74fn build_webfishing_macos(webfishing_path: &Path) { 75 let template_path = Path::new("build/osx_template.app"); 76 Command::new("rm") 77 .current_dir(template_path) 78 .arg("Contents/MacOS/godot_osx_debug.64") 79 .output().expect("Could not remove delete godot_osx_debug.64"); 80 81 Command::new("mv") 82 .current_dir(template_path) 83 .arg("Contents/MacOS/godot_osx_release.64") 84 .arg("Contents/MacOS/webfishing") 85 .output().expect("Could not rename godot_osc_release.64"); 86 87 let mut steamappid = File::create(template_path.join("Contents").join("MacOS").join("steam_appid.txt")).expect("could not create steam_appid.txt file"); 88 steamappid.write(include_str!("../res/steam_appid.txt").as_bytes()).expect("could not write steam_appid.txt"); 89 90 Command::new("cp") 91 .arg(webfishing_path.join("webfishing.exe")) 92 .arg(template_path.join("Contents").join("Resources").join("webfishing.pck")) 93 .output().expect("Could not copy webfishing.exe"); 94 95 let mut info_plist = File::create(template_path.join("Contents").join("Info.plist")).expect("Could not open Info.plist"); 96 info_plist.write_all(include_str!("../res/Info.plist").as_bytes()).expect("could not write Info.plist"); 97 98 Command::new("mv") 99 .arg(template_path) 100 .arg(Path::new("build/webfishing.app")) 101 .output().expect("Could not copy webfishing.app"); 102} 103 104fn decomp_game() { 105 let webfishing_pck_path = Path::new("build").join("webfishing.app").join("Contents").join("Resources").join("webfishing.pck"); 106 let decomp_command = "build/Godot RE Tools.app/Contents/MacOS/Godot RE Tools"; 107 Command::new(decomp_command) 108 .arg("--headless") 109 .arg(format!("--extract={}", webfishing_pck_path.display())) 110 .arg("--include=\"*options_menu*\"") 111 .arg("--include=\"*SteamNetwork*\"") 112 .arg("--output-dir=build/webfishing-export") 113 .output().expect("Could not extract game"); 114} 115 116#[tokio::main] 117async fn main() { 118 if !Path::exists("build".as_ref()) { 119 println!("Creating build folder"); 120 create_dir("build").await.expect("could not create build folder"); 121 } 122 123 let location = SteamDir::locate().expect("could not locate steam directory"); 124 125 let webfishing = location.find_app(WEBFISHING_APPID); 126 if webfishing.is_err() || webfishing.unwrap().is_none() { 127 println!("Installing Webfishing"); 128 install_webfishing(&location).await; 129 } 130 131 let (app, library) = location.find_app(WEBFISHING_APPID).unwrap().unwrap(); 132 133 if !Path::exists("build/decompiler.zip".as_ref()) { 134 download_gd_decomp().await; 135 } 136 137 if !Path::exists("build/godot_steam_template.zip".as_ref()) { 138 download_godot_steam_template().await; 139 } 140 141 if !Path::exists("build/macos.zip".as_ref()) { 142 println!("Unzipping template"); 143 Command::new("unzip") 144 .arg("-o") 145 .arg("godot_steam_template.zip") 146 .current_dir("./build") 147 .output().expect("Could not unzip godot_steam_template.zip"); 148 } 149 150 if !Path::exists("build/osx_template.app".as_ref()) && !Path::exists("build/webfishing.app".as_ref()) { 151 println!("Unzipping template"); 152 Command::new("unzip") 153 .arg("-o") 154 .arg("macos.zip") 155 .current_dir("./build") 156 .output() 157 .expect("Could not unzip macos.zip"); 158 } 159 160 161 let binding = library.resolve_app_dir(&app); 162 let webfishing_path = binding.as_path(); 163 if !Path::exists(Path::new("build/webfishing.app")) { 164 build_webfishing_macos(webfishing_path); 165 } 166 167 if sudo::check() != RunningAs::Root { 168 decomp_game(); 169 patches::steam_network_patch::patch().await; 170 patches::options_menu_patch::patch().await; 171 println!("Root permissions needed to sign webfishing"); 172 } 173 174 sudo::escalate_if_needed().expect("Could not escalate to sign the app"); 175 176 Command::new("xattr") 177 .arg("-cr") 178 .arg("build/webfishing.app") 179 .output() 180 .expect("Could not execute xattr"); 181 182 if Confirm::new("Do you wanna install Webfishing in the app folder?").prompt().expect("Could not confirm to install the webfishing") { 183 Command::new("rsync") 184 .arg("-a") 185 .arg("build/webfishing.app") 186 .current_dir("/Applications/") 187 .output().expect("Could not execute rsync"); 188 189 Command::new("rm") 190 .arg("-r") 191 .arg("build/webfishing.app") 192 .output().expect("Could not remove webfishing.app"); 193 194 println!("Successfully installed webfishing !"); 195 } else { 196 println!("Webfishing is in the build folder !") 197 } 198}