1use super::replay::*;
2use crate::reader::{ReplayBufReadExt, ReplayReadError, ReplayReadExt, ReplayResult};
3use crate::replay::SimData;
4use crate::version::Command;
5use std::io::{BufRead, Read};
6
7impl Command for ReplayCommand {
8 fn as_advance(&self) -> Option<crate::version::Advance> {
9 match self {
10 Self::Advance { ticks } => Some(crate::version::Advance { ticks: *ticks }),
11 _ => None,
12 }
13 }
14
15 fn parse_command_data(command_id: u8, mut reader: &[u8]) -> ReplayResult<ReplayCommand> {
16 use replay_command::*;
17 use ReplayCommand::*;
18
19 let cmd = match command_id {
20 ADVANCE => Advance {
21 ticks: reader.read_u32_le()?,
22 },
23 SET_COMMAND_SOURCE => SetCommandSource {
24 id: reader.read_u8()?,
25 },
26 COMMAND_SOURCE_TERMINATED => CommandSourceTerminated,
27 VERIFY_CHECKSUM => {
28 let mut digest = [0; 16];
29 reader.read_exact(&mut digest)?;
30 VerifyChecksum {
31 digest,
32 tick: reader.read_u32_le()?,
33 }
34 }
35 REQUEST_PAUSE => RequestPause,
36 RESUME => Resume,
37 SINGLE_STEP => SingleStep,
38 CREATE_UNIT => CreateUnit {
39 army: reader.read_u8()?,
40 blueprint: reader.read_string()?,
41 x: reader.read_f32_le()?,
42 z: reader.read_f32_le()?,
43 heading: reader.read_f32_le()?,
44 },
45 CREATE_PROP => CreateProp {
46 blueprint: reader.read_string()?,
47 position: read_position(&mut reader)?,
48 },
49 DESTROY_ENTITY => DestroyEntity {
50 unit: reader.read_u32_le()?,
51 },
52 WARP_ENTITY => WarpEntity {
53 unit: reader.read_u32_le()?,
54 x: reader.read_f32_le()?,
55 y: reader.read_f32_le()?,
56 z: reader.read_f32_le()?,
57 },
58 PROCESS_INFO_PAIR => ProcessInfoPair {
59 unit: reader.read_u32_le()?,
60 arg1: reader.read_string()?,
61 arg2: reader.read_string()?,
62 },
63 ISSUE_COMMAND => IssueCommand(read_game_command(&mut reader)?),
64 COMMAND_16 => Command16(read_game_command(&mut reader)?),
65 ISSUE_FACTORY_COMMAND => IssueFactoryCommand(read_game_command(&mut reader)?),
66 COMMAND_18 => Command18 {
67 id: reader.read_u32_le()?,
68 source_id: reader.read_u8()?,
69 unknown: reader.vec_read_to_end()?,
70 },
71 INCREASE_COMMAND_COUNT => IncreaseCommandCount {
72 id: reader.read_u32_le()?,
73 delta: reader.read_i32_le()?,
74 },
75 DECREASE_COMMAND_COUNT => DecreaseCommandCount {
76 id: reader.read_u32_le()?,
77 delta: reader.read_i32_le()?,
78 },
79 SET_COMMAND_TARGET => SetCommandTarget {
80 id: reader.read_u32_le()?,
81 target: read_target(&mut reader)?,
82 },
83 SET_COMMAND_TYPE => {
84 let id = reader.read_u32_le()?;
85 let type_ = reader.read_u32_le()? as u8;
86 if type_ > game_command::MAX {
87 return Err(ReplayReadError::Malformed("invalid game command"));
88 }
89 SetCommandType { id, type_ }
90 }
91 SET_COMMAND_CELLS => {
92 let id = reader.read_u32_le()?;
95 let cells = reader.read_lua_object()?;
96 if cells.evaluate_as_bool() {
97 reader.skip_buf(1, &mut [0])?;
98 }
99 SetCommandCells {
100 id,
101 cells,
102 position: read_position(&mut reader)?,
103 }
104 }
105 REMOVE_COMMAND_FROM_QUEUE => RemoveCommandFromQueue {
106 id: reader.read_u32_le()?,
107 unit: reader.read_u32_le()?,
108 },
109 DEBUG_COMMAND => DebugCommand {
110 command: reader.read_string()?,
111 position: read_position(&mut reader)?,
112 focus_army: reader.read_u8()?,
113 selection: read_entity_list(&mut reader)?,
114 },
115 EXECUTE_LUA_IN_SIM => ExecuteLuaInSim {
116 code: reader.read_string()?,
117 },
118 LUA_SIM_CALLBACK => {
119 let func = reader.read_string()?;
120 let args = reader.read_lua_object()?;
121
122 let selection = read_entity_list(&mut reader)?;
123 LuaSimCallback {
124 func,
125 args,
126 selection,
127 }
128 }
129 END_GAME => EndGame,
130 id => Unknown {
131 cmd: id,
132 data: reader.vec_read_to_end()?,
133 },
134 };
135 Ok(cmd)
136 }
137
138 fn process_command(sim: &mut SimData, command: &ReplayCommand) -> ReplayResult<()> {
140 use ReplayCommand::*;
141
142 match command {
143 Advance { ticks } => sim.tick += *ticks,
145 SetCommandSource { id } => sim.command_source = *id,
147 CommandSourceTerminated => {
149 sim.players_last_tick.insert(sim.command_source, sim.tick);
150 }
151 VerifyChecksum { digest, tick } => {
156 if sim.checksum_tick < Some(*tick) {
157 sim.checksum_tick.replace(*tick);
159 sim.checksum.copy_from_slice(&digest[..16]);
160 return Ok(());
161 }
162 debug_assert_eq!(
163 sim.checksum_tick,
164 Some(*tick),
165 "Checksum received out of order!"
166 );
167
168 if sim.checksum != digest[..16] {
169 if sim.desync_tick.is_none() {
170 sim.desync_tick.replace(sim.tick);
171 sim.desync_ticks.replace(vec![]);
172 }
173
174 sim.desync_ticks.as_mut().unwrap().push(sim.tick);
175
176 return Err(ReplayReadError::Desynced(sim.tick));
178 }
179 }
180 _ => (),
181 };
182 Ok(())
183 }
184}
185
186#[inline]
187pub fn read_position(reader: &mut impl Read) -> ReplayResult<Position> {
188 Ok(Position {
189 x: reader.read_f32_le()?,
190 y: reader.read_f32_le()?,
191 z: reader.read_f32_le()?,
192 })
193}
194
195pub fn read_entity_list(reader: &mut impl Read) -> ReplayResult<Vec<u32>> {
196 let len = reader.read_u32_le()? as usize;
197 let mut result = Vec::with_capacity(std::cmp::min(len, 16_000));
200
201 for _ in 0..len {
202 result.push(reader.read_u32_le()?);
203 }
204
205 Ok(result)
206}
207
208pub fn read_target(reader: &mut impl Read) -> ReplayResult<Target> {
209 use target_type::*;
210
211 match reader.read_u8()? {
212 NONE => Ok(Target::None),
213 ENTITY => Ok(Target::Entity {
214 id: reader.read_u32_le()?,
215 }),
216 POSITION => Ok(Target::Position(read_position(reader)?)),
217 _ => Err(ReplayReadError::Malformed("invalid target type")),
218 }
219}
220
221pub fn read_formation(reader: &mut impl Read) -> ReplayResult<Option<Formation>> {
222 let type_ = reader.read_i32_le()?;
223 if type_ == -1 {
224 return Ok(None);
225 }
226
227 Ok(Some(Formation {
228 a: reader.read_f32_le()?,
229 b: reader.read_f32_le()?,
230 c: reader.read_f32_le()?,
231 d: reader.read_f32_le()?,
232 scale: reader.read_f32_le()?,
233 }))
234}
235
236pub fn read_cells(reader: &mut impl Read) -> ReplayResult<Vec<Cell>> {
237 let size = reader.read_u32_le()? as usize;
238 let mut result = Vec::with_capacity(std::cmp::min(
240 size,
241 crate::MIB / std::mem::size_of::<Cell>(),
242 ));
243 for _ in 0..size {
244 result.push(Cell {
245 x: reader.read_i16_le()?,
246 z: reader.read_i16_le()?,
247 });
248 }
249 Ok(result)
250}
251
252pub fn read_game_command(reader: &mut (impl Read + BufRead)) -> ReplayResult<GameCommand> {
253 let entity_ids = read_entity_list(reader)?;
254 let id = reader.read_u32_le()?;
255
256 let coordinated_attack_cmd_id = reader.read_i32_le()?;
260 let type_ = reader.read_u8()?;
261
262 if type_ > game_command::MAX {
263 return Err(ReplayReadError::Malformed("invalid game command"));
264 }
265 let arg2 = reader.read_i32_le()?;
268
269 let target = read_target(reader)?;
270 let arg3 = reader.read_u8()?; let formation = read_formation(reader)?;
272
273 let blueprint = reader.by_ref().take(255).read_string()?;
274
275 let arg4 = reader.read_u32_le()?; let arg5 = reader.read_u32_le()?; let arg6 = reader.read_u32_le()?; let mut unknown = [0; 8];
281 reader.read_exact(&mut unknown)?;
282
283 let mut clear_queue = None;
284
285 let ability = reader.read_lua_object()?;
286 if ability.evaluate_as_bool() {
287 clear_queue = Some(reader.read_bool()?);
288 }
289
290 let unknown2 = reader.vec_read_to_end()?;
291
292 Ok(GameCommand {
293 entity_ids,
294 id,
295 coordinated_attack_cmd_id,
296 type_,
297 arg2,
298 target,
299 arg3,
300 formation,
301 blueprint,
302 arg4,
303 arg5,
304 arg6,
305 unknown,
306 ability,
307 clear_queue,
308 unknown2,
309 })
310}