faf_replay_parser/scfa/
bytes.rs1use crate::reader::ReplayReadError;
8use crate::scfa::replay_command;
9use crate::ReplayResult;
10use byteorder::{ByteOrder, LittleEndian};
11
12pub fn body_offset(data: &[u8]) -> ReplayResult<usize> {
14 unsafe {
15 let mut ptr = data.as_ptr();
16 let end = ptr.add(data.len());
17 let start: usize = ptr as usize;
18
19 ptr = ptr.add(skip_string(ptr, end)?); ptr = ptr.add(skip_string(ptr, end)?); ptr = ptr.add(skip_string(ptr, end)?); ptr = ptr.add(skip_string(ptr, end)?); ptr = ptr.add(read_u32(ptr, end)? as usize + 4); ptr = ptr.add(read_u32(ptr, end)? as usize + 4); verify_len(ptr, end, 1 + 6)?;
30 let num_sources = *ptr as usize;
31 ptr = ptr.add(1);
32
33 for _ in 0..num_sources {
34 ptr = ptr.add(skip_string(ptr, end)?);
36 verify_len(ptr, end, 4 + 6)?;
38 ptr = ptr.add(4);
39 }
40
41 verify_len(ptr, end, 1 + 5)?;
42 ptr = ptr.add(1); verify_len(ptr, end, 1 + 4)?;
44 let army_count = *ptr as usize;
45 ptr = ptr.add(1);
46
47 for _ in 0..army_count {
48 ptr = ptr.add(read_u32(ptr, end)? as usize + 4); verify_len(ptr, end, 1)?;
51 let player_source = *ptr as usize;
52 ptr = ptr.add(1);
53
54 if player_source != 255 {
55 ptr = ptr.add(1);
56 }
57 }
58
59 verify_len(ptr, end, 4)?;
60 ptr = ptr.add(4); Ok(ptr as usize - start)
63 }
64}
65
66pub fn body_ticks(data: &[u8]) -> ReplayResult<u32> {
75 let mut ticks = 0;
76 let mut curr = 0;
77 let end = data.len();
78
79 while curr + 2 < end {
81 let command = unsafe { *data.get_unchecked(curr) };
83 if command > replay_command::MAX {
84 return Err(ReplayReadError::Malformed("invalid command"));
85 }
86 let size =
88 unsafe { LittleEndian::read_u16(data.get_unchecked(curr + 1..curr + 3)) as usize };
89
90 if command == replay_command::ADVANCE && !(end < curr + size) {
92 if size != 7 {
93 return Err(ReplayReadError::Malformed("invalid command size"));
94 }
95 ticks += unsafe { LittleEndian::read_u32(data.get_unchecked(curr + 3..curr + 7)) };
96 }
97 curr += size;
98 }
99
100 Ok(ticks)
101}
102
103pub fn has_frame(data: &[u8]) -> ReplayResult<bool> {
108 if data.is_empty() {
109 return Ok(false);
110 }
111
112 let command = unsafe { *data.get_unchecked(0) };
113 if command > replay_command::MAX {
114 return Err(ReplayReadError::Malformed("invalid command"));
115 }
116
117 if data.len() < 3 {
118 return Ok(false);
119 }
120
121 let size = unsafe { LittleEndian::read_u16(data.get_unchecked(1..3)) as usize };
122 if size < 3 {
123 return Err(ReplayReadError::Malformed("invalid command size"));
124 }
125
126 Ok(!(data.len() < size))
127}
128
129unsafe fn skip_string(mut data: *const u8, end: *const u8) -> ReplayResult<usize> {
130 let mut count = 1;
131
132 while data < end {
133 if *data == b'\x00' {
134 return Ok(count);
135 }
136 data = data.add(1);
137 count += 1;
138 }
139
140 Err(ReplayReadError::Malformed("missing header data"))
141}
142
143#[inline]
144unsafe fn read_u32(ptr: *const u8, end: *const u8) -> ReplayResult<u32> {
145 verify_len(ptr, end, 4)?;
146 Ok(LittleEndian::read_u32(std::slice::from_raw_parts(ptr, 4)))
147}
148
149#[inline]
150unsafe fn verify_len(ptr: *const u8, end: *const u8, needed: usize) -> ReplayResult<()> {
151 return match ptr.add(needed) < end {
152 true => Ok(()),
153 false => Err(ReplayReadError::Malformed("missing header data")),
154 };
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use pretty_assertions::assert_eq;
161
162 #[test]
163 fn test_body_ticks_empty() {
164 assert_eq!(body_ticks(&[]).unwrap(), 0);
165 }
166
167 #[test]
168 fn test_body_ticks_invalid_command() {
169 body_ticks(&[0xff, 0x03, 0x00]).unwrap_err();
170 }
171
172 #[test]
173 fn test_body_ticks_invalid_command_size() {
174 body_ticks(&[0x0, 0x03, 0x00]).unwrap_err();
175 }
176
177 #[test]
178 fn test_body_ticks_exactly_one() {
179 assert_eq!(
180 body_ticks(&[0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00]).unwrap(),
181 1
182 );
183 }
184
185 #[test]
186 fn test_body_ticks_short_by_one() {
187 assert_eq!(
188 body_ticks(&[0x00, 0x07, 0x00, 0x01, 0x00, 0x00]).unwrap(),
189 0
190 );
191 }
192
193 #[test]
194 fn test_body_ticks_missing_data() {
195 assert_eq!(body_ticks(&[0x00, 0x07, 0x00, 0x01]).unwrap(), 0);
196 }
197
198 #[test]
199 fn test_body_ticks_excessive_data() {
200 assert_eq!(
201 body_ticks(&[0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01,])
202 .unwrap(),
203 1
204 );
205 }
206
207 #[test]
208 fn test_has_frame_empty() {
209 assert_eq!(has_frame(&[]).unwrap(), false);
210 }
211
212 #[test]
213 fn test_has_frame_exact() {
214 let data = [0x00, 0x04, 0x00, 0xff];
215 assert_eq!(has_frame(&data).unwrap(), true);
216 }
217
218 #[test]
219 fn test_has_frame_exact_empty_contents() {
220 let data = [0x00, 0x03, 0x00];
221 assert_eq!(has_frame(&data).unwrap(), true);
222 }
223
224 #[test]
225 fn test_has_frame_missing_data() {
226 let data = [0x00, 0x05, 0x00, 0xff];
227 assert_eq!(has_frame(&data).unwrap(), false);
228 }
229
230 #[test]
231 fn test_has_frame_excessive_data() {
232 let data = [0x00, 0x04, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff];
233 assert_eq!(has_frame(&data).unwrap(), true);
234 }
235}