Skip to content

Commit adda652

Browse files
committed
Add fix-uncompressed-trade-route-loading mod
1 parent f429cb5 commit adda652

File tree

10 files changed

+272
-146
lines changed

10 files changed

+272
-146
lines changed

.github/workflows/release.yml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
mv target/i686-pc-windows-msvc/release/fix_multiplayer_locks.dll ./mods/
2222
mv target/i686-pc-windows-msvc/release/fix_new_settlement_ware_production.dll ./mods/
2323
mv target/i686-pc-windows-msvc/release/fix_siege_beggar_satisfaction_bonus.dll ./mods/
24+
mv target/i686-pc-windows-msvc/release/fix_uncompressed_trade_route_loading.dll ./mods/
2425
mv target/i686-pc-windows-msvc/release/high_res.dll ./mods/
2526
mv target/i686-pc-windows-msvc/release/increase_alderman_found_settlement_mission_limit.dll ./mods/
2627
mv target/i686-pc-windows-msvc/release/scrollmap_render_all_ships.dll ./mods/

Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ members = [
1010
"mod-fix-market-hall-production-town",
1111
"mod-fix-multiplayer-locks",
1212
"mod-fix-new-settlement-ware-production",
13-
"mod-fix-siege-beggar-satisfaction-bonus",
13+
"mod-fix-siege-beggar-satisfaction-bonus",
14+
"mod-fix-uncompressed-trade-route-loading",
1415
"mod-high-res",
1516
"mod-increase-alderman-found-settlement-mission-limit",
1617
"mod-scrollmap-render-all-ships",
@@ -33,7 +34,7 @@ exclude = [
3334
version = "0.1.11"
3435

3536
[workspace.dependencies]
36-
hooklet = "^0.1.7"
37+
hooklet = "^0.1.8"
3738
clap = { version = "4.3.0", features = ["derive"] }
3839
simple_logger = "4.1"
3940
log = "0.4"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "mod-fix-uncompressed-trade-route-loading"
3+
edition = "2021"
4+
version.workspace = true
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
name="fix_uncompressed_trade_route_loading"
9+
10+
[dependencies]
11+
log = { workspace = true }
12+
win_dbg_logger = { workspace = true }
13+
hooklet = { workspace = true }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use std::ffi::c_void;
2+
3+
use hooklet::windows::x86::{deploy_rel32_raw, X86Rel32Type};
4+
5+
const PATCH_ADDRESS: u32 = 0x004D5FD5;
6+
7+
#[no_mangle]
8+
pub unsafe extern "C" fn start() -> u32 {
9+
let _ = log::set_logger(&win_dbg_logger::DEBUGGER_LOGGER);
10+
log::set_max_level(log::LevelFilter::Trace);
11+
12+
match deploy_rel32_raw(PATCH_ADDRESS, &abs_clean as *const c_void as _, X86Rel32Type::Call) {
13+
Ok(_) => {}
14+
Err(_) => return 1,
15+
}
16+
17+
0
18+
}
19+
20+
#[no_mangle]
21+
unsafe extern "thiscall" fn abs(input: i32) -> i32 {
22+
input.abs()
23+
}
24+
25+
extern "C" {
26+
static abs_clean: c_void;
27+
}
28+
29+
std::arch::global_asm!("
30+
.global {detour_symbol}
31+
{detour_symbol}:
32+
pushfd
33+
push ecx
34+
push edx
35+
mov eax, ecx
36+
call {function_symbol}
37+
pop edx
38+
pop ecx
39+
popfd
40+
ret
41+
",
42+
detour_symbol = sym abs_clean,
43+
function_symbol = sym abs,
44+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub(crate) mod ffi;

p3-rou/src/cli.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ use std::fs;
22

33
use log::LevelFilter;
44

5+
use p3_rou::decompress::decompress_file;
6+
57
pub fn main() {
68
simple_logger::SimpleLogger::new().with_level(LevelFilter::Debug).env().init().unwrap();
7-
let data = p3_rou::read_rou("tests/a-unloadall.rou");
9+
let data = decompress_file("tests/a-unloadall.rou");
810
let stops = data.len() / 220;
911
for i in 0..stops {
1012
fs::write(format!("a-unloadall.{}.roustop", i), &data[i * 220..i * 220 + 220]).unwrap();

p3-rou/src/decompress.rs

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use std::{fs, ops::Not, path::Path};
2+
3+
use bitvec::{field::BitField, order::Lsb0, slice::BitSlice, view::BitView};
4+
use log::{debug, info, trace};
5+
6+
const DECOMPRESS_TABLE: [DecompressTableEntry; 8] = [
7+
DecompressTableEntry { field_0: 1, field_4: 0x00 },
8+
DecompressTableEntry { field_0: 2, field_4: 0x02 },
9+
DecompressTableEntry { field_0: 3, field_4: 0x06 },
10+
DecompressTableEntry { field_0: 4, field_4: 0x0e },
11+
DecompressTableEntry { field_0: 5, field_4: 0x1e },
12+
DecompressTableEntry { field_0: 6, field_4: 0x3e },
13+
DecompressTableEntry { field_0: 7, field_4: 0x7e },
14+
DecompressTableEntry { field_0: 8, field_4: 0xfe },
15+
];
16+
17+
const BITMASKS_TABLE_1: [u32; 8] = [0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff];
18+
const BITMASKS_TABLE_2: [u32; 32] = [
19+
0x00,
20+
0x01,
21+
0x03,
22+
0x07,
23+
0x0f,
24+
0x1f,
25+
0x3f,
26+
0x7f,
27+
0xff,
28+
0x1ff,
29+
0x3ff,
30+
0x7ff,
31+
0xfff,
32+
0x1ff,
33+
0x3fff,
34+
0x7fff,
35+
0xffff,
36+
0x1_ffff,
37+
0x3_ffff,
38+
0x7_ffff,
39+
0xf_ffff,
40+
0x1f_ffff,
41+
0x3f_ffff,
42+
0x7f_ffff,
43+
0xff_ffff,
44+
0x1ff_ffff,
45+
0x3ff_ffff,
46+
0x7ff_ffff,
47+
0xfff_ffff,
48+
0x1fff_ffff,
49+
0x3FFFFFFF,
50+
0x7FFFFFFF,
51+
];
52+
53+
struct DecompressTableEntry {
54+
field_0: u32,
55+
field_4: u32,
56+
}
57+
58+
struct BitReader<'a> {
59+
data: &'a BitSlice<u8, Lsb0>,
60+
pos: usize,
61+
}
62+
63+
impl<'a> BitReader<'a> {
64+
pub fn new(data: &'a [u8]) -> Self {
65+
BitReader {
66+
data: data.view_bits(),
67+
pos: 0,
68+
}
69+
}
70+
71+
pub fn read<T: bitvec::macros::internal::funty::Integral>(&mut self, len: usize) -> T {
72+
let value = self.data[self.pos..self.pos + len].load::<T>();
73+
self.pos += len;
74+
value
75+
}
76+
77+
pub fn read_bool(&mut self) -> bool {
78+
let result = self.data[self.pos..self.pos + 1].load::<u8>() == 1;
79+
self.pos += 1;
80+
result
81+
}
82+
83+
pub fn read_u3(&mut self) -> u8 {
84+
let result = self.data[self.pos..self.pos + 3].load();
85+
self.pos += 3;
86+
result
87+
}
88+
89+
pub fn read_u8(&mut self) -> u8 {
90+
let result = self.data[self.pos..self.pos + 8].load();
91+
self.pos += 8;
92+
result
93+
}
94+
95+
pub fn read_i32(&mut self) -> i32 {
96+
let result = self.data[self.pos..self.pos + 32].load();
97+
self.pos += 32;
98+
result
99+
}
100+
}
101+
102+
pub fn decompress_file<P: AsRef<Path>>(path: P) -> Vec<u8> {
103+
let path = path.as_ref();
104+
info!("Reading {:?}", path);
105+
decompress(&fs::read(path).unwrap())
106+
}
107+
108+
fn decompress(input: &[u8]) -> Vec<u8> {
109+
let mut reader = BitReader::new(input);
110+
let len = reader.read_i32();
111+
assert!(len > 0); //TODO implement negative length special case if required
112+
let mut output = Vec::with_capacity(len as usize);
113+
debug!("Parsing {len} bytes");
114+
while output.len() < len as usize {
115+
if reader.read_bool() {
116+
let compressed_chunk_type = reader.read_u3() as usize;
117+
let thingy_len = DECOMPRESS_TABLE[compressed_chunk_type].field_0;
118+
let thingy = reader.read::<u8>(thingy_len as usize) as u32;
119+
trace!("compressed_chunk_type={compressed_chunk_type:#04x} thingy_len={thingy_len} thingy={thingy}");
120+
121+
let negated_value = DECOMPRESS_TABLE[compressed_chunk_type].field_4 + (BITMASKS_TABLE_1[compressed_chunk_type] & thingy);
122+
let offset = negated_value.not() as i32;
123+
trace!("negated={negated_value} offset={offset}");
124+
125+
let mut elements = 1;
126+
let mut output_elements = 2;
127+
loop {
128+
elements += 1;
129+
let wat = reader.read::<u8>(elements) as u32;
130+
let why = BITMASKS_TABLE_1[elements] & wat;
131+
output_elements += why;
132+
if why != BITMASKS_TABLE_2[elements] {
133+
trace!("Breaking loop: {why:#010x} != {:#010x}", BITMASKS_TABLE_2[elements]);
134+
break;
135+
}
136+
}
137+
138+
// Repeat slice
139+
let repeated_value_index = (output.len() as i32 + offset) as usize;
140+
for i in 0..output_elements {
141+
let value = output[repeated_value_index + i as usize];
142+
trace!("Pushing value {value:#04x} (decompressed)");
143+
output.push(value);
144+
}
145+
} else {
146+
let value = reader.read_u8();
147+
trace!("Pushing value {value:#04x} (regular)");
148+
output.push(value)
149+
}
150+
}
151+
output
152+
}

0 commit comments

Comments
 (0)