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 Ok(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: 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 ISSUE_FACTORY_COMMAND => IssueFactoryCommand(read_game_command(&mut reader)?),
65 INCREASE_COMMAND_COUNT => IncreaseCommandCount {
66 id: reader.read_u32_le()?,
67 delta: reader.read_i32_le()?,
68 },
69 DECREASE_COMMAND_COUNT => DecreaseCommandCount {
70 id: reader.read_u32_le()?,
71 delta: reader.read_i32_le()?,
72 },
73 SET_COMMAND_TARGET => SetCommandTarget {
74 id: reader.read_u32_le()?,
75 target: read_target(&mut reader)?,
76 },
77 SET_COMMAND_TYPE => {
78 let id = reader.read_u32_le()?;
79 let type_ = reader.read_u32_le()? as u8;
80 if type_ > game_command::MAX {
81 return Err(ReplayReadError::Malformed("invalid game command"));
82 }
83 SetCommandType { id, type_ }
84 }
85 SET_COMMAND_CELLS => {
86 let id = reader.read_u32_le()?;
89 let cells = reader.read_lua_object()?;
90 if cells.evaluate_as_bool() {
91 reader.skip_buf(1, &mut [0])?;
92 }
93 SetCommandCells {
94 id,
95 cells,
96 position: read_position(&mut reader)?,
97 }
98 }
99 REMOVE_COMMAND_FROM_QUEUE => RemoveCommandFromQueue {
100 id: reader.read_u32_le()?,
101 unit: reader.read_u32_le()?,
102 },
103 DEBUG_COMMAND => DebugCommand {
104 command: reader.read_string()?,
105 position: read_position(&mut reader)?,
106 focus_army: reader.read_u8()?,
107 selection: read_entity_list(&mut reader)?,
108 },
109 EXECUTE_LUA_IN_SIM => ExecuteLuaInSim {
110 code: reader.read_string()?,
111 },
112 LUA_SIM_CALLBACK => {
113 let func = reader.read_string()?;
114 let args = reader.read_lua_object()?;
115
116 let selection = read_entity_list(&mut reader)?;
117 LuaSimCallback {
118 func,
119 args,
120 selection,
121 }
122 }
123 END_GAME => EndGame,
124 _ => {
125 return Err(ReplayReadError::Malformed(
126 "encountered unrecognized command",
127 ))
128 }
129 })
130 }
131
132 fn process_command(sim: &mut SimData, command: &ReplayCommand) -> ReplayResult<()> {
134 use ReplayCommand::*;
135
136 match command {
137 Advance { ticks } => sim.tick += *ticks,
139 SetCommandSource { id } => sim.command_source = *id,
141 CommandSourceTerminated => {
143 sim.players_last_tick.insert(sim.command_source, sim.tick);
144 }
145 VerifyChecksum { digest, tick } => {
150 if sim.checksum_tick < Some(*tick) {
151 sim.checksum_tick.replace(*tick);
153 sim.checksum.copy_from_slice(&digest[..16]);
154 return Ok(());
155 }
156 debug_assert_eq!(
157 sim.checksum_tick,
158 Some(*tick),
159 "Checksum received out of order!"
160 );
161
162 if sim.checksum != digest[..16] {
163 if sim.desync_tick.is_none() {
164 sim.desync_tick.replace(sim.tick);
165 sim.desync_ticks.replace(vec![]);
166 }
167
168 sim.desync_ticks.as_mut().unwrap().push(sim.tick);
169
170 return Err(ReplayReadError::Desynced(sim.tick));
172 }
173 }
174 _ => (),
175 };
176 Ok(())
177 }
178}
179
180#[inline]
181pub fn read_position(reader: &mut impl Read) -> ReplayResult<Position> {
182 Ok(Position {
183 x: reader.read_f32_le()?,
184 y: reader.read_f32_le()?,
185 z: reader.read_f32_le()?,
186 })
187}
188
189pub fn read_entity_list(reader: &mut impl Read) -> ReplayResult<Vec<u32>> {
190 let len = reader.read_u32_le()? as usize;
191 let mut result = Vec::with_capacity(std::cmp::min(len, 16_000));
194
195 for _ in 0..len {
196 result.push(reader.read_u32_le()?);
197 }
198
199 Ok(result)
200}
201
202pub fn read_target(reader: &mut impl Read) -> ReplayResult<Target> {
203 use target_type::*;
204
205 match reader.read_u8()? {
206 NONE => Ok(Target::None),
207 ENTITY => Ok(Target::Entity {
208 id: reader.read_u32_le()?,
209 }),
210 POSITION => Ok(Target::Position(read_position(reader)?)),
211 _ => Err(ReplayReadError::Malformed("invalid target type")),
212 }
213}
214
215pub fn read_formation(reader: &mut impl Read) -> ReplayResult<Option<Formation>> {
216 let type_ = reader.read_i32_le()?;
217 if type_ == -1 {
218 return Ok(None);
219 }
220
221 Ok(Some(Formation {
222 a: reader.read_f32_le()?,
223 b: reader.read_f32_le()?,
224 c: reader.read_f32_le()?,
225 d: reader.read_f32_le()?,
226 scale: reader.read_f32_le()?,
227 }))
228}
229
230pub fn read_cells(reader: &mut impl Read) -> ReplayResult<Vec<Cell>> {
231 let size = reader.read_u32_le()? as usize;
232 let mut result = Vec::with_capacity(std::cmp::min(
234 size,
235 crate::MIB / std::mem::size_of::<Cell>(),
236 ));
237 for _ in 0..size {
238 result.push(Cell {
239 x: reader.read_i16_le()?,
240 z: reader.read_i16_le()?,
241 });
242 }
243 Ok(result)
244}
245
246pub fn read_game_command(reader: &mut (impl Read + BufRead)) -> ReplayResult<GameCommand> {
247 let entity_ids = read_entity_list(reader)?;
248 let id = reader.read_u32_le()?;
249
250 let coordinated_attack_cmd_id = reader.read_i32_le()?;
254 let type_ = reader.read_u8()?;
255
256 if type_ > game_command::MAX {
257 return Err(ReplayReadError::Malformed("invalid game command"));
258 }
259 let arg2 = reader.read_i32_le()?;
262
263 let target = read_target(reader)?;
264 let arg3 = reader.read_u8()?; let formation = read_formation(reader)?;
266
267 let blueprint = reader.by_ref().take(255).read_string()?;
268
269 let arg4 = reader.read_u32_le()?; let arg5 = reader.read_u32_le()?; let arg6 = reader.read_u32_le()?; let mut clear_queue = None;
275
276 let upgrades = reader.read_lua_object()?;
277 if upgrades.evaluate_as_bool() {
278 clear_queue = Some(reader.read_bool()?);
279 }
280
281 Ok(GameCommand {
282 entity_ids,
283 id,
284 coordinated_attack_cmd_id,
285 type_,
286 arg2,
287 target,
288 arg3,
289 formation,
290 blueprint,
291 arg4,
292 arg5,
293 arg6,
294 upgrades,
295 clear_queue,
296 })
297}