1extern crate self as procmod_layout;
34
35pub use procmod_core::{Error, Process, Result};
36pub use procmod_layout_derive::GameStruct;
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 #[derive(GameStruct, Debug)]
43 struct SimpleLayout {
44 #[offset(0)]
45 a: u32,
46 #[offset(4)]
47 b: u32,
48 }
49
50 #[derive(GameStruct, Debug)]
51 struct WithGap {
52 #[offset(0)]
53 first: u64,
54 #[offset(16)]
55 second: u64,
56 }
57
58 #[derive(GameStruct, Debug)]
59 struct SingleField {
60 #[offset(0)]
61 value: f32,
62 }
63
64 #[derive(GameStruct, Debug)]
65 struct ArrayField {
66 #[offset(0)]
67 values: [u32; 4],
68 }
69
70 #[derive(GameStruct, Debug)]
71 struct MixedTypes {
72 #[offset(0)]
73 byte_val: u8,
74 #[offset(4)]
75 int_val: u32,
76 #[offset(8)]
77 float_val: f64,
78 }
79
80 #[derive(GameStruct, Debug)]
81 struct WithPointerChain {
82 #[offset(0)]
83 direct: u32,
84 #[offset(8)]
85 #[pointer_chain(0)]
86 through_ptr: u32,
87 }
88
89 fn self_process() -> Process {
90 let pid = std::process::id();
91 Process::attach(pid).expect("failed to attach to self")
92 }
93
94 #[test]
95 fn read_simple_layout() {
96 let data: [u32; 2] = [42, 99];
97 let process = self_process();
98 let base = data.as_ptr() as usize;
99 let result = SimpleLayout::read(&process, base).unwrap();
100 assert_eq!(result.a, 42);
101 assert_eq!(result.b, 99);
102 }
103
104 #[test]
105 fn read_with_gap() {
106 let mut buf = [0u8; 24];
107 let first: u64 = 0xDEAD_BEEF;
108 let second: u64 = 0xCAFE_BABE;
109 buf[0..8].copy_from_slice(&first.to_ne_bytes());
110 buf[16..24].copy_from_slice(&second.to_ne_bytes());
111
112 let process = self_process();
113 let base = buf.as_ptr() as usize;
114 let result = WithGap::read(&process, base).unwrap();
115 assert_eq!(result.first, 0xDEAD_BEEF);
116 assert_eq!(result.second, 0xCAFE_BABE);
117 }
118
119 #[test]
120 fn read_single_field() {
121 let value: f32 = 3.14;
122 let process = self_process();
123 let base = &value as *const f32 as usize;
124 let result = SingleField::read(&process, base).unwrap();
125 assert!((result.value - 3.14).abs() < f32::EPSILON);
126 }
127
128 #[test]
129 fn read_array_field() {
130 let data: [u32; 4] = [10, 20, 30, 40];
131 let process = self_process();
132 let base = data.as_ptr() as usize;
133 let result = ArrayField::read(&process, base).unwrap();
134 assert_eq!(result.values, [10, 20, 30, 40]);
135 }
136
137 #[test]
138 fn read_mixed_types() {
139 let mut buf = [0u8; 16];
140 buf[0] = 0xFF;
141 buf[4..8].copy_from_slice(&42u32.to_ne_bytes());
142 buf[8..16].copy_from_slice(&2.718f64.to_ne_bytes());
143
144 let process = self_process();
145 let base = buf.as_ptr() as usize;
146 let result = MixedTypes::read(&process, base).unwrap();
147 assert_eq!(result.byte_val, 0xFF);
148 assert_eq!(result.int_val, 42);
149 assert!((result.float_val - 2.718).abs() < f64::EPSILON);
150 }
151
152 #[test]
153 fn read_pointer_chain() {
154 let target: u32 = 12345;
155 let target_ptr: usize = &target as *const u32 as usize;
156
157 let mut buf = [0u8; 16];
162 buf[0..4].copy_from_slice(&999u32.to_ne_bytes());
163 buf[8..8 + std::mem::size_of::<usize>()].copy_from_slice(&target_ptr.to_ne_bytes());
164
165 let process = self_process();
166 let base = buf.as_ptr() as usize;
167 let result = WithPointerChain::read(&process, base).unwrap();
168 assert_eq!(result.direct, 999);
169 assert_eq!(result.through_ptr, 12345);
170 }
171
172 #[test]
173 fn read_multi_hop_pointer_chain() {
174 #[derive(GameStruct, Debug)]
175 struct MultiHop {
176 #[offset(0)]
177 #[pointer_chain(0, 0)]
178 value: u64,
179 }
180
181 let target: u64 = 0xBEEF;
182 let target_addr: usize = &target as *const u64 as usize;
183 let mid_ptr: usize = &target_addr as *const usize as usize;
184
185 let base_data: usize = mid_ptr;
187 let process = self_process();
188 let base = &base_data as *const usize as usize;
189 let result = MultiHop::read(&process, base).unwrap();
190 assert_eq!(result.value, 0xBEEF);
191 }
192
193 #[test]
194 fn read_pointer_chain_with_offsets() {
195 #[derive(GameStruct, Debug)]
196 struct OffsetChain {
197 #[offset(0)]
198 #[pointer_chain(8)]
199 value: u32,
200 }
201
202 let mut level2 = [0u8; 12];
204 level2[8..12].copy_from_slice(&7777u32.to_ne_bytes());
205 let level2_addr: usize = level2.as_ptr() as usize;
206
207 let process = self_process();
209 let base = &level2_addr as *const usize as usize;
210 let result = OffsetChain::read(&process, base).unwrap();
211 assert_eq!(result.value, 7777);
212 }
213
214 #[test]
215 fn read_u8_as_flag() {
216 #[derive(GameStruct, Debug)]
217 struct WithFlag {
218 #[offset(0)]
219 alive: u8,
220 #[offset(4)]
221 score: u32,
222 }
223
224 let mut buf = [0u8; 8];
225 buf[0] = 1;
226 buf[4..8].copy_from_slice(&100u32.to_ne_bytes());
227
228 let process = self_process();
229 let base = buf.as_ptr() as usize;
230 let result = WithFlag::read(&process, base).unwrap();
231 assert!(result.alive != 0);
232 assert_eq!(result.score, 100);
233 }
234
235 #[test]
236 fn pointer_chain_null_pointer() {
237 #[derive(GameStruct, Debug)]
238 #[allow(dead_code)]
239 struct NullChain {
240 #[offset(0)]
241 #[pointer_chain(0)]
242 value: u32,
243 }
244
245 let null_ptr: usize = 0;
247 let process = self_process();
248 let base = &null_ptr as *const usize as usize;
249 let result = NullChain::read(&process, base);
250 assert!(result.is_err());
251 }
252
253 #[test]
254 fn read_negative_values() {
255 #[derive(GameStruct, Debug)]
256 struct Signed {
257 #[offset(0)]
258 x: i32,
259 #[offset(4)]
260 y: i32,
261 }
262
263 let data: [i32; 2] = [-50, -100];
264 let process = self_process();
265 let base = data.as_ptr() as usize;
266 let result = Signed::read(&process, base).unwrap();
267 assert_eq!(result.x, -50);
268 assert_eq!(result.y, -100);
269 }
270}