A native webfishing installer for macos
1mod mods; 2mod patches; 3mod utils; 4 5use asky::Text; 6use async_std::fs::create_dir; 7use godot_pck::structs::PCK; 8use std::env::{current_exe, set_current_dir}; 9use std::fs::File; 10use std::io::{Read, Write}; 11use std::path::Path; 12use std::process::{exit, Command}; 13use std::time::Duration; 14use steamlocate::SteamDir; 15use sudo::RunningAs; 16use sysinfo::ProcessesToUpdate; 17 18static WEBFISHING_APPID: u32 = 3146520; 19 20async fn install_webfishing(location: &SteamDir) { 21 let steam_location = location.path(); 22 let acf_path = steam_location 23 .join("steamapps") 24 .join(format!("appmanifest_{}.acf", WEBFISHING_APPID)); 25 26 println!("Creating Webfishing ACF"); 27 File::create(acf_path) 28 .unwrap() 29 .write(include_str!("../res/webfishing.acf").as_bytes()) 30 .expect("could not write acf"); 31 32 println!("Waiting for steam to close"); 33 let mut system = sysinfo::System::new_all(); 34 while system.processes_by_name("steam_osx".as_ref()).count() > 0 { 35 println!("Steam is still running..."); 36 async_std::task::sleep(Duration::from_secs(5)).await; 37 system.refresh_processes(ProcessesToUpdate::All, true); 38 } 39 40 println!("Steam is now closed, please launch it back and wait for webfishing to install"); 41 42 while location.find_app(WEBFISHING_APPID).is_err() { 43 println!("Steam is not launched..."); 44 async_std::task::sleep(Duration::from_secs(10)).await; 45 } 46 47 println!("Steam launched, downloading webfishing"); 48 let download_path = steam_location 49 .join("steamapps") 50 .join("downloading") 51 .join(format!("{}", WEBFISHING_APPID)); 52 53 while Path::exists(download_path.as_path()) { 54 println!("Downloading webfishing..."); 55 async_std::task::sleep(Duration::from_secs(10)).await; 56 } 57} 58 59async fn download_godot_steam_template() { 60 println!("Downloading GodotSteam template..."); 61 let res = reqwest::get( 62 "https://codeberg.org/godotsteam/godotsteam/releases/download/v3.24/macos-g353-s159-gs324.zip", 63 ) 64 .await 65 .expect("Could not download godotsteam template"); 66 let body = res.bytes().await.expect("Could not read body"); 67 68 let mut file = File::create("build/godot_steam_template_324.zip") 69 .expect("Could not create godotsteam template"); 70 file.write_all(&body) 71 .expect("Could not write godotsteam template"); 72} 73 74async fn download_gd_decomp() { 75 println!("Download Godot Decompiler..."); 76 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"); 77 let body = res.bytes().await.expect("Could not read body"); 78 79 println!("Unzipping GodotSteam Decompiler..."); 80 let mut file = File::create("build/decompiler.zip").expect("Could not create decompiler"); 81 file.write_all(&body).expect("Could not write decompiler"); 82 83 Command::new("unzip") 84 .arg("decompiler.zip") 85 .current_dir("build") 86 .output() 87 .expect("Could not unzip godotsteam template"); 88} 89 90fn build_webfishing_macos(webfishing_path: &Path) { 91 let template_path = Path::new("build/osx_template.app"); 92 93 Command::new("rm") 94 .current_dir(template_path) 95 .arg("Contents/MacOS/godot_osx_debug.universal") 96 .output() 97 .expect("Could not remove delete godot_osx_debug.universal"); 98 99 Command::new("mv") 100 .current_dir(template_path) 101 .arg("Contents/MacOS/godot_osx_release.universal") 102 .arg("Contents/MacOS/webfishing") 103 .output() 104 .expect("Could not rename godot_osc_release.universal"); 105 106 let mut steamappid = File::create( 107 template_path 108 .join("Contents") 109 .join("MacOS") 110 .join("steam_appid.txt"), 111 ) 112 .expect("could not create steam_appid.txt file"); 113 steamappid 114 .write(include_str!("../res/steam_appid.txt").as_bytes()) 115 .expect("could not write steam_appid.txt"); 116 117 Command::new("cp") 118 .arg(webfishing_path.join("webfishing.exe")) 119 .arg( 120 template_path 121 .join("Contents") 122 .join("Resources") 123 .join("webfishing.pck"), 124 ) 125 .output() 126 .expect("Could not copy webfishing.exe"); 127 128 let mut info_plist = File::create(template_path.join("Contents").join("Info.plist")) 129 .expect("Could not open Info.plist"); 130 info_plist 131 .write_all(include_str!("../res/Info.plist").as_bytes()) 132 .expect("could not write Info.plist"); 133 134 Command::new("mv") 135 .arg(template_path) 136 .arg(Path::new("build/webfishing.app")) 137 .output() 138 .expect("Could not copy webfishing.app"); 139} 140 141fn sign_webfishing() { 142 Command::new("xattr") 143 .arg("-cr") 144 .arg("build/webfishing.app") 145 .output() 146 .expect("Could not execute xattr"); 147 148 Command::new("rm") 149 .arg("build/signing-step") 150 .output() 151 .expect("Could not remove signing-step file"); 152 153 println!("Webfishing is in the build folder !"); 154 155 Text::new("Press Enter to quit") 156 .prompt() 157 .expect("Could not confirm to quit"); 158 159 160} 161 162#[tokio::main] 163async fn main() { 164 if sudo::check() == RunningAs::Root && Path::new("build/signing-step").exists() { 165 sign_webfishing(); 166 exit(0); 167 } 168 169 set_current_dir( 170 current_exe() 171 .unwrap() 172 .parent() 173 .expect("Could not get current dir"), 174 ) 175 .expect("Could not set current dir"); 176 if !Path::exists("build".as_ref()) { 177 println!("Creating build folder"); 178 create_dir("build") 179 .await 180 .expect("could not create build folder"); 181 } 182 183 let location = SteamDir::locate().expect("could not locate steam directory"); 184 185 let webfishing = location.find_app(WEBFISHING_APPID); 186 if webfishing.is_err() || webfishing.unwrap().is_none() { 187 println!("Installing Webfishing"); 188 install_webfishing(&location).await; 189 } 190 191 let (app, library) = location.find_app(WEBFISHING_APPID).unwrap().unwrap(); 192 193 if !Path::exists("build/decompiler.zip".as_ref()) { 194 download_gd_decomp().await; 195 } 196 197 if !Path::exists("build/godot_steam_template_324.zip".as_ref()) { 198 download_godot_steam_template().await; 199 } 200 201 println!("Unzipping template 1/2"); 202 Command::new("unzip") 203 .arg("-o") 204 .arg("godot_steam_template_324.zip") 205 .current_dir("./build") 206 .output() 207 .expect("Could not unzip godot_steam_template_324.zip"); 208 209 Command::new("mv") 210 .arg("build/godot_steam_template_324/macos.zip") 211 .arg("build/macos.zip") 212 .current_dir("./build") 213 .output() 214 .expect("Could not copy godot_steam_template_324/macos.zip"); 215 216 println!("Unzipping template 2/2"); 217 Command::new("unzip") 218 .arg("-o") 219 .arg("macos.zip") 220 .current_dir("./build") 221 .output() 222 .expect("Could not unzip macos.zip"); 223 224 let binding = library.resolve_app_dir(&app); 225 let webfishing_path = binding.as_path(); 226 if !Path::exists(Path::new("build/webfishing.app")) { 227 build_webfishing_macos(webfishing_path); 228 } 229 230 let _ = create_dir("build/webfishing-export").await; 231 let mut bytes = vec![]; 232 File::open(webfishing_path.join("webfishing.exe")) 233 .unwrap() 234 .read_to_end(&mut bytes) 235 .unwrap(); 236 let mut pck = PCK::from_bytes(&*bytes).unwrap(); 237 238 patches::steam_network_patch::patch(&mut pck).await; 239 patches::options_menu_patch::patch(&mut pck).await; 240 mods::mods::process_mods(&mut pck); 241 242 let bytes = &pck.to_bytes(); 243 File::create("build/webfishing.app/Contents/Resources/webfishing.pck") 244 .unwrap() 245 .write(bytes) 246 .expect("Could not write to webfishing.pck"); 247 248 File::create("build/signing-step").expect("Could not create signing step file"); 249 250 if sudo::check() != RunningAs::Root { 251 println!("In order to sign the app, you need to be root"); 252 sudo::escalate_if_needed().expect("Could not escalate"); 253 exit(1); 254 } 255 256 sign_webfishing(); 257}