1use crate::import::soup_def::SoupDef;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
7pub struct Step {
8 pub next: u32,
9 pub operation: Operation,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13#[serde(tag = "type", rename_all = "snake_case")]
14pub enum Operation {
15 Call {
16 call: Call,
17 },
18 ObjectCall {
19 call: Call,
20 },
21 Assign {
22 destination: Place,
23 formula: Formula,
24 },
25 Increment {
26 destination: Place,
27 delta: i32,
28 },
29 If {
30 conditions: Vec<Condition>,
31 target: u32,
32 },
33 Goto {
34 target: u32,
35 },
36 End {
37 target: u32,
38 },
39 Gosub {
40 target: u32,
41 },
42 Return,
43 Endint,
44 Anchor {
45 value: u8,
46 },
47 Yield,
48 TaskStop {
49 label: u32,
50 },
51 TaskPause {
52 label: u32,
53 },
54 TaskResume {
55 label: u32,
56 },
57 ScriptRv {
58 expected: u8,
59 target: u32,
60 save_return: bool,
61 },
62 Unsupported {
63 reason: String,
64 },
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
68pub struct Call {
69 pub function_id: u16,
71 pub kind: CallKind,
72 pub target: Object,
73 pub function: String,
74 pub arguments: Vec<Value>,
75}
76
77#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
78#[serde(rename_all = "snake_case")]
79pub enum CallKind {
80 Task,
81 Multitask,
82 Function,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
86#[serde(tag = "type", rename_all = "snake_case")]
87pub enum Object {
88 Me,
89 Player,
90 Camera,
91 Named { string: u32 },
92 Local { index: u8 },
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
96#[serde(tag = "type", rename_all = "snake_case")]
97pub enum Place {
98 Flag { index: u16 },
99 Local { index: u8 },
100 Property { object: Object, reference: u8 },
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
104#[serde(tag = "type", rename_all = "snake_case")]
105pub enum Value {
106 Integer { value: i32 },
107 String { index: u32 },
108 Read { place: Place },
109 Update { place: Place, delta: i32 },
110 Call { call: Box<Call> },
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
115pub struct Formula {
116 pub first: Value,
117 pub rest: Vec<(u8, Value)>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
122pub struct Condition {
123 pub lhs: Value,
124 pub comparison: u8,
125 pub rhs: Value,
126 pub conjunction: u8,
127}
128
129#[derive(Clone, Copy)]
130enum Mode {
131 Lhs,
132 Rhs,
133 Parameter,
134 Formula,
135}
136
137struct Decoder<'a> {
138 bytes: &'a [u8],
139 cursor: usize,
140 definition: &'a SoupDef,
141 depth: usize,
142}
143
144impl Decoder<'_> {
145 fn read(&mut self, count: usize) -> Result<&[u8], String> {
146 let end = self
147 .cursor
148 .checked_add(count)
149 .ok_or("operand offset overflow")?;
150 let bytes = self
151 .bytes
152 .get(self.cursor..end)
153 .ok_or_else(|| format!("truncated operand at {}", self.cursor))?;
154 self.cursor = end;
155 Ok(bytes)
156 }
157 fn byte(&mut self) -> Result<u8, String> {
158 Ok(self.read(1)?[0])
159 }
160 fn word(&mut self) -> Result<u16, String> {
161 Ok(u16::from_le_bytes(self.read(2)?.try_into().unwrap()))
162 }
163 fn dword(&mut self) -> Result<u32, String> {
164 Ok(u32::from_le_bytes(self.read(4)?.try_into().unwrap()))
165 }
166 fn object(&mut self) -> Result<Object, String> {
167 let kind = self.byte()?;
168 let operand = self.byte()?;
169 Ok(match kind {
170 0 => Object::Me,
171 1 => Object::Player,
172 2 => Object::Camera,
173 4 => Object::Named {
174 string: u32::from(operand),
175 },
176 10 => Object::Local { index: operand },
177 _ => return Err(format!("unsupported object kind {kind}")),
178 })
179 }
180 fn place(&mut self, opcode: u8) -> Result<Place, String> {
181 Ok(match opcode {
182 6 => Place::Flag {
183 index: self.word()?,
184 },
185 10 => Place::Local {
186 index: self.byte()?,
187 },
188 20 => Place::Property {
189 object: self.object()?,
190 reference: self.word()? as u8,
191 },
192 _ => return Err(format!("unsupported assignment opcode {opcode}")),
193 })
194 }
195 fn call(&mut self, target: Object, kind: CallKind) -> Result<Call, String> {
196 let id = self.word()?;
197 let count = if id == 0 { 0 } else { self.byte()? };
198 self.call_arguments(id, count, target, kind)
199 }
200 fn call_arguments(
201 &mut self,
202 id: u16,
203 count: u8,
204 target: Object,
205 kind: CallKind,
206 ) -> Result<Call, String> {
207 let function = self
208 .definition
209 .functions
210 .get(usize::from(id))
211 .ok_or_else(|| format!("SOUP386.DEF function {id} absent"))?;
212 if count != function.param_count {
213 return Err(format!(
214 "function {} requires {} arguments, found {count}",
215 function.name, function.param_count
216 ));
217 }
218 let name = function.name.clone();
219 let mut arguments = Vec::with_capacity(usize::from(count));
220 for _ in 0..count {
221 arguments.push(self.value(Mode::Parameter)?);
222 }
223 Ok(Call {
224 function_id: id,
225 target,
226 kind,
227 function: name,
228 arguments,
229 })
230 }
231 fn kind(&mut self) -> Result<CallKind, String> {
232 match self.byte()? {
233 0 => Ok(CallKind::Task),
234 1 => Ok(CallKind::Multitask),
235 2 => Ok(CallKind::Function),
236 byte => Err(format!("unsupported call kind {byte}")),
237 }
238 }
239 fn value(&mut self, mode: Mode) -> Result<Value, String> {
240 if self.depth == 128 {
242 return Err("expression nesting limit exceeded".into());
243 }
244 self.depth += 1;
245 let result = self.value_inner(mode);
246 self.depth -= 1;
247 result
248 }
249 fn value_inner(&mut self, mode: Mode) -> Result<Value, String> {
250 let opcode = self.byte()?;
251 if matches!(mode, Mode::Parameter) && matches!(opcode, 2 | 25 | 26) {
252 return Err("unsupported native call in parameter mode".into());
253 }
254 Ok(match opcode {
255 7 => Value::Integer {
256 value: self.dword()? as i32,
257 },
258 22 => {
259 let value = self.dword()?;
260 Value::Integer {
261 value: if matches!(mode, Mode::Parameter) {
262 i32::from(value as u16)
263 } else {
264 value as i32
265 },
266 }
267 }
268 21 => Value::String {
269 index: {
270 let value = self.dword()?;
271 if matches!(mode, Mode::Parameter) {
272 u32::from(value as u8)
273 } else {
274 value
275 }
276 },
277 },
278 6 | 10 | 20 => {
279 let place = self.place(opcode)?;
280 let extra = match (opcode, mode) {
281 (6 | 10 | 20, Mode::Lhs | Mode::Rhs) => 1,
282 (6, Mode::Parameter) => 2,
283 (10, Mode::Parameter) => 3,
284 _ => 0,
285 };
286 let trailing = self.read(extra)?;
287 if matches!(mode, Mode::Lhs | Mode::Rhs) {
288 match trailing[0] {
289 0 => Value::Read { place },
290 10 => Value::Update { place, delta: 1 },
291 11 => Value::Update { place, delta: -1 },
292 value => return Err(format!("unsupported conditional update {value}")),
293 }
294 } else {
295 Value::Read { place }
296 }
297 }
298 2 => Value::Call {
299 call: Box::new(self.call(Object::Me, CallKind::Function)?),
300 },
301 25 | 26 => {
302 let target = self.object()?;
303 Value::Call {
304 call: Box::new(self.call(target, CallKind::Function)?),
305 }
306 }
307 _ => return Err(format!("unsupported value opcode {opcode}")),
308 })
309 }
310 fn formula(&mut self) -> Result<Formula, String> {
311 let first = self.value(Mode::Formula)?;
312 let mut rest = Vec::new();
313 loop {
314 let operator = self.byte()?;
315 match operator {
316 0 => break,
317 1..=9 => rest.push((operator, self.value(Mode::Formula)?)),
318 10 | 11 => return Err(format!("invalid formula operator {operator}")),
319 _ => return Err(format!("invalid formula operator {operator}")),
320 }
321 }
322 Ok(Formula { first, rest })
323 }
324 fn statement(&mut self) -> Result<Operation, String> {
325 let opcode = self.byte()?;
326 Ok(match opcode {
327 0..=2 => {
328 let kind =
329 [CallKind::Task, CallKind::Multitask, CallKind::Function][usize::from(opcode)];
330 Operation::Call {
331 call: self.call(Object::Me, kind)?,
332 }
333 }
334 3 => {
335 let mut conditions = Vec::new();
336 loop {
337 let lhs = self.value(Mode::Lhs)?;
338 let comparison = self.byte()?;
339 if comparison > 5 {
340 return Err(format!("invalid comparison {comparison}"));
341 }
342 let rhs = self.value(Mode::Rhs)?;
343 let conjunction = self.byte()?;
344 if conjunction > 2 {
345 return Err(format!("invalid conjunction {conjunction}"));
346 }
347 conditions.push(Condition {
348 lhs,
349 comparison,
350 rhs,
351 conjunction,
352 });
353 if conjunction == 0 {
354 break;
355 }
356 }
357 Operation::If {
358 conditions,
359 target: self.dword()?,
360 }
361 }
362 4 => Operation::Goto {
363 target: self.dword()?,
364 },
365 5 => Operation::End {
366 target: self.dword()?,
367 },
368 6 | 10 | 20 => Operation::Assign {
369 destination: self.place(opcode)?,
370 formula: self.formula()?,
371 },
372 15 | 16 => Operation::Increment {
373 destination: Place::Property {
374 object: self.object()?,
375 reference: self.word()? as u8,
376 },
377 delta: if opcode == 15 { 1 } else { -1 },
378 },
379 17 => Operation::Gosub {
380 target: self.dword()?,
381 },
382 18 => Operation::Return,
383 19 => Operation::Endint,
384 23 => Operation::Anchor {
385 value: self.byte()?,
386 },
387 24 => Operation::Yield,
388 25 | 26 => {
389 let target = self.object()?;
390 let kind = self.kind()?;
391 Operation::ObjectCall {
392 call: self.call(target, kind)?,
393 }
394 }
395 27 => Operation::TaskStop {
396 label: self.dword()?,
397 },
398 28 => Operation::TaskPause {
399 label: self.dword()?,
400 },
401 29 => Operation::TaskResume {
402 label: self.dword()?,
403 },
404 30 => {
405 let expected = self.byte()?;
406 let target = u32::try_from(self.cursor + 5)
407 .map_err(|_| "ScriptRV continuation exceeds u32")?;
408 let opcode = *self
409 .bytes
410 .get(self.cursor)
411 .ok_or("truncated ScriptRV body")?;
412 if opcode == 18 {
413 self.read(4)?;
414 }
415 Operation::ScriptRv {
416 expected,
417 target,
418 save_return: opcode == 17,
419 }
420 }
421 _ => return Err(format!("unsupported opcode {opcode}")),
422 })
423 }
424}
425
426pub fn decode_native_hook(
434 rgm: &super::RgmFile,
435 header_index: usize,
436 definition: &SoupDef,
437) -> Result<Option<Call>, String> {
438 let decode = || -> Result<Option<Call>, String> {
439 let headers = rgm
440 .sections
441 .iter()
442 .find_map(|section| match section {
443 super::RgmSection::Rahd(_, data) => Some(data.as_slice()),
444 _ => None,
445 })
446 .ok_or("RAHD section absent")?;
447 let count = u32::from_le_bytes(
448 headers
449 .get(..4)
450 .ok_or("RAHD count truncated")?
451 .try_into()
452 .unwrap(),
453 );
454 if header_index >= count as usize {
455 return Err("RAHD index outside record count".into());
456 }
457 let start = header_index
458 .checked_mul(165)
459 .and_then(|offset| offset.checked_add(8))
460 .ok_or("RAHD record offset overflow")?;
461 let end = start.checked_add(165).ok_or("RAHD record end overflow")?;
462 let header = headers.get(start..end).ok_or("RAHD record truncated")?;
463 let offset = super::shared::read_i32_le(header, 0x5d).ok_or("RAHD hook offset absent")?;
464 if offset == 0 {
465 return Ok(None);
466 }
467 let offset = usize::try_from(offset).map_err(|_| "negative RAHK offset")?;
468 let bytes = rgm
469 .sections
470 .iter()
471 .find_map(|section| match section {
472 super::RgmSection::Rahk(_, data) => Some(data.as_slice()),
473 _ => None,
474 })
475 .ok_or("RAHK section absent")?;
476 let mut decoder = Decoder {
477 bytes,
478 cursor: offset,
479 definition,
480 depth: 0,
481 };
482 let function = decoder.word()?;
483 let count = decoder.byte()?;
484 if count > 8 {
485 return Err(format!("RAHK argument count {count} exceeds eight"));
486 }
487 let end = decoder
488 .cursor
489 .checked_add(usize::from(count) * 5)
490 .ok_or("RAHK record end overflow")?;
491 decoder.bytes = bytes.get(..end).ok_or("RAHK arguments truncated")?;
492 let call = decoder.call_arguments(function, count, Object::Me, CallKind::Function)?;
493 if decoder.cursor != end {
494 return Err("RAHK arguments do not occupy five bytes each".into());
495 }
496 Ok(Some(call))
497 };
498 decode().map_err(|error| format!("actor header {header_index} native hook: {error}"))
499}
500
501pub fn decode_script_step(
504 bytes: &[u8],
505 offset: usize,
506 definition: &SoupDef,
507) -> Result<Step, String> {
508 let mut decoder = Decoder {
509 bytes,
510 cursor: offset,
511 definition,
512 depth: 0,
513 };
514 let operation = decoder
515 .statement()
516 .map_err(|error| format!("script instruction {offset}: {error}"))?;
517 let next = u32::try_from(decoder.cursor)
518 .map_err(|_| format!("script instruction {offset}: continuation exceeds u32"))?;
519 Ok(Step { next, operation })
520}
521
522pub fn decode_script_program(
526 bytes: &[u8],
527 entry: u32,
528 definition: &SoupDef,
529) -> Result<std::collections::BTreeMap<u32, Step>, String> {
530 use std::collections::{BTreeMap, BTreeSet};
531 let mut pending = BTreeSet::from([0, entry]);
532 let mut steps: BTreeMap<u32, Step> = BTreeMap::new();
533 while let Some(offset) = pending.pop_first() {
534 if steps.contains_key(&offset) {
535 continue;
536 }
537 if offset as usize >= bytes.len() {
538 return Err(format!(
539 "script instruction {offset}: target outside script"
540 ));
541 }
542 if let Some((&start, step)) = steps.range(..offset).next_back()
543 && offset < step.next
544 {
545 return Err(format!(
546 "script instruction {offset}: target inside instruction {start}"
547 ));
548 }
549 let step = match decode_script_step(bytes, offset as usize, definition) {
550 Ok(step) => step,
551 Err(reason) if reason.contains(": unsupported ") => Step {
552 next: offset,
553 operation: Operation::Unsupported { reason },
554 },
555 Err(reason) => return Err(reason),
556 };
557 if let Some((&start, _)) = steps.range(offset..step.next).next() {
558 return Err(format!(
559 "script instruction {offset}: overlaps instruction {start}"
560 ));
561 }
562 match &step.operation {
563 Operation::Goto { target } | Operation::End { target } => {
564 pending.insert(*target);
565 }
566 Operation::If { target, .. }
567 | Operation::Gosub { target }
568 | Operation::ScriptRv { target, .. } => {
569 pending.insert(*target);
570 pending.insert(step.next);
571 }
572 Operation::Return | Operation::Endint | Operation::Unsupported { .. } => {}
573 _ => {
574 pending.insert(step.next);
575 }
576 }
577 steps.insert(offset, step);
578 }
579 Ok(steps)
580}