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}