+35
-26
src/main.rs
+35
-26
src/main.rs
···
1
+
mod utils;
2
+
mod patches;
3
+
1
4
use std::fs::File;
2
-
use std::io::{Read, Write};
5
+
use std::io::{Write};
3
6
use std::path::Path;
4
7
use std::process::Command;
5
8
use std::time::Duration;
···
12
15
static WEBFISHING_APPID: u32 = 3146520;
13
16
14
17
15
-
// https://stackoverflow.com/a/54152901
16
-
fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T])
17
-
where
18
-
T: Clone + PartialEq,
19
-
{
20
-
for i in 0..=buf.len() - from.len() {
21
-
if buf[i..].starts_with(from) {
22
-
buf[i..(i + from.len())].clone_from_slice(to);
23
-
}
24
-
}
25
-
}
26
18
27
19
async fn install_webfishing(location: &SteamDir) {
28
20
let steam_location = location.path();
···
62
54
63
55
let mut file = File::create("build/godot_steam_template.zip").expect("Could not create godotsteam template");
64
56
file.write_all(&body).expect("Could not write godotsteam template");
57
+
}
58
+
59
+
async 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");
65
72
}
66
73
67
74
fn build_webfishing_macos(webfishing_path: &Path) {
···
94
101
.output().expect("Could not copy webfishing.app");
95
102
}
96
103
97
-
fn patch_webfishing_pck() {
98
-
println!("Patching webfishing PCK");
104
+
fn decomp_game() {
99
105
let webfishing_pck_path = Path::new("build").join("webfishing.app").join("Contents").join("Resources").join("webfishing.pck");
100
-
101
-
let mut webfishing_pck_read = File::open(&webfishing_pck_path).expect("Could not open webfishing.pck file");
102
-
let mut bytes = Vec::new();
103
-
webfishing_pck_read.read_to_end(&mut bytes).expect("Could not read webfishing.pck");
104
-
drop(webfishing_pck_read);
105
-
106
-
// PATCH
107
-
replace_slice(&mut bytes, "steam_id_remote".as_bytes(), "remote_steam_id".as_bytes());
108
-
109
-
let mut webfishing_pck_write = File::create(webfishing_pck_path).expect("Could not open webfishing.pck file");
110
-
webfishing_pck_write.write_all(bytes.as_slice()).expect("Could not write 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");
111
114
}
112
115
113
116
#[tokio::main]
···
126
129
}
127
130
128
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
+
}
129
136
130
137
if !Path::exists("build/godot_steam_template.zip".as_ref()) {
131
138
download_godot_steam_template().await;
···
158
165
}
159
166
160
167
if sudo::check() != RunningAs::Root {
161
-
patch_webfishing_pck();
168
+
decomp_game();
169
+
patches::steam_network_patch::patch().await;
170
+
patches::options_menu_patch::patch().await;
162
171
println!("Root permissions needed to sign webfishing");
163
172
}
164
173
+42
src/patches/steam_network_patch.rs
+42
src/patches/steam_network_patch.rs
···
1
+
use async_std::fs::File;
2
+
use async_std::io::{ReadExt, WriteExt};
3
+
use crate::utils::gd_utils::replace_slice;
4
+
5
+
const SCRIPT_PATH: &str = "build/webfishing-decomp/SteamNetwork.gd";
6
+
const COMPILED_PATH: &str = "build/webfishing-recomp/SteamNetwork.gdc";
7
+
const GAME_PCK: &str = "build/webfishing.app/Contents/Resources/webfishing.pck";
8
+
9
+
pub(crate) async fn patch() {
10
+
crate::utils::gd_utils::decomp_file("build/webfishing-export/Scenes/Singletons/SteamNetwork.gdc");
11
+
12
+
let mut script = File::open(SCRIPT_PATH).await.expect("Cannot open script");
13
+
let mut script_txt = String::new();
14
+
script.read_to_string(&mut script_txt).await.expect("Cannot read script");
15
+
drop(script);
16
+
17
+
let patched_script = script_txt.replace("steam_id_remote", "remote_steam_id");
18
+
let mut script = File::create(SCRIPT_PATH).await.expect("Cannot open script");
19
+
script.write_all(patched_script.as_bytes()).await.expect("Cannot write");
20
+
drop(script);
21
+
22
+
crate::utils::gd_utils::recomp_file(SCRIPT_PATH);
23
+
24
+
let mut compiled_script_bytes = Vec::new();
25
+
let mut compiled_script = File::open(COMPILED_PATH).await.expect("Cannot open script");
26
+
compiled_script.read_to_end(&mut compiled_script_bytes).await.expect("Cannot read");
27
+
drop(compiled_script);
28
+
29
+
let mut compiled_pck_bytes = Vec::new();
30
+
let mut compiled_pck = File::open(GAME_PCK).await.expect("Cannot open pck");
31
+
compiled_pck.read_to_end(&mut compiled_pck_bytes).await.expect("Cannot read");
32
+
drop(compiled_pck);
33
+
34
+
replace_slice(&mut compiled_pck_bytes,
35
+
&[0x47, 0x44, 0x53, 0x43, 0x0D, 0x00, 0x00, 0x00, 0x5B, 0x01, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00],
36
+
"GDSC".as_ref(),
37
+
&compiled_script_bytes
38
+
);
39
+
40
+
let mut compiled_pck = File::create(GAME_PCK).await.expect("Cannot open pck");
41
+
compiled_pck.write_all(compiled_pck_bytes.as_slice()).await.expect("Cannot write");
42
+
}
+45
src/utils/gd_utils.rs
+45
src/utils/gd_utils.rs
···
1
+
use std::process::Command;
2
+
3
+
const RE_TOOLS: &str = "build/Godot RE Tools.app/Contents/MacOS/Godot RE Tools";
4
+
5
+
// https://stackoverflow.com/a/54152901
6
+
pub(crate) fn replace_slice<T>(buf: &mut [T], from: &[T], to: &[T], replace_with: &[T])
7
+
where
8
+
T: Clone + PartialEq + From<u8>,
9
+
{
10
+
for i in 0..=buf.len() - replace_with.len() {
11
+
if buf[i..].starts_with(from) {
12
+
for j in (i + 1)..=buf.len() {
13
+
if buf[j..].starts_with(to) {
14
+
let mut vec = Vec::new();
15
+
vec.extend_from_slice(replace_with);
16
+
if replace_with.len() < j-i {
17
+
for _ in 0.. (j-i-replace_with.len()) {
18
+
vec.push(T::try_from(0).expect("Failed to convert from usize"));
19
+
}
20
+
}
21
+
buf[i..j].clone_from_slice(vec.as_slice());
22
+
break;
23
+
}
24
+
}
25
+
}
26
+
}
27
+
}
28
+
29
+
pub(crate) fn decomp_file(path: &str) {
30
+
Command::new(RE_TOOLS)
31
+
.arg("--headless")
32
+
.arg(format!("--decompile=\"{}\"", path))
33
+
.arg("--bytecode=3.5.0")
34
+
.arg("--output-dir=build/webfishing-decomp")
35
+
.output().expect(format!("Failed to decompile file: {}", path).as_str());
36
+
}
37
+
38
+
pub(crate) fn recomp_file(path: &str) {
39
+
Command::new(RE_TOOLS)
40
+
.arg("--headless")
41
+
.arg(format!("--compile=\"{}\"", path))
42
+
.arg("--bytecode=3.5.0")
43
+
.arg("--output-dir=build/webfishing-recomp")
44
+
.output().expect(format!("Failed to recompile file: {}", path).as_str());
45
+
}
+1
src/utils/mod.rs
+1
src/utils/mod.rs
···
1
+
pub mod gd_utils;