+123
-5
Cargo.lock
+123
-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"
···
223
238
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
224
239
225
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]]
226
247
name = "byteorder"
227
248
version = "1.5.0"
228
249
source = "registry+https://github.com/rust-lang/crates.io-index"
···
250
271
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
251
272
252
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]]
253
288
name = "colored"
254
289
version = "2.2.0"
255
290
source = "registry+https://github.com/rust-lang/crates.io-index"
···
741
776
]
742
777
743
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]]
744
802
name = "icu_collections"
745
803
version = "1.5.0"
746
804
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1002
1060
version = "0.3.17"
1003
1061
source = "registry+https://github.com/rust-lang/crates.io-index"
1004
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"
1005
1069
1006
1070
[[package]]
1007
1071
name = "miniz_oxide"
···
1053
1117
]
1054
1118
1055
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]]
1056
1141
name = "ntapi"
1057
1142
version = "0.4.1"
1058
1143
source = "registry+https://github.com/rust-lang/crates.io-index"
1059
1144
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
1060
1145
dependencies = [
1061
1146
"winapi",
1147
+
]
1148
+
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",
1062
1156
]
1063
1157
1064
1158
[[package]]
···
1147
1241
"redox_syscall",
1148
1242
"smallvec",
1149
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",
1150
1255
]
1151
1256
1152
1257
[[package]]
···
1453
1558
1454
1559
[[package]]
1455
1560
name = "serde"
1456
-
version = "1.0.216"
1561
+
version = "1.0.217"
1457
1562
source = "registry+https://github.com/rust-lang/crates.io-index"
1458
-
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
1563
+
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
1459
1564
dependencies = [
1460
1565
"serde_derive",
1461
1566
]
1462
1567
1463
1568
[[package]]
1464
1569
name = "serde_derive"
1465
-
version = "1.0.216"
1570
+
version = "1.0.217"
1466
1571
source = "registry+https://github.com/rust-lang/crates.io-index"
1467
-
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
1572
+
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
1468
1573
dependencies = [
1469
1574
"proc-macro2",
1470
1575
"quote",
···
1993
2098
"asky",
1994
2099
"async-std",
1995
2100
"godot_pck",
2101
+
"patch-apply",
1996
2102
"reqwest",
2103
+
"serde",
2104
+
"serde_derive",
2105
+
"serde_json",
1997
2106
"steamlocate",
1998
2107
"sudo",
1999
2108
"sysinfo",
···
2028
2137
source = "registry+https://github.com/rust-lang/crates.io-index"
2029
2138
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
2030
2139
dependencies = [
2031
-
"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 = [
2032
2150
"windows-targets 0.52.6",
2033
2151
]
2034
2152
+7
-1
Cargo.toml
+7
-1
Cargo.toml
···
11
11
async-std = "1.13.0"
12
12
sudo = "0.6.0"
13
13
asky = "0.1.1"
14
-
godot_pck = {path = "./src/godot_pck"}
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"}
+27
README.md
+27
README.md
···
25
25
- renaming `steam_id_remote` dictionnary key to `remote_steam_id` to fix network spam detection that resulted in timeouts
26
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
27
28
+
## How to make a mod?
29
+
30
+
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:
31
+
```json
32
+
{
33
+
"name": "Ship Mod", // Mod name
34
+
"author": "Estym", // Author
35
+
36
+
"pck_info": { // (Optional)
37
+
"directory": "pck", // Relative folder path to where the mod resources are
38
+
"resource_prefix": "res://Mods/Ship/" // Resource path prefix for the mod resources
39
+
},
40
+
41
+
"mod_patches": [ // Array of patches
42
+
{
43
+
"resource": "res://Scenes/Entities/Player/player.gdc", // Resource to patch
44
+
"patch_file": "patch/player.patch" // relative file path to the patch file
45
+
}
46
+
],
47
+
48
+
"deps": [] // Dependencies for the mod (Optional)
49
+
}
50
+
```
51
+
52
+
### Notes:
53
+
- The patch files are made by using `$ git diff [original_file] [modded_file] > file.patch`
54
+
28
55
## Credits
29
56
30
57
[@vimaexd](https://github.com/vimaexd) for their blog post !
+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
+
"mod_patches": [
9
+
{
10
+
"resource": "res://Scenes/Entities/Player/player.gdc",
11
+
"patch_file": "patch/player.patch"
12
+
}
13
+
],
14
+
"deps": []
15
+
}
+58
example_mods/ship/patch/player.patch
+58
example_mods/ship/patch/player.patch
···
1
+
diff --git a/webfishing/Scenes/Entities/Player/player.gd b/Users/evann/Godot/Webfishing/Scenes/Entities/Player/player.gd
2
+
index 3879ff8..cc20048 100644
3
+
--- a/webfishing/Scenes/Entities/Player/player.gd
4
+
+++ b/Users/evann/Godot/Webfishing/Scenes/Entities/Player/player.gd
5
+
@@ -30,6 +30,7 @@ export var NPC_cosmetics = {"species": "species_cat", "pattern": "pattern_none"
6
+
export var NPC_name = "NPC Test"
7
+
export var NPC_title = "npc title here"
8
+
9
+
+var ship_mod_instance = preload("res://Mods/Ship/ship.gd").new()
10
+
var camera_zoom = 5.0
11
+
12
+
var direction = Vector3()
13
+
@@ -341,6 +342,7 @@ func _process(delta):
14
+
15
+
func _controlled_process(delta):
16
+
_get_input()
17
+
+
18
+
_process_movement(delta)
19
+
_process_timers()
20
+
_interact_check()
21
+
@@ -480,7 +482,7 @@ func _process_timers():
22
+
23
+
func _get_input():
24
+
direction = Vector3.ZERO
25
+
-
26
+
+
27
+
if Input.is_action_just_released("primary_action"): _primary_action_release()
28
+
if Input.is_action_pressed("primary_action"): _primary_action_hold()
29
+
else: primary_hold_timer = 0
30
+
@@ -538,8 +540,10 @@ func _get_input():
31
+
32
+
mouse_look = false
33
+
34
+
+ if ship_mod_instance.is_sitting_on_ship(self):
35
+
+ ship_mod_instance.process_ship(self, get_world())
36
+
+ return
37
+
if sitting: return
38
+
-
39
+
if Input.is_action_pressed("move_forward"): direction -= cam_base.transform.basis.z
40
+
if Input.is_action_pressed("move_back"): direction += cam_base.transform.basis.z
41
+
if Input.is_action_pressed("move_right"): direction += cam_base.transform.basis.x
42
+
@@ -1405,16 +1409,6 @@ func _create_prop(ref, offset = Vector3(0, 1, 0), restrict_to_one = false):
43
+
return false
44
+
45
+
46
+
- if $detection_zones / prop_detect.get_overlapping_bodies().size() > 0 or not is_on_floor() or not $detection_zones / prop_ray.is_colliding():
47
+
- PlayerData._send_notification("invalid prop placement", 1)
48
+
- return false
49
+
-
50
+
-
51
+
- if prop_ids.size() > 4:
52
+
- PlayerData._send_notification("prop limit reached", 1)
53
+
- return false
54
+
-
55
+
-
56
+
var item = PlayerData._find_item_code(ref)
57
+
var data = Globals.item_data[item["id"]]["file"]
58
+
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
+
+8
-2
src/godot_pck/src/structs.rs
+8
-2
src/godot_pck/src/structs.rs
···
12
12
#[derive(Debug, Clone)]
13
13
pub struct PckFile {
14
14
path: String,
15
-
offset: u64,
16
15
content: Vec<u8>,
17
16
md5: [u8; 16],
18
17
}
···
136
135
let content: Vec<u8> = file_bytes.iter().skip(offset as usize).take(size as usize).cloned().collect();
137
136
PckFile {
138
137
path,
139
-
offset,
140
138
content,
141
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
142
148
}
143
149
}
144
150
+7
-18
src/main.rs
+7
-18
src/main.rs
···
1
1
mod utils;
2
2
mod patches;
3
+
mod mods;
3
4
5
+
use std::env::{current_exe, set_current_dir};
4
6
use std::fs::File;
5
7
use std::io::{Read, Write};
6
8
use std::path::Path;
7
9
use std::process::Command;
8
10
use std::time::Duration;
9
-
use asky::Confirm;
11
+
use asky::Text;
10
12
use async_std::fs::create_dir;
11
13
use steamlocate::SteamDir;
12
14
use sudo::RunningAs;
···
14
16
use godot_pck::structs::PCK;
15
17
16
18
static WEBFISHING_APPID: u32 = 3146520;
17
-
18
-
19
19
20
20
async fn install_webfishing(location: &SteamDir) {
21
21
let steam_location = location.path();
···
104
104
105
105
#[tokio::main]
106
106
async fn main() {
107
+
set_current_dir(current_exe().unwrap().parent().expect("Could not get current dir")).expect("Could not set current dir");
107
108
if !Path::exists("build".as_ref()) {
108
109
println!("Creating build folder");
109
110
create_dir("build").await.expect("could not create build folder");
···
161
162
162
163
patches::steam_network_patch::patch(&mut pck).await;
163
164
patches::options_menu_patch::patch(&mut pck).await;
165
+
mods::mods::process_mods(&mut pck);
164
166
println!("Root permissions needed to sign webfishing");
165
167
166
168
let bytes = &pck.to_bytes();
···
175
177
.output()
176
178
.expect("Could not execute xattr");
177
179
178
-
if Confirm::new("Do you wanna install Webfishing in the app folder?").prompt().expect("Could not confirm to install the webfishing") {
179
-
Command::new("rsync")
180
-
.arg("-a")
181
-
.arg("build/webfishing.app")
182
-
.current_dir("/Applications/")
183
-
.output().expect("Could not execute rsync");
180
+
println!("Webfishing is in the build folder !");
184
181
185
-
Command::new("rm")
186
-
.arg("-r")
187
-
.arg("build/webfishing.app")
188
-
.output().expect("Could not remove webfishing.app");
189
-
190
-
println!("Successfully installed webfishing !");
191
-
} else {
192
-
println!("Webfishing is in the build folder !")
193
-
}
182
+
Text::new("Press Enter to quit").prompt().expect("Could not confirm to quit");
194
183
}
+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
+
}
+1
-1
src/patches/mod.rs
+1
-1
src/patches/mod.rs
+2
-2
src/patches/steam_network_patch.rs
+2
-2
src/patches/steam_network_patch.rs
···
10
10
11
11
pub(crate) async fn patch(pck: &mut PCK) {
12
12
println!("Patching {} files...", RESOURCE_PATH);
13
-
let mut pck_file: &mut PckFile = pck.get_file_by_path_mut(RESOURCE_PATH).expect("Couldn't find options_menu.gdc file");
13
+
let pck_file: &mut PckFile = pck.get_file_by_path_mut(RESOURCE_PATH).expect("Couldn't find options_menu.gdc file");
14
14
15
15
let content = pck_file.get_content();
16
16
let mut exported_file = File::create(FILE_PATH).await.expect("Couldn't create file");
17
17
exported_file.write_all(content).await.expect("Couldn't write file");
18
18
drop(exported_file);
19
19
20
-
crate::utils::gd_utils::decomp_file(FILE_PATH);
20
+
crate::utils::gd_utils::decomp_script(FILE_PATH);
21
21
22
22
let mut script = File::open(SCRIPT_PATH).await.expect("Cannot open script");
23
23
let mut script_txt = String::new();
+1
-25
src/utils/gd_utils.rs
+1
-25
src/utils/gd_utils.rs
···
2
2
3
3
const RE_TOOLS: &str = "build/Godot RE Tools.app/Contents/MacOS/Godot RE Tools";
4
4
5
-
// https://stackoverflow.com/a/54152901
6
-
pub(crate) fn replace_slice<T>(buf: &[T], from: &[T], to: &[T], replace_with: &mut [T]) -> Vec<T>
7
-
where
8
-
T: Clone + PartialEq + From<u8>,
9
-
{
10
-
let mut last_j = 0;
11
-
let mut res : Vec<T> = Vec::new();
12
-
for i in 0..=buf.len() {
13
-
if buf[i..].starts_with(from) {
14
-
res.append(&mut buf[last_j..i].to_vec());
15
-
for j in (i + 1)..=buf.len() {
16
-
if buf[j..].starts_with(to) {
17
-
res.append(replace_with.to_vec().as_mut());
18
-
last_j = j;
19
-
break;
20
-
}
21
-
}
22
-
}
23
-
}
24
-
25
-
res.append(&mut buf[last_j..].to_vec());
26
-
res
27
-
}
28
-
29
-
pub(crate) fn decomp_file(path: &str) {
5
+
pub(crate) fn decomp_script(path: &str) {
30
6
Command::new(RE_TOOLS)
31
7
.arg("--headless")
32
8
.arg(format!("--decompile=\"{}\"", path))