+153
-5
Cargo.lock
+153
-5
Cargo.lock
···
18
18
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
19
19
20
20
[[package]]
21
+
name = "android-tzdata"
22
+
version = "0.1.1"
23
+
source = "registry+https://github.com/rust-lang/crates.io-index"
24
+
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
25
+
26
+
[[package]]
27
+
name = "android_system_properties"
28
+
version = "0.1.5"
29
+
source = "registry+https://github.com/rust-lang/crates.io-index"
30
+
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
31
+
dependencies = [
32
+
"libc",
33
+
]
34
+
35
+
[[package]]
21
36
name = "asky"
22
37
version = "0.1.1"
23
38
source = "registry+https://github.com/rust-lang/crates.io-index"
···
174
189
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
175
190
176
191
[[package]]
192
+
name = "binary-reader"
193
+
version = "0.4.5"
194
+
source = "registry+https://github.com/rust-lang/crates.io-index"
195
+
checksum = "1d173c51941d642588ed6a13d464617e3a9176b8fe00dc2de182434c36812a5e"
196
+
dependencies = [
197
+
"byteorder",
198
+
]
199
+
200
+
[[package]]
177
201
name = "bitflags"
178
202
version = "1.3.2"
179
203
source = "registry+https://github.com/rust-lang/crates.io-index"
···
214
238
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
215
239
216
240
[[package]]
241
+
name = "bytecount"
242
+
version = "0.6.8"
243
+
source = "registry+https://github.com/rust-lang/crates.io-index"
244
+
checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
245
+
246
+
[[package]]
247
+
name = "byteorder"
248
+
version = "1.5.0"
249
+
source = "registry+https://github.com/rust-lang/crates.io-index"
250
+
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
251
+
252
+
[[package]]
217
253
name = "bytes"
218
254
version = "1.9.0"
219
255
source = "registry+https://github.com/rust-lang/crates.io-index"
···
235
271
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
236
272
237
273
[[package]]
274
+
name = "chrono"
275
+
version = "0.4.39"
276
+
source = "registry+https://github.com/rust-lang/crates.io-index"
277
+
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
278
+
dependencies = [
279
+
"android-tzdata",
280
+
"iana-time-zone",
281
+
"js-sys",
282
+
"num-traits",
283
+
"wasm-bindgen",
284
+
"windows-targets 0.52.6",
285
+
]
286
+
287
+
[[package]]
238
288
name = "colored"
239
289
version = "2.2.0"
240
290
source = "registry+https://github.com/rust-lang/crates.io-index"
···
566
616
]
567
617
568
618
[[package]]
619
+
name = "godot_pck"
620
+
version = "0.1.0"
621
+
dependencies = [
622
+
"binary-reader",
623
+
"md5",
624
+
]
625
+
626
+
[[package]]
569
627
name = "h2"
570
628
version = "0.4.7"
571
629
source = "registry+https://github.com/rust-lang/crates.io-index"
···
718
776
]
719
777
720
778
[[package]]
779
+
name = "iana-time-zone"
780
+
version = "0.1.61"
781
+
source = "registry+https://github.com/rust-lang/crates.io-index"
782
+
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
783
+
dependencies = [
784
+
"android_system_properties",
785
+
"core-foundation-sys",
786
+
"iana-time-zone-haiku",
787
+
"js-sys",
788
+
"wasm-bindgen",
789
+
"windows-core 0.52.0",
790
+
]
791
+
792
+
[[package]]
793
+
name = "iana-time-zone-haiku"
794
+
version = "0.1.2"
795
+
source = "registry+https://github.com/rust-lang/crates.io-index"
796
+
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
797
+
dependencies = [
798
+
"cc",
799
+
]
800
+
801
+
[[package]]
721
802
name = "icu_collections"
722
803
version = "1.5.0"
723
804
source = "registry+https://github.com/rust-lang/crates.io-index"
···
963
1044
]
964
1045
965
1046
[[package]]
1047
+
name = "md5"
1048
+
version = "0.7.0"
1049
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1050
+
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
1051
+
1052
+
[[package]]
966
1053
name = "memchr"
967
1054
version = "2.7.4"
968
1055
source = "registry+https://github.com/rust-lang/crates.io-index"
···
973
1060
version = "0.3.17"
974
1061
source = "registry+https://github.com/rust-lang/crates.io-index"
975
1062
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1063
+
1064
+
[[package]]
1065
+
name = "minimal-lexical"
1066
+
version = "0.2.1"
1067
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1068
+
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
976
1069
977
1070
[[package]]
978
1071
name = "miniz_oxide"
···
1024
1117
]
1025
1118
1026
1119
[[package]]
1120
+
name = "nom"
1121
+
version = "7.1.3"
1122
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1123
+
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
1124
+
dependencies = [
1125
+
"memchr",
1126
+
"minimal-lexical",
1127
+
]
1128
+
1129
+
[[package]]
1130
+
name = "nom_locate"
1131
+
version = "4.2.0"
1132
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1133
+
checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3"
1134
+
dependencies = [
1135
+
"bytecount",
1136
+
"memchr",
1137
+
"nom",
1138
+
]
1139
+
1140
+
[[package]]
1027
1141
name = "ntapi"
1028
1142
version = "0.4.1"
1029
1143
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1033
1147
]
1034
1148
1035
1149
[[package]]
1150
+
name = "num-traits"
1151
+
version = "0.2.19"
1152
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1153
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
1154
+
dependencies = [
1155
+
"autocfg",
1156
+
]
1157
+
1158
+
[[package]]
1036
1159
name = "object"
1037
1160
version = "0.36.7"
1038
1161
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1118
1241
"redox_syscall",
1119
1242
"smallvec",
1120
1243
"windows-targets 0.52.6",
1244
+
]
1245
+
1246
+
[[package]]
1247
+
name = "patch-apply"
1248
+
version = "0.8.3"
1249
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1250
+
checksum = "7fe95476ec50a4e9b95ed12a4677ff5996aba4329bf2535fb21c49afaad20809"
1251
+
dependencies = [
1252
+
"chrono",
1253
+
"nom",
1254
+
"nom_locate",
1121
1255
]
1122
1256
1123
1257
[[package]]
···
1424
1558
1425
1559
[[package]]
1426
1560
name = "serde"
1427
-
version = "1.0.216"
1561
+
version = "1.0.217"
1428
1562
source = "registry+https://github.com/rust-lang/crates.io-index"
1429
-
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
1563
+
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
1430
1564
dependencies = [
1431
1565
"serde_derive",
1432
1566
]
1433
1567
1434
1568
[[package]]
1435
1569
name = "serde_derive"
1436
-
version = "1.0.216"
1570
+
version = "1.0.217"
1437
1571
source = "registry+https://github.com/rust-lang/crates.io-index"
1438
-
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
1572
+
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
1439
1573
dependencies = [
1440
1574
"proc-macro2",
1441
1575
"quote",
···
1963
2097
dependencies = [
1964
2098
"asky",
1965
2099
"async-std",
2100
+
"godot_pck",
2101
+
"patch-apply",
1966
2102
"reqwest",
2103
+
"serde",
2104
+
"serde_derive",
2105
+
"serde_json",
1967
2106
"steamlocate",
1968
2107
"sudo",
1969
2108
"sysinfo",
···
1998
2137
source = "registry+https://github.com/rust-lang/crates.io-index"
1999
2138
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
2000
2139
dependencies = [
2001
-
"windows-core",
2140
+
"windows-core 0.57.0",
2141
+
"windows-targets 0.52.6",
2142
+
]
2143
+
2144
+
[[package]]
2145
+
name = "windows-core"
2146
+
version = "0.52.0"
2147
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2148
+
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
2149
+
dependencies = [
2002
2150
"windows-targets 0.52.6",
2003
2151
]
2004
2152
+8
-1
Cargo.toml
+8
-1
Cargo.toml
···
10
10
sysinfo = "0.33.0"
11
11
async-std = "1.13.0"
12
12
sudo = "0.6.0"
13
-
asky = "0.1.1"
13
+
asky = "0.1.1"
14
+
patch-apply = "0.8.3"
15
+
serde = { version = "1.0.217" }
16
+
serde_json = { version = "1.0.134" }
17
+
serde_derive = { version = "1.0.217" }
18
+
19
+
20
+
godot_pck = {path = "./src/godot_pck"}
+70
README.md
+70
README.md
···
1
+
# Webfishing MacOS Installer
2
+
3
+
An installer for a native version of webfishing on macos
4
+
5
+
## Why??
6
+
7
+
Webfishing is a game I love, however, it is poorely running on Whisky for macOS due to some unknown reason.
8
+
9
+
While searching for some solutions I stumbled across a [blog article](https://mae.wtf/blog/28102024-webfishing-mac) made by [@vimaexd](https://github.com/vimaexd),
10
+
it worked well in singleplayer, however as of now the multiplayer no longer works and this is why I decided to make this installer after figuring out how to patch this issue.
11
+
12
+
13
+
## Prerequisite
14
+
- Root privileges
15
+
- Steam
16
+
17
+
## Installation
18
+
19
+
To run the app, you can double-click on it, doing so will put the build folder inside your home directory `/Users/[you]/build`
20
+
21
+
You can also run it from the command line and doing so will put the build folder inside the current working directory.
22
+
23
+
## Implemented Patches
24
+
25
+
- renaming `steam_id_remote` dictionnary key to `remote_steam_id` to fix network spam detection that resulted in timeouts
26
+
- prevent the game from crashing when saving the options by not setting any values to `OS.windowed_borderless` because setting a value to it crashes the game somehow
27
+
28
+
29
+
## How to install a mod?
30
+
31
+
When running the software for the first time and building webfishing, you'll notice that a `mods` folder has appeared in the folder where the installer is.
32
+
33
+
In order to install a mod, just copy a mod folder in it, a mod folder has a `manifest.json` file in it.
34
+
35
+
After that, run the installer again ! It will tell you in the terminal if the mod is installed or if something went wrong.
36
+
37
+
Here's a small mod list : [link to the mod list](modlist.md)
38
+
39
+
## How to make a mod?
40
+
41
+
As you can see in the `example_mods` folder, a mod has typically two folders and a single `manifest.json` file having the following structure:
42
+
```jsonc
43
+
{
44
+
"name": "Ship Mod", // Mod name
45
+
"author": "Estym", // Author
46
+
47
+
"pck_info": { // (Optional)
48
+
"directory": "pck", // Relative folder path to where the mod resources are
49
+
"resource_prefix": "res://Mods/Ship/" // Resource path prefix for the mod resources
50
+
},
51
+
52
+
"patches": [ // Array of patches
53
+
{
54
+
"resource": "res://Scenes/Entities/Player/player.gdc", // Resource to patch
55
+
"patch_file": "patch/player.patch" // relative file path to the patch file
56
+
}
57
+
],
58
+
59
+
"deps": [] // Dependencies for the mod (Optional)
60
+
}
61
+
```
62
+
63
+
### Notes:
64
+
- The patch files are made by using `$ git diff [original_file] [modded_file] > file.patch`
65
+
66
+
## Credits
67
+
68
+
[@vimaexd](https://github.com/vimaexd) for their blog post !
69
+
70
+
[Godot RE Tools](https://github.com/GDRETools/gdsdecomp) for the amazing tooling that allows me to patch the game !
+15
example_mods/ship/manifest.json
+15
example_mods/ship/manifest.json
···
1
+
{
2
+
"name": "Ship Mod",
3
+
"author": "Estym",
4
+
"pck_info": {
5
+
"directory": "pck",
6
+
"resource_prefix": "res://Mods/Ship/"
7
+
},
8
+
"patches": [
9
+
{
10
+
"resource": "res://Scenes/Entities/Player/player.gdc",
11
+
"patch_file": "patch/player.patch"
12
+
}
13
+
],
14
+
"deps": []
15
+
}
+49
example_mods/ship/patch/player.patch
+49
example_mods/ship/patch/player.patch
···
1
+
diff --git a/player.gd b/player copie.gd
2
+
index c84af29..4cb41f9 100644
3
+
--- a/player.gd
4
+
+++ b/player copie.gd
5
+
@@ -23,6 +23,7 @@ const PARTICLE_DATA = {
6
+
"music": preload("res://Scenes/Particles/music_particle.tscn"),
7
+
"kiss": preload("res://Scenes/Particles/kiss.tscn"),
8
+
}
9
+
+var ship_mod_instance = preload("res://Mods/Ship/ship.gd").new()
10
+
11
+
export (NodePath) var hand_sprite_node
12
+
export (NodePath) var hand_bone_node
13
+
@@ -31,6 +32,7 @@ export var NPC_cosmetics = {"species": "species_cat", "pattern": "pattern_none"
14
+
export var NPC_name = "NPC Test"
15
+
export var NPC_title = "npc title here"
16
+
17
+
+
18
+
var camera_zoom = 5.0
19
+
20
+
var direction = Vector3()
21
+
@@ -531,6 +533,10 @@ func _get_input():
22
+
23
+
mouse_look = false
24
+
25
+
+ if ship_mod_instance.is_sitting_on_ship(self):
26
+
+ ship_mod_instance.process_ship(self, get_world())
27
+
+ return
28
+
+
29
+
if sitting: return
30
+
31
+
if Input.is_action_pressed("move_forward"): direction -= cam_base.transform.basis.z
32
+
@@ -1389,17 +1395,6 @@ func _create_prop(ref, offset = Vector3(0, 1, 0), restrict_to_one = false):
33
+
PlayerData.emit_signal("_prop_update")
34
+
return false
35
+
36
+
-
37
+
- if $detection_zones / prop_detect.get_overlapping_bodies().size() > 0 or not is_on_floor() or not $detection_zones / prop_ray.is_colliding():
38
+
- PlayerData._send_notification("invalid prop placement", 1)
39
+
- return false
40
+
-
41
+
-
42
+
- if prop_ids.size() > 4:
43
+
- PlayerData._send_notification("prop limit reached", 1)
44
+
- return false
45
+
-
46
+
-
47
+
var item = PlayerData._find_item_code(ref)
48
+
var data = Globals.item_data[item["id"]]["file"]
49
+
var ver_offset = Vector3(0, 1.0, 0) * (1.0 - player_scale)
+42
example_mods/ship/pck/ship.gd
+42
example_mods/ship/pck/ship.gd
···
1
+
extends Node
2
+
3
+
var ship
4
+
# Declare member variables here. Examples:
5
+
# var a = 2
6
+
# var b = "text"
7
+
var entity_node
8
+
9
+
# Called when the node enters the scene tree for the first time.
10
+
func _ready():
11
+
12
+
pass # Replace with function body.
13
+
14
+
func process_ship(player, world):
15
+
var entities = player.get_parent()
16
+
for i in entities.get_children():
17
+
if i.actor_type == "table":
18
+
if Input.is_key_pressed(KEY_SHIFT):
19
+
i.translation.y += 0.05
20
+
i.packet_cooldown = 0
21
+
i._network_share()
22
+
elif Input.is_key_pressed(KEY_CONTROL):
23
+
i.translation.y -= 0.05
24
+
i.packet_cooldown = 0
25
+
i._network_share()
26
+
27
+
28
+
func get_prop_by_ref(ref):
29
+
for obj in PlayerData.inventory:
30
+
if obj.ref == ref:
31
+
return obj
32
+
return null
33
+
34
+
func is_sitting_on_ship(player):
35
+
36
+
for prop in PlayerData.props_placed:
37
+
var obj = get_prop_by_ref(prop.ref)
38
+
if obj != null && obj.id == 'prop_table':
39
+
ship = obj
40
+
return player.sitting
41
+
return false;
42
+
+7
modlist.md
+7
modlist.md
+32
src/godot_pck/Cargo.lock
+32
src/godot_pck/Cargo.lock
···
1
+
# This file is automatically @generated by Cargo.
2
+
# It is not intended for manual editing.
3
+
version = 4
4
+
5
+
[[package]]
6
+
name = "binary-reader"
7
+
version = "0.4.5"
8
+
source = "registry+https://github.com/rust-lang/crates.io-index"
9
+
checksum = "1d173c51941d642588ed6a13d464617e3a9176b8fe00dc2de182434c36812a5e"
10
+
dependencies = [
11
+
"byteorder",
12
+
]
13
+
14
+
[[package]]
15
+
name = "byteorder"
16
+
version = "1.5.0"
17
+
source = "registry+https://github.com/rust-lang/crates.io-index"
18
+
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
19
+
20
+
[[package]]
21
+
name = "godot_pck"
22
+
version = "0.1.0"
23
+
dependencies = [
24
+
"binary-reader",
25
+
"md5",
26
+
]
27
+
28
+
[[package]]
29
+
name = "md5"
30
+
version = "0.7.0"
31
+
source = "registry+https://github.com/rust-lang/crates.io-index"
32
+
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
+8
src/godot_pck/Cargo.toml
+8
src/godot_pck/Cargo.toml
+1
src/godot_pck/src/lib.rs
+1
src/godot_pck/src/lib.rs
···
1
+
pub mod structs;
+159
src/godot_pck/src/structs.rs
+159
src/godot_pck/src/structs.rs
···
1
+
use binary_reader::{BinaryReader, Endian};
2
+
3
+
#[derive(Debug)]
4
+
pub struct PCK {
5
+
version: u8,
6
+
major_version: u8,
7
+
minor_version: u8,
8
+
patch_version: u8,
9
+
files: Vec<PckFile>,
10
+
}
11
+
12
+
#[derive(Debug, Clone)]
13
+
pub struct PckFile {
14
+
path: String,
15
+
content: Vec<u8>,
16
+
md5: [u8; 16],
17
+
}
18
+
19
+
impl PCK {
20
+
/// Converts pck file bytes to a PCK struct
21
+
pub fn from_bytes(bytes: &[u8]) -> Result<PCK, &str> {
22
+
let chunked_start = bytes.chunks(16).position(|chunk| chunk.starts_with("GDPC".as_ref()));
23
+
if let None = chunked_start {
24
+
return Err("Invalid PCK");
25
+
};
26
+
27
+
let start_offset = chunked_start.unwrap() * 16;
28
+
let pck: Vec<u8> = bytes.to_vec().into_iter().skip(start_offset).collect();
29
+
let mut pck_reader = BinaryReader::from_vec(&pck);
30
+
pck_reader.set_endian(Endian::Little);
31
+
32
+
let _magic = pck_reader.read_u32().unwrap();
33
+
let version = pck_reader.read_u32().unwrap();
34
+
let major_version = pck_reader.read_u32().unwrap();
35
+
let minor_version = pck_reader.read_u32().unwrap();
36
+
let patch_version = pck_reader.read_u32().unwrap();
37
+
38
+
// Skip unused
39
+
let _ = pck_reader.read(64);
40
+
41
+
let num_files = pck_reader.read_u32().unwrap();
42
+
let mut files = vec![];
43
+
for _ in 0..num_files {
44
+
files.push(PckFile::from_bytes(&mut pck_reader, bytes));
45
+
}
46
+
47
+
Ok(PCK {
48
+
version: version as u8,
49
+
major_version: major_version as u8,
50
+
minor_version: minor_version as u8,
51
+
patch_version: patch_version as u8,
52
+
files
53
+
})
54
+
}
55
+
56
+
/// Converts a PCK struct to the byte vector representing a pck file
57
+
pub fn to_bytes(&self) -> Vec<u8> {
58
+
let mut bytes = vec![];
59
+
bytes.append(&mut "GDPC".as_bytes().to_vec());
60
+
bytes.append((self.version as u32).to_le_bytes().to_vec().as_mut());
61
+
bytes.append((self.major_version as u32).to_le_bytes().to_vec().as_mut());
62
+
bytes.append((self.minor_version as u32).to_le_bytes().to_vec().as_mut());
63
+
bytes.append((self.patch_version as u32).to_le_bytes().to_vec().as_mut());
64
+
bytes.append([0u8; 16*4].to_vec().as_mut());
65
+
bytes.append((self.files.len() as u32).to_le_bytes().to_vec().as_mut());
66
+
67
+
let mut file_offset = bytes.len()
68
+
+ self.files.len() * (4 + 8 + 8 + 16)
69
+
+ self.files.iter().map(|x| x.path.len() + (4 - (x.path.len() % 4))).sum::<usize>();
70
+
71
+
if file_offset % 16 != 0 {
72
+
file_offset = file_offset + (16 - (file_offset % 16));
73
+
}
74
+
75
+
let file_base_offset = file_offset;
76
+
77
+
let mut content : Vec<u8> = vec![];
78
+
79
+
for file in &self.files {
80
+
content.extend(&file.content);
81
+
82
+
let mut padded_content_len = file.content.len();
83
+
if padded_content_len % 16 != 0 {
84
+
padded_content_len = padded_content_len + (16 - (padded_content_len % 16));
85
+
}
86
+
87
+
for _ in file.content.len()..padded_content_len {
88
+
content.push(0);
89
+
}
90
+
91
+
let padded_length = file.path.len() + (4 - (file.path.len() % 4));
92
+
93
+
bytes.append((padded_length as u32).to_le_bytes().to_vec().as_mut());
94
+
bytes.append(file.path.as_bytes().to_vec().as_mut());
95
+
for _ in file.path.len()..padded_length {
96
+
bytes.push(0);
97
+
}
98
+
bytes.append((file_offset as u64).to_le_bytes().to_vec().as_mut());
99
+
bytes.append((padded_content_len as u64).to_le_bytes().to_vec().as_mut());
100
+
bytes.extend(file.md5);
101
+
102
+
file_offset += padded_content_len;
103
+
}
104
+
105
+
for _ in bytes.len()..file_base_offset {
106
+
bytes.push(0);
107
+
}
108
+
109
+
bytes.extend(content);
110
+
bytes
111
+
}
112
+
113
+
pub fn get_file_by_path(&self, path: &str) -> Option<&PckFile> {
114
+
self.files.iter().find(|x| x.path == path)
115
+
}
116
+
117
+
pub fn get_file_by_path_mut(&mut self, path: &str) -> Option<&mut PckFile> {
118
+
self.files.iter_mut().find(|x| x.path == path)
119
+
}
120
+
121
+
pub fn add_file(&mut self, new_file: PckFile) {
122
+
self.files.retain(|x| x.path != new_file.path);
123
+
self.files.push(new_file);
124
+
}
125
+
}
126
+
127
+
impl PckFile {
128
+
pub fn from_bytes(pck_reader: &mut BinaryReader, file_bytes: &[u8]) -> PckFile {
129
+
let path_length = pck_reader.read_u32().unwrap();
130
+
let path_bytes= pck_reader.read(path_length as usize).unwrap();
131
+
let path: String = String::from_utf8_lossy(path_bytes).replace("\0", "").to_string();
132
+
let offset = pck_reader.read_u64().unwrap();
133
+
let size = pck_reader.read_u64().unwrap();
134
+
let md5 = pck_reader.read(16).unwrap();
135
+
let content: Vec<u8> = file_bytes.iter().skip(offset as usize).take(size as usize).cloned().collect();
136
+
PckFile {
137
+
path,
138
+
content,
139
+
md5: <[u8; 16]>::try_from(md5).unwrap(),
140
+
}
141
+
}
142
+
143
+
pub fn new_file(path: String, content: Vec<u8>) -> PckFile {
144
+
PckFile {
145
+
path,
146
+
md5: *md5::compute(&content),
147
+
content
148
+
}
149
+
}
150
+
151
+
pub fn get_content(&self) -> &[u8] {
152
+
&self.content
153
+
}
154
+
155
+
pub fn set_content(&mut self, content: Vec<u8>) {
156
+
self.md5 = *md5::compute(&content);
157
+
self.content = content;
158
+
}
159
+
}
+156
-88
src/main.rs
+156
-88
src/main.rs
···
1
+
mod mods;
2
+
mod patches;
3
+
mod utils;
4
+
5
+
use asky::Text;
6
+
use async_std::fs::create_dir;
7
+
use godot_pck::structs::PCK;
8
+
use std::env::{current_exe, set_current_dir};
1
9
use std::fs::File;
2
10
use std::io::{Read, Write};
3
11
use std::path::Path;
4
-
use std::process::Command;
12
+
use std::process::{exit, Command};
5
13
use std::time::Duration;
6
-
use asky::Confirm;
7
-
use async_std::fs::create_dir;
8
14
use steamlocate::SteamDir;
9
15
use sudo::RunningAs;
10
16
use sysinfo::ProcessesToUpdate;
11
17
12
18
static WEBFISHING_APPID: u32 = 3146520;
13
19
14
-
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
-
27
20
async fn install_webfishing(location: &SteamDir) {
28
21
let steam_location = location.path();
29
-
let acf_path = steam_location.join("steamapps").join(format!("appmanifest_{}.acf", WEBFISHING_APPID));
22
+
let acf_path = steam_location
23
+
.join("steamapps")
24
+
.join(format!("appmanifest_{}.acf", WEBFISHING_APPID));
30
25
31
26
println!("Creating Webfishing ACF");
32
-
File::create(acf_path).unwrap().write(include_str!("../res/webfishing.acf").as_bytes()).expect("could not write acf");
27
+
File::create(acf_path)
28
+
.unwrap()
29
+
.write(include_str!("../res/webfishing.acf").as_bytes())
30
+
.expect("could not write acf");
33
31
34
32
println!("Waiting for steam to close");
35
33
let mut system = sysinfo::System::new_all();
···
47
45
}
48
46
49
47
println!("Steam launched, downloading webfishing");
50
-
let download_path = steam_location.join("steamapps").join("downloading").join(format!("{}", WEBFISHING_APPID));
48
+
let download_path = steam_location
49
+
.join("steamapps")
50
+
.join("downloading")
51
+
.join(format!("{}", WEBFISHING_APPID));
51
52
52
53
while Path::exists(download_path.as_path()) {
53
54
println!("Downloading webfishing...");
···
57
58
58
59
async fn download_godot_steam_template() {
59
60
println!("Downloading GodotSteam template...");
60
-
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");
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");
61
66
let body = res.bytes().await.expect("Could not read body");
62
67
63
-
let mut file = File::create("build/godot_steam_template.zip").expect("Could not create godotsteam template");
64
-
file.write_all(&body).expect("Could not write godotsteam template");
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
+
74
+
async 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");
65
88
}
66
89
67
90
fn build_webfishing_macos(webfishing_path: &Path) {
68
91
let template_path = Path::new("build/osx_template.app");
92
+
69
93
Command::new("rm")
70
94
.current_dir(template_path)
71
-
.arg("Contents/MacOS/godot_osx_debug.64")
72
-
.output().expect("Could not remove delete godot_osx_debug.64");
95
+
.arg("Contents/MacOS/godot_osx_debug.universal")
96
+
.output()
97
+
.expect("Could not remove delete godot_osx_debug.universal");
73
98
74
99
Command::new("mv")
75
100
.current_dir(template_path)
76
-
.arg("Contents/MacOS/godot_osx_release.64")
101
+
.arg("Contents/MacOS/godot_osx_release.universal")
77
102
.arg("Contents/MacOS/webfishing")
78
-
.output().expect("Could not rename godot_osc_release.64");
103
+
.output()
104
+
.expect("Could not rename godot_osc_release.universal");
79
105
80
-
let mut steamappid = File::create(template_path.join("Contents").join("MacOS").join("steam_appid.txt")).expect("could not create steam_appid.txt file");
81
-
steamappid.write(include_str!("../res/steam_appid.txt").as_bytes()).expect("could not write steam_appid.txt");
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");
82
116
83
117
Command::new("cp")
84
118
.arg(webfishing_path.join("webfishing.exe"))
85
-
.arg(template_path.join("Contents").join("Resources").join("webfishing.pck"))
86
-
.output().expect("Could not copy 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");
87
127
88
-
let mut info_plist = File::create(template_path.join("Contents").join("Info.plist")).expect("Could not open Info.plist");
89
-
info_plist.write_all(include_str!("../res/Info.plist").as_bytes()).expect("could not write Info.plist");
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");
90
133
91
134
Command::new("mv")
92
135
.arg(template_path)
93
136
.arg(Path::new("build/webfishing.app"))
94
-
.output().expect("Could not copy webfishing.app");
137
+
.output()
138
+
.expect("Could not copy webfishing.app");
95
139
}
96
140
97
-
fn patch_webfishing_pck() {
98
-
println!("Patching webfishing PCK");
99
-
let webfishing_pck_path = Path::new("build").join("webfishing.app").join("Contents").join("Resources").join("webfishing.pck");
141
+
fn sign_webfishing() {
142
+
Command::new("xattr")
143
+
.arg("-cr")
144
+
.arg("build/webfishing.app")
145
+
.output()
146
+
.expect("Could not execute xattr");
100
147
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);
148
+
Command::new("rm")
149
+
.arg("build/signing-step")
150
+
.output()
151
+
.expect("Could not remove signing-step file");
105
152
106
-
// PATCH
107
-
replace_slice(&mut bytes, "steam_id_remote".as_bytes(), "remote_steam_id".as_bytes());
153
+
println!("Webfishing is in the build folder !");
108
154
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");
155
+
Text::new("Press Enter to quit")
156
+
.prompt()
157
+
.expect("Could not confirm to quit");
158
+
159
+
111
160
}
112
161
113
162
#[tokio::main]
114
163
async 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");
115
176
if !Path::exists("build".as_ref()) {
116
177
println!("Creating build folder");
117
-
create_dir("build").await.expect("could not create build folder");
178
+
create_dir("build")
179
+
.await
180
+
.expect("could not create build folder");
118
181
}
119
182
120
183
let location = SteamDir::locate().expect("could not locate steam directory");
···
125
188
install_webfishing(&location).await;
126
189
}
127
190
128
-
let (app, library) = location.find_app(WEBFISHING_APPID).unwrap().unwrap();
191
+
let (app, library) = location.find_app(WEBFISHING_APPID).unwrap().unwrap();
129
192
130
-
if !Path::exists("build/godot_steam_template.zip".as_ref()) {
131
-
download_godot_steam_template().await;
193
+
if !Path::exists("build/decompiler.zip".as_ref()) {
194
+
download_gd_decomp().await;
132
195
}
133
196
134
-
if !Path::exists("build/macos.zip".as_ref()) {
135
-
println!("Unzipping template");
136
-
Command::new("unzip")
137
-
.arg("-o")
138
-
.arg("godot_steam_template.zip")
139
-
.current_dir("./build")
140
-
.output().expect("Could not unzip godot_steam_template.zip");
197
+
if !Path::exists("build/godot_steam_template_324.zip".as_ref()) {
198
+
download_godot_steam_template().await;
141
199
}
142
200
143
-
if !Path::exists("build/osx_template.app".as_ref()) && !Path::exists("build/webfishing.app".as_ref()) {
144
-
println!("Unzipping template");
145
-
Command::new("unzip")
146
-
.arg("-o")
147
-
.arg("macos.zip")
148
-
.current_dir("./build")
149
-
.output()
150
-
.expect("Could not unzip macos.zip");
151
-
}
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");
152
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");
153
223
154
224
let binding = library.resolve_app_dir(&app);
155
225
let webfishing_path = binding.as_path();
···
157
227
build_webfishing_macos(webfishing_path);
158
228
}
159
229
160
-
if sudo::check() != RunningAs::Root {
161
-
patch_webfishing_pck();
162
-
println!("Root permissions needed to sign webfishing");
163
-
}
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();
164
237
165
-
sudo::escalate_if_needed().expect("Could not escalate to sign the app");
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);
166
241
167
-
Command::new("xattr")
168
-
.arg("-cr")
169
-
.arg("build/webfishing.app")
170
-
.output()
171
-
.expect("Could not execute xattr");
172
-
173
-
if Confirm::new("Do you wanna install Webfishing in the app folder?").prompt().expect("Could not confirm to install the webfishing") {
174
-
Command::new("rsync")
175
-
.arg("-a")
176
-
.arg("build/webfishing.app")
177
-
.current_dir("/Applications/")
178
-
.output().expect("Could not execute rsync");
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");
179
247
180
-
Command::new("rm")
181
-
.arg("-r")
182
-
.arg("build/webfishing.app")
183
-
.output().expect("Could not remove webfishing.app");
248
+
File::create("build/signing-step").expect("Could not create signing step file");
184
249
185
-
println!("Successfully installed webfishing !");
186
-
} else {
187
-
println!("Webfishing is in the build folder !")
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);
188
254
}
255
+
256
+
sign_webfishing();
189
257
}
+154
src/mods/mods.rs
+154
src/mods/mods.rs
···
1
+
use std::fs;
2
+
use std::fs::{read_dir, File};
3
+
use std::io::{Read, Write};
4
+
use std::path::{Path, PathBuf};
5
+
use godot_pck::structs::{PckFile, PCK};
6
+
use crate::mods::structs::{Manifest, Patch, PckInfo};
7
+
use crate::utils;
8
+
9
+
fn read_manifests() -> Vec<Manifest> {
10
+
let mut manifests = Vec::new();
11
+
let mods_dir = fs::read_dir("mods").expect("Cannot read mods directory");
12
+
for mod_dir in mods_dir {
13
+
let mod_dir = mod_dir.expect("Failed to read mod directory");
14
+
if !mod_dir.path().is_dir() {continue}
15
+
16
+
if !mod_dir.path().join("manifest.json").exists() {
17
+
println!("No manifest found in {:?}", mod_dir.path());
18
+
continue;
19
+
}
20
+
21
+
let mut manifest: Manifest = serde_json::from_reader(fs::File::open(mod_dir.path().join("manifest.json")).unwrap()).unwrap();
22
+
manifest.path = mod_dir.path().display().to_string();
23
+
24
+
manifests.push(manifest);
25
+
}
26
+
manifests
27
+
}
28
+
29
+
fn check_mods(manifests: Vec<Manifest>) -> Vec<Manifest> {
30
+
let mod_names: Vec<String> = manifests.iter().map(|m| m.get_name()).collect();
31
+
32
+
manifests.into_iter().filter(|manifest: &Manifest| {
33
+
let mut res = true;
34
+
println!("Checking {}", manifest.get_name());
35
+
for mod_name in manifest.get_dependencies() {
36
+
if !mod_names.contains(&mod_name) {
37
+
println!("\t- missing dependency {}", mod_name);
38
+
res = false;
39
+
}
40
+
}
41
+
res
42
+
}).collect::<Vec<Manifest>>()
43
+
}
44
+
45
+
fn apply_patch(manifest: &Manifest, pck: &mut PCK, patch: &&Patch) {
46
+
let resource_path = patch.get_resource();
47
+
let resource_path = resource_path.as_str();
48
+
let resource = pck.get_file_by_path_mut(resource_path).expect(&format!("Could not patch resource {}", resource_path));
49
+
let resource_name = resource_path.split("res://").last().expect("could not remove res://").split("/").last().expect("Could not get last part of resource");
50
+
if resource_path.ends_with(".gdc") {
51
+
let mut resource_exported = File::create(format!("build/webfishing-export/{}", resource_name)).expect(&format!("Could not create mod resource {}", resource_name));
52
+
resource_exported.write_all(resource.get_content()).expect(&format!("Could not write mod resource {}", resource_name));
53
+
drop(resource_exported);
54
+
utils::gd_utils::decomp_script(&format!("build/webfishing-export/{}", resource_name))
55
+
} else {
56
+
let mut resource_exported = File::create(format!("build/webfishing-decomp/{}", resource_name)).expect(&format!("Could not create mod resource {}", resource_name));
57
+
resource_exported.write_all(resource.get_content()).expect(&format!("Could not write mod resource {}", resource_name));
58
+
drop(resource_exported);
59
+
}
60
+
61
+
let mut patch_file = File::open(format!("{}/{}", manifest.path, patch.get_patch_file())).expect(&format!("Could not open patch file {}", patch.get_patch_file()));
62
+
let mut patch_file_content = String::new();
63
+
patch_file.read_to_string(&mut patch_file_content).expect(&format!("Could not read patch file {}", patch.get_patch_file()));
64
+
drop(patch_file);
65
+
66
+
let patch_struct = patch_apply::Patch::from_single(patch_file_content.as_str()).expect(&format!("Could not create patch {}", patch.get_patch_file()));
67
+
68
+
let mut file_to_patch = File::open(format!("build/webfishing-decomp/{}", resource_name.replace(".gdc", ".gd"))).expect(&format!("Could not open file {}", resource_name));
69
+
let mut file_to_patch_content = String::new();
70
+
file_to_patch.read_to_string(&mut file_to_patch_content).expect(&format!("Could not read patch file {}", resource_name));
71
+
drop(file_to_patch);
72
+
73
+
let patched_content = patch_apply::apply(file_to_patch_content, patch_struct);
74
+
75
+
if resource_path.ends_with(".gdc") {
76
+
let mut patched_file = File::create(format!("build/webfishing-decomp/{}", resource_name.replace(".gdc", ".gd"))).unwrap();
77
+
patched_file.write_all(patched_content.as_bytes()).unwrap();
78
+
drop(patched_file);
79
+
80
+
utils::gd_utils::recomp_file(&format!("build/webfishing-decomp/{}", resource_name.replace(".gdc", ".gd")));
81
+
82
+
let mut recompiled_patched_file = File::open(format!("build/webfishing-recomp/{}", resource_name)).expect("Could not open recompiled patched file");
83
+
let mut recompiled_patched_content = Vec::new();
84
+
recompiled_patched_file.read_to_end(&mut recompiled_patched_content).expect(&format!("Could not read patched file {}", resource_name));
85
+
86
+
resource.set_content(recompiled_patched_content);
87
+
} else {
88
+
resource.set_content(Vec::from(patched_content.as_bytes()));
89
+
}
90
+
}
91
+
92
+
fn add_recursive_files_to_pck(dir: String, pck: &mut PCK, pck_info: &PckInfo) {
93
+
let directory = read_dir(Path::new(dir.as_str())).expect("read_dir failed");
94
+
for entry in directory {
95
+
let entry = entry.expect("Failed to read entry");
96
+
if entry.path().is_file() {
97
+
let path: PathBuf = entry.path().iter()
98
+
.skip_while(|s| *s != pck_info.get_directory().as_str())
99
+
.skip(1)
100
+
.collect();
101
+
102
+
let resource_path = format!("{}/{}", pck_info.get_resource_prefix(), path.display()).replace("//", "/").replace("res:/", "res://");
103
+
104
+
let mut resource_file = File::open(entry.path()).expect("Cannot open resource file");
105
+
let mut resource_file_content = Vec::new();
106
+
resource_file.read_to_end(&mut resource_file_content).expect("Cannot read resource file");
107
+
drop(resource_file);
108
+
109
+
let pck_file = PckFile::new_file(resource_path, resource_file_content);
110
+
pck.add_file(pck_file);
111
+
} else {
112
+
add_recursive_files_to_pck(entry.path().display().to_string(), pck, pck_info);
113
+
}
114
+
}
115
+
}
116
+
117
+
fn apply_mod(manifest: &Manifest, pck: &mut PCK) {
118
+
println!("Applying {} by {}", manifest.get_name(), manifest.get_author());
119
+
// Apply game patches
120
+
for patch in manifest.get_patches() {
121
+
apply_patch(manifest, pck, &patch);
122
+
};
123
+
124
+
// Add pck data
125
+
match manifest.get_pck_info() {
126
+
Some(pck_info) => {
127
+
add_recursive_files_to_pck(format!("{}/{}",manifest.path,pck_info.get_directory()), pck, pck_info);
128
+
}
129
+
None => {}
130
+
}
131
+
}
132
+
133
+
134
+
pub fn process_mods(pck: &mut PCK) {
135
+
if !Path::exists("mods".as_ref()) {
136
+
fs::create_dir(Path::new("mods")).expect("Failed to create mods directory");
137
+
}
138
+
139
+
let manifests = read_manifests();
140
+
println!("Found {} mods", manifests.len());
141
+
142
+
// Dependency checking
143
+
println!("Checking mod dependencies");
144
+
let mut checked_manifests = check_mods(manifests);
145
+
while !checked_manifests.clone().into_iter().map(|x| x.get_name()).eq(check_mods(checked_manifests.clone()).into_iter().map(|x| x.get_name())) {
146
+
checked_manifests = check_mods(checked_manifests);
147
+
}
148
+
149
+
println!("Checked all mod dependencies, loading {} mods", checked_manifests.len());
150
+
151
+
for manifest in checked_manifests {
152
+
apply_mod(&manifest, pck)
153
+
}
154
+
}
+73
src/mods/structs.rs
+73
src/mods/structs.rs
···
1
+
use serde_derive::{Deserialize};
2
+
3
+
#[derive(Deserialize, Debug, Default, Clone)]
4
+
pub struct Manifest {
5
+
name: String,
6
+
author: String,
7
+
pck_info: Option<PckInfo>,
8
+
patches: Vec<Patch>,
9
+
deps: Option<Vec<String>>,
10
+
11
+
// Reserved
12
+
#[serde(skip_serializing)]
13
+
#[serde(default)]
14
+
pub path: String,
15
+
}
16
+
17
+
#[derive(Deserialize, Debug, Clone)]
18
+
pub struct Patch {
19
+
resource: String,
20
+
patch_file: String,
21
+
}
22
+
23
+
#[derive(Deserialize, Debug, Default, Clone)]
24
+
pub struct PckInfo {
25
+
directory: String,
26
+
resource_prefix: String
27
+
}
28
+
29
+
30
+
impl Manifest {
31
+
pub fn get_dependencies(&self) -> Vec<String> {
32
+
match self.deps {
33
+
Some(ref deps) => deps.clone(),
34
+
None => Vec::new()
35
+
}
36
+
}
37
+
38
+
pub fn get_author(&self) -> String {
39
+
self.author.clone()
40
+
}
41
+
42
+
pub fn get_name(&self) -> String {
43
+
self.name.clone()
44
+
}
45
+
46
+
pub fn get_patches(&self) -> &Vec<Patch> {
47
+
&self.patches
48
+
}
49
+
50
+
pub fn get_pck_info(&self) -> &Option<PckInfo> {
51
+
&self.pck_info
52
+
}
53
+
}
54
+
55
+
impl Patch {
56
+
pub fn get_resource(&self) -> String {
57
+
self.resource.clone()
58
+
}
59
+
60
+
pub fn get_patch_file(&self) -> String {
61
+
self.patch_file.clone()
62
+
}
63
+
}
64
+
65
+
impl PckInfo {
66
+
pub fn get_directory(&self) -> String {
67
+
self.directory.clone()
68
+
}
69
+
70
+
pub fn get_resource_prefix(&self) -> String {
71
+
self.resource_prefix.clone()
72
+
}
73
+
}
+39
src/patches/steam_network_patch.rs
+39
src/patches/steam_network_patch.rs
···
1
+
use godot_pck::structs::PckFile;
2
+
use async_std::fs::File;
3
+
use async_std::io::{ReadExt, WriteExt};
4
+
use godot_pck::structs::PCK;
5
+
6
+
const RESOURCE_PATH: &str = "res://Scenes/Singletons/SteamNetwork.gdc";
7
+
const FILE_PATH: &str = "build/webfishing-export/SteamNetwork.gdc";
8
+
const SCRIPT_PATH: &str = "build/webfishing-decomp/SteamNetwork.gd";
9
+
const COMPILED_PATH: &str = "build/webfishing-recomp/SteamNetwork.gdc";
10
+
11
+
pub(crate) async fn patch(pck: &mut PCK) {
12
+
println!("Patching {} files...", RESOURCE_PATH);
13
+
let pck_file: &mut PckFile = pck.get_file_by_path_mut(RESOURCE_PATH).expect("Couldn't find options_menu.gdc file");
14
+
15
+
let content = pck_file.get_content();
16
+
let mut exported_file = File::create(FILE_PATH).await.expect("Couldn't create file");
17
+
exported_file.write_all(content).await.expect("Couldn't write file");
18
+
drop(exported_file);
19
+
20
+
crate::utils::gd_utils::decomp_script(FILE_PATH);
21
+
22
+
let mut script = File::open(SCRIPT_PATH).await.expect("Cannot open script");
23
+
let mut script_txt = String::new();
24
+
script.read_to_string(&mut script_txt).await.expect("Cannot read script");
25
+
drop(script);
26
+
27
+
let patched_script = script_txt.replace(".LOBBY_COMPARISON_EQUAL_TO_GREATER_THAN", ".OBBY_COMPARISON_EQUAL_TO_GREATER_THAN");
28
+
let mut script = File::create(SCRIPT_PATH).await.expect("Cannot open script");
29
+
script.write_all(patched_script.as_bytes()).await.expect("Cannot write");
30
+
drop(script);
31
+
32
+
crate::utils::gd_utils::recomp_file(SCRIPT_PATH);
33
+
34
+
let mut file = File::open(COMPILED_PATH).await.expect("Cannot open compiled script");
35
+
let mut new_content = vec![];
36
+
file.read_to_end(&mut new_content).await.unwrap();
37
+
38
+
pck_file.set_content(new_content);
39
+
}
+21
src/utils/gd_utils.rs
+21
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
+
pub(crate) fn decomp_script(path: &str) {
6
+
Command::new(RE_TOOLS)
7
+
.arg("--headless")
8
+
.arg(format!("--decompile=\"{}\"", path))
9
+
.arg("--bytecode=3.5.0")
10
+
.arg("--output-dir=build/webfishing-decomp")
11
+
.output().expect(format!("Failed to decompile file: {}", path).as_str());
12
+
}
13
+
14
+
pub(crate) fn recomp_file(path: &str) {
15
+
Command::new(RE_TOOLS)
16
+
.arg("--headless")
17
+
.arg(format!("--compile=\"{}\"", path))
18
+
.arg("--bytecode=3.5.0")
19
+
.arg("--output-dir=build/webfishing-recomp")
20
+
.output().expect(format!("Failed to recompile file: {}", path).as_str());
21
+
}
+1
src/utils/mod.rs
+1
src/utils/mod.rs
···
1
+
pub mod gd_utils;