Skip to main content

rgpre/import/rgm/
script_step.rs

1//! Typed literal task and End decoding from the SOUP bytecode specification.
2
3/// A decoded operation, with offsets relative to its actor's RASC script slice.
4#[derive(Debug, PartialEq, Eq)]
5pub struct LiteralScriptStep {
6    /// Byte offset of the following encoded instruction, independent of an End target.
7    pub next: usize,
8    /// The operation; nonliteral operands and other opcodes are explicitly unsupported.
9    pub operation: LiteralScriptOperation,
10}
11
12/// The subset required by literal blocking animation tasks and invocation endings.
13#[derive(Debug, PartialEq, Eq)]
14pub enum LiteralScriptOperation {
15    /// Function ID indexes the installed SOUP386.DEF, including its null entry.
16    Task {
17        function_id: u16,
18        arguments: Vec<i32>,
19    },
20    /// Store this script-relative continuation and yield the current invocation.
21    End { target: u32 },
22}
23
24/// Decodes one operation without executing it or following its continuation.
25/// Errors include the instruction offset; unsupported input is never skipped.
26pub fn decode_literal_script_step(code: &[u8], offset: usize) -> Result<LiteralScriptStep, String> {
27    let mut cursor = offset;
28    let mut read = |count: usize| {
29        let end = cursor
30            .checked_add(count)
31            .ok_or_else(|| format!("script instruction {offset}: operand offset overflow"))?;
32        let bytes = code
33            .get(cursor..end)
34            .ok_or_else(|| format!("script instruction {offset}: truncated operand at {cursor}"))?;
35        cursor = end;
36        Ok::<_, String>(bytes)
37    };
38    let opcode = read(1)?[0];
39    let operation = match opcode {
40        0 => {
41            let bytes = read(2)?;
42            let function_id = u16::from_le_bytes([bytes[0], bytes[1]]);
43            let count = if function_id == 0 { 0 } else { read(1)?[0] };
44            let mut arguments = Vec::with_capacity(usize::from(count));
45            for _ in 0..count {
46                let operand = read(1)?[0];
47                if !matches!(operand, 7 | 22) {
48                    return Err(format!(
49                        "script instruction {offset}: nonliteral task operand {operand} unsupported"
50                    ));
51                }
52                let bytes = read(4)?;
53                arguments.push(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]));
54            }
55            LiteralScriptOperation::Task {
56                function_id,
57                arguments,
58            }
59        }
60        5 => {
61            let bytes = read(4)?;
62            LiteralScriptOperation::End {
63                target: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
64            }
65        }
66        _ => {
67            return Err(format!(
68                "script instruction {offset}: opcode {opcode} unsupported by literal task decoder"
69            ));
70        }
71    };
72    Ok(LiteralScriptStep {
73        next: cursor,
74        operation,
75    })
76}