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}