Skip to content

Commit b62b4eb

Browse files
committed
wip
1 parent 1718bbb commit b62b4eb

File tree

6 files changed

+317
-1
lines changed

6 files changed

+317
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ members = [
88
"mod-fix-damage-to-offside-ship-artillery",
99
"mod-fix-invulnerable-ship-artillery-slots",
1010
"mod-fix-market-hall-production-town",
11+
"mod-fix-multiplayer-locks",
1112
"mod-fix-new-settlement-ware-production",
1213
"mod-fix-siege-beggar-satisfaction-bonus",
1314
"mod-high-res",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "mod-fix-multiplayer-locks"
3+
edition = "2021"
4+
version.workspace = true
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
name="fix_multiplayer_locks"
9+
10+
[dependencies]
11+
log = { workspace = true }
12+
p3-api = { path = "../p3-api" }
13+
hooklet = { workspace = true }
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
use std::{backtrace::Backtrace, ffi::c_void, thread};
2+
3+
use hooklet::windows::x86::{deploy_rel32_raw, replace_slice_rwx, X86Rel32Type};
4+
use log::{debug, error, trace};
5+
use p3_api::{mods::init_mod, operations::OperationsPtr};
6+
7+
#[no_mangle]
8+
pub unsafe extern "C" fn start() -> u32 {
9+
init_mod();
10+
11+
debug!("Fix execute_operations current ops lock (TODO evaluate whether we have to save and restore regs)");
12+
match deploy_rel32_raw(0x005468B3, lock_current as *const c_void as _, X86Rel32Type::Call) {
13+
Ok(_) => {}
14+
Err(_) => return 1,
15+
}
16+
match deploy_rel32_raw(0x005468B3 + 5, 0x005468EE, X86Rel32Type::Jump) {
17+
Ok(_) => {}
18+
Err(_) => return 2,
19+
}
20+
21+
debug!("Fix execute_operations current ops unlock");
22+
match deploy_rel32_raw(0x00547254, execute_operations_unlock_current as *const c_void as _, X86Rel32Type::Call) {
23+
Ok(_) => {}
24+
Err(_) => return 3,
25+
}
26+
let execute_operations_unlock: [u8; 27] = [
27+
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
28+
0x90, 0x90,
29+
];
30+
match replace_slice_rwx(0x547259, &execute_operations_unlock) {
31+
Ok(_) => {}
32+
Err(_) => return 4,
33+
}
34+
35+
debug!("Fix insert_into_pending_operations_wrapper pending lock (TODO should we keep the >= 52 pending check?)");
36+
match deploy_rel32_raw(0x0054AA79, lock_pending as *const c_void as _, X86Rel32Type::Call) {
37+
Ok(_) => {}
38+
Err(_) => return 5,
39+
}
40+
match deploy_rel32_raw(0x0054AA79 + 5, 0x0054AAB5, X86Rel32Type::Jump) {
41+
Ok(_) => {}
42+
Err(_) => return 6,
43+
}
44+
45+
debug!("Fix insert_into_pending_operations_wrapper pending unlock (TODO check reg save)");
46+
match deploy_rel32_raw(0x0054AAC2, unlock_pending as *const c_void as _, X86Rel32Type::Call) {
47+
Ok(_) => {}
48+
Err(_) => return 7,
49+
}
50+
let insert_into_pending_operations_unlock: [u8; 5] = [0x90, 0x90, 0x90, 0x90, 0x90];
51+
match replace_slice_rwx(0x0054AAC2 + 5, &insert_into_pending_operations_unlock) {
52+
Ok(_) => {}
53+
Err(_) => return 8,
54+
}
55+
56+
debug!("Fix operations_network_host_send_to_all_and_move_to_current_ops current lock");
57+
match deploy_rel32_raw(0x0054BCCB, &lock_current_clean as *const c_void as _, X86Rel32Type::Call) {
58+
Ok(_) => {}
59+
Err(_) => return 11,
60+
}
61+
let operations_network_host_send_to_all_and_move_to_current_ops_lock: [u8; 5] = [0x90, 0x90, 0x90, 0x90, 0x90];
62+
match replace_slice_rwx(0x0054BCCB + 5, &operations_network_host_send_to_all_and_move_to_current_ops_lock) {
63+
Ok(_) => {}
64+
Err(_) => return 12,
65+
}
66+
67+
debug!("Fix operations_network_host_send_to_all_and_move_to_current_ops current unlock");
68+
match deploy_rel32_raw(0x0054BD2C, &unlock_current_clean as *const c_void as _, X86Rel32Type::Call) {
69+
Ok(_) => {}
70+
Err(_) => return 13,
71+
}
72+
match replace_slice_rwx(0x0054BD2C + 5, &[0x90]) {
73+
Ok(_) => {}
74+
Err(_) => return 14,
75+
}
76+
77+
debug!("Fix operations_network_host_receive_from_all_and_own_pending pending lock");
78+
// There is a callee saved register restoring `pop ebx`` in the lock code (╯°□°)╯︵ ┻━┻
79+
match deploy_rel32_raw(0x0054B90D, &lock_pending_clean as *const c_void as _, X86Rel32Type::Call) {
80+
Ok(_) => {}
81+
Err(_) => return 15,
82+
}
83+
match deploy_rel32_raw(0x0054B90D + 5, 0x0054B932, X86Rel32Type::Jump) {
84+
Ok(_) => {}
85+
Err(_) => return 16,
86+
}
87+
88+
debug!("Fix operations_network_host_receive_from_all_and_own_pending pending unlock");
89+
match deploy_rel32_raw(0x0054B949, &unlock_pending_clean as *const c_void as _, X86Rel32Type::Call) {
90+
Ok(_) => {}
91+
Err(_) => return 17,
92+
}
93+
// We sneak in the `pop ebx` here to keep the stack intact 🧠
94+
match replace_slice_rwx(0x0054B949 + 5, &[0x5b, 0x90, 0x90, 0x90, 0x90]) {
95+
Ok(_) => {}
96+
Err(_) => return 18,
97+
}
98+
99+
debug!("Fix operations_client_receive_from_host current lock");
100+
match deploy_rel32_raw(0x0054B13F, &try_lock_current_clean as *const c_void as _, X86Rel32Type::Call) {
101+
Ok(_) => {}
102+
Err(_) => return 19,
103+
}
104+
match replace_slice_rwx(0x0054B13F + 5, &[0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90]) {
105+
Ok(_) => {}
106+
Err(_) => return 20,
107+
}
108+
109+
debug!("Fix operations_client_receive_from_host current unlock");
110+
match deploy_rel32_raw(0x0054B193, &unlock_current_clean as *const c_void as _, X86Rel32Type::Call) {
111+
Ok(_) => {}
112+
Err(_) => return 21,
113+
}
114+
match replace_slice_rwx(0x0054B193 + 5, &[0x66, 0x39, 0x7c, 0x24, 0x26, 0x90]) {
115+
Ok(_) => {}
116+
Err(_) => return 22,
117+
}
118+
// Fix jnz above
119+
match replace_slice_rwx(0x0054B14D + 1, &[0x49]) {
120+
Ok(_) => {}
121+
Err(_) => return 23,
122+
}
123+
124+
debug!("Fix operations_network_client_send_pending_operations pending lock");
125+
debug!("Fix operations_network_client_send_pending_operations pending unlock");
126+
let addr: u32 = &try_lock_current_clean as *const _ as _;
127+
debug!("#### {addr}");
128+
129+
0
130+
}
131+
132+
#[no_mangle]
133+
unsafe extern "stdcall" fn lock_pending() {
134+
let thread_id = thread::current().id();
135+
debug!("lock_pending {thread_id:?}");
136+
crate::lock(&crate::PENDING_OPS_LOCK);
137+
}
138+
139+
#[no_mangle]
140+
unsafe extern "stdcall" fn try_lock_pending() -> u32 {
141+
let thread_id = thread::current().id();
142+
debug!("try_lock_pending {thread_id:?}");
143+
let in_use = crate::try_lock(&crate::PENDING_OPS_LOCK);
144+
debug!("try_lock_pending {thread_id:?} {in_use}");
145+
in_use
146+
}
147+
148+
#[no_mangle]
149+
unsafe extern "stdcall" fn unlock_pending() {
150+
let operations = OperationsPtr::new();
151+
// Unlock ops are often not guarded by the mp check, so we have to do an mp check here.
152+
if operations.get_status() & 0x0c != 0 {
153+
let thread_id = thread::current().id();
154+
debug!("unlock_pending {thread_id:?}");
155+
crate::unlock(&crate::PENDING_OPS_LOCK);
156+
} else {
157+
crate::PENDING_OPS_LOCK.store(0, std::sync::atomic::Ordering::SeqCst);
158+
}
159+
}
160+
161+
#[no_mangle]
162+
unsafe extern "stdcall" fn lock_current() {
163+
let thread_id = thread::current().id();
164+
debug!("lock_current {thread_id:?}");
165+
crate::lock(&crate::CURRENT_OPS_LOCK);
166+
}
167+
168+
#[no_mangle]
169+
unsafe extern "stdcall" fn try_lock_current() -> u32 {
170+
let thread_id = thread::current().id();
171+
debug!("try_lock_current {thread_id:?}");
172+
let in_use = crate::try_lock(&crate::CURRENT_OPS_LOCK);
173+
debug!("try_lock_current {thread_id:?} {in_use}");
174+
in_use
175+
}
176+
177+
#[no_mangle]
178+
unsafe extern "stdcall" fn unlock_current() {
179+
let operations = OperationsPtr::new();
180+
// Unlock ops are often not guarded by the mp check, so we have to do an mp check here.
181+
if operations.get_status() & 0x0c != 0 {
182+
let thread_id = thread::current().id();
183+
debug!("unlock_current {thread_id:?}");
184+
crate::unlock(&crate::CURRENT_OPS_LOCK);
185+
} else {
186+
crate::CURRENT_OPS_LOCK.store(0, std::sync::atomic::Ordering::SeqCst);
187+
}
188+
}
189+
190+
#[no_mangle]
191+
unsafe extern "stdcall" fn execute_operations_unlock_current() {
192+
let operations = OperationsPtr::new();
193+
let unpacked_traderoute_ptr = operations.get_unpacked_traderoute_ptr();
194+
operations.set_current_operations_array_pos(0);
195+
unlock_current();
196+
if !unpacked_traderoute_ptr.is_null() {
197+
operations.transfer_loaded_traderoute();
198+
}
199+
}
200+
201+
macro_rules! save_volatile_registers {
202+
($function_name:ident, $symbol_name:ident) => {
203+
extern "C" {
204+
static $symbol_name: c_void;
205+
}
206+
207+
std::arch::global_asm!("
208+
.global {detour_symbol}
209+
{detour_symbol}:
210+
pushfd
211+
pushad
212+
call {function_symbol}
213+
popad
214+
popfd
215+
ret
216+
",
217+
detour_symbol = sym $symbol_name,
218+
function_symbol = sym $function_name,
219+
);
220+
};
221+
}
222+
223+
macro_rules! save_volatile_registers_except_eax {
224+
($function_name:ident, $symbol_name:ident) => {
225+
extern "C" {
226+
static $symbol_name: c_void;
227+
}
228+
229+
std::arch::global_asm!("
230+
.global {detour_symbol}
231+
{detour_symbol}:
232+
pushfd
233+
push ecx
234+
push edx
235+
call {function_symbol}
236+
pop edx
237+
pop ecx
238+
popfd
239+
ret
240+
",
241+
detour_symbol = sym $symbol_name,
242+
function_symbol = sym $function_name,
243+
);
244+
};
245+
}
246+
247+
save_volatile_registers!(lock_pending, lock_pending_clean);
248+
save_volatile_registers_except_eax!(try_lock_pending, try_lock_pending_clean);
249+
save_volatile_registers!(unlock_pending, unlock_pending_clean);
250+
251+
save_volatile_registers!(lock_current, lock_current_clean);
252+
save_volatile_registers_except_eax!(try_lock_current, try_lock_current_clean);
253+
save_volatile_registers!(unlock_current, unlock_current_clean);
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use std::sync::atomic::{AtomicU16, Ordering};
2+
3+
pub(crate) mod ffi;
4+
5+
static PENDING_OPS_LOCK: AtomicU16 = AtomicU16::new(0);
6+
static CURRENT_OPS_LOCK: AtomicU16 = AtomicU16::new(0);
7+
8+
pub(crate) fn lock(lock: &AtomicU16) {
9+
loop {
10+
if lock.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst).is_ok() {
11+
break;
12+
}
13+
}
14+
}
15+
16+
pub(crate) fn try_lock(lock: &AtomicU16) -> u32 {
17+
match lock.compare_exchange(0, 1, Ordering::SeqCst, Ordering::SeqCst) {
18+
Ok(_) => 0,
19+
Err(_) => 1
20+
}
21+
}
22+
23+
pub(crate) fn unlock(lock: &AtomicU16) {
24+
let old = lock.swap(0, Ordering::SeqCst);
25+
if old != 1 {
26+
panic!("Lock is dirty ({old})");
27+
}
28+
}

p3-api/src/mods.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::panic;
1+
use std::{backtrace::Backtrace, panic};
22

33
use log::error;
44

@@ -8,5 +8,7 @@ pub unsafe fn init_mod() {
88

99
panic::set_hook(Box::new(|p| {
1010
error!("{p}");
11+
let trace = Backtrace::force_capture();
12+
error!("{}", trace)
1113
}));
1214
}

p3-api/src/operations.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ pub const OPERATIONS_PTR: OperationsPtr = OperationsPtr::new();
88
const OPERATIONS_ADDRESS: u32 = 0x006DF2F0;
99
static EXECUTE_OPERATION_ADDRESS: u32 = 0x00535760;
1010
static ENQUEUE_OPERATION_ADDRESS: u32 = 0x0054AA70;
11+
static TRANSFER_LOADED_TRADEROUTE_ADDRESS: u32 = 0x005492D0;
1112
static ENQUEUE_OPERATION: &extern "thiscall" fn(*mut c_void, *const c_void) = unsafe { transmute(&ENQUEUE_OPERATION_ADDRESS) };
13+
static TRANSFER_LOADED_TRADEROUTE: &extern "thiscall" fn(*mut c_void) = unsafe { transmute(&TRANSFER_LOADED_TRADEROUTE_ADDRESS) };
1214

1315
#[derive(Clone, Debug, Copy)]
1416
pub struct OperationsPtr {
@@ -26,15 +28,32 @@ impl OperationsPtr {
2628
Self { address: OPERATIONS_ADDRESS }
2729
}
2830

31+
pub unsafe fn get_status(&self) -> u32 {
32+
self.get(0x00)
33+
}
34+
35+
pub unsafe fn set_current_operations_array_pos(&self, pos: u16) {
36+
self.set(0x474 + 0x0e, &pos)
37+
}
38+
2939
pub unsafe fn get_player_merchant_index(&self) -> i32 {
3040
self.get(0x0924)
3141
}
3242

43+
pub unsafe fn get_unpacked_traderoute_ptr(&self) -> *mut c_void {
44+
self.get(0x930)
45+
}
46+
3347
pub unsafe fn enqueue_operation(&self, op: Operation) {
3448
debug!("Enqueuing operation {op:?}");
3549
let op_bytes = op.to_raw();
3650
ENQUEUE_OPERATION(OPERATIONS_ADDRESS as _, op_bytes.as_ptr() as _)
3751
}
52+
53+
pub unsafe fn transfer_loaded_traderoute(&self) {
54+
debug!("Transfer loaded traderoute");
55+
TRANSFER_LOADED_TRADEROUTE(OPERATIONS_ADDRESS as _)
56+
}
3857
}
3958

4059
impl P3Pointer for OperationsPtr {

0 commit comments

Comments
 (0)