summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml6
-rw-r--r--rust-toolchain.toml2
-rw-r--r--src/glk.rs3
-rw-r--r--src/interpreter.rs302
-rw-r--r--src/main.rs88
7 files changed, 409 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..11e08a5
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "optimistic-glulxe"
+version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..e8af3ea
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "optimistic-glulxe"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..5d56faf
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
diff --git a/src/glk.rs b/src/glk.rs
new file mode 100644
index 0000000..3efb095
--- /dev/null
+++ b/src/glk.rs
@@ -0,0 +1,3 @@
+pub struct Glk {
+
+}
diff --git a/src/interpreter.rs b/src/interpreter.rs
new file mode 100644
index 0000000..bc51754
--- /dev/null
+++ b/src/interpreter.rs
@@ -0,0 +1,302 @@
+use core::panic;
+
+use crate::glk::Glk;
+
+pub type MemAddress = u32;
+pub type StackAddress = u32;
+pub type FunctionPointer = MemAddress;
+
+enum StubDestType {
+    DoNotStore = 0,
+    MainMemory = 1,
+    LocalVariable = 2,
+    PushOnStack = 3,
+    ResumeCompressedString = 10,
+    ResumeFunctionFromString = 11,
+    ResumeInteger = 12,
+    ResumeCString = 13,
+    ResumeUnicodeString = 14,
+}
+
+enum Nybble {
+    High,
+    Low,
+}
+
+enum Operand {
+    Constant(i32),
+    PopStack,
+    FrameIndirect(StackAddress),
+    MemIndirect(MemAddress),
+    MemRamIndirect(MemAddress),
+}
+
+enum ReadWriteDest {
+    Stack,
+    Memory,
+}
+
+pub struct HeapChunk {
+    pub start_address: MemAddress,
+    pub length: u32,
+    pub contents: Option<Vec<u32>>
+}
+
+pub enum Output {
+    Null,
+    Filter(FunctionPointer),
+    Glk(Glk),
+}
+
+pub struct Interpreter {
+    memory: Vec<u8>,
+    stack: Vec<u8>,
+    mem_writeable: MemAddress,
+    mem_min: MemAddress,
+    heap: Vec<HeapChunk>,
+
+    reg_program: MemAddress,
+    reg_stack: StackAddress,
+    reg_frame: StackAddress,
+    reg_strdec: MemAddress,
+
+    output_mode: Output,
+}
+
+impl Interpreter {
+    pub fn init(
+        mut source_file: Vec<u8>,
+        ram_start: u32,
+        ext_start: u32,
+        ram_end: u32,
+        stack_size: u32,
+        str_decode: u32,
+    ) -> Self {
+        // Trick to ensure everything after ext_start is cleared
+        // even if the original file was longer
+        source_file.resize(ext_start as usize, 0);
+        source_file.resize(ext_start as usize, 0);
+
+        let mut stack = Vec::new();
+        stack.resize(stack_size as usize, 0);
+        
+        Self {
+            memory: source_file,
+            stack,
+            mem_writeable: ram_start,
+            mem_min: ram_end,
+            heap: Vec::new(),
+
+            reg_program: 0,
+            reg_stack: 0,
+            reg_frame: 0,
+            reg_strdec: str_decode,
+
+            output_mode: Output::Null,
+        }
+    }
+
+    pub fn run(&mut self, entry_func: MemAddress) {
+        self.reg_program = self.create_stack_frame(entry_func, Vec::new());
+
+        loop {
+            let instruction = self.load_program_instruction();
+
+            match instruction {
+                _ => todo!("Haven't done any of the instruction loading yet")
+            }
+        }
+    }
+
+    fn create_return_stub(&mut self, dest_type: StubDestType, dest_addr: u32, program_counter: MemAddress, frame_pointer: StackAddress) {
+
+    }
+
+    // Returns program counter after parsing all the args
+    fn create_stack_frame(&mut self, func: FunctionPointer, mut args: Vec<u32>) -> MemAddress {
+        println!("create_stack_frame: Loading function from {func:x}");
+
+        println!("Initial frame ptr: {}", self.reg_frame);
+        println!("Initial stack ptr: {}", self.reg_stack);
+
+        // Reading function signature
+        let func_type = self.get_byte(func, ReadWriteDest::Memory);
+        let args_in_locals = match func_type {
+            0xC0 => false,
+            0xC1 => true,
+            _ => panic!("Tried to call object that is not a function: {}", func_type),
+        };
+
+        let mut local_pointer = func + 1;
+        let mut format_of_locals = Vec::<(u8, u8)>::new();
+        loop {
+            let format = (
+                self.get_byte(local_pointer, ReadWriteDest::Memory),
+                self.get_byte(local_pointer + 1, ReadWriteDest::Memory),
+            );
+
+            local_pointer += 2;
+
+            if format == (0,0) {
+                break
+            }
+
+            format_of_locals.push(format);
+        };
+
+        let opcode_start = local_pointer + 1;
+
+        // Writing stack frame
+        self.reg_frame = self.reg_stack;
+        let mut header_len = 4 + 4 + format_of_locals.len() as StackAddress * 2;
+        Interpreter::align_ptr(&mut header_len, 16);
+
+        // Write locals pos
+        println!("Writing locals pos ({header_len}) to frame + 4 ({})", self.reg_frame + 4);
+        self.write_word(self.reg_frame + 4, ReadWriteDest::Stack, header_len);
+
+        // Write format of locals
+        let mut local_format_ptr = self.reg_frame + 8;
+        for (local_type, local_count) in format_of_locals.iter() {
+            println!("Writing local format ({}, {}) to {}", local_type, local_count, local_format_ptr);
+            self.write_byte(local_format_ptr, ReadWriteDest::Stack, *local_type);
+            self.write_byte(local_format_ptr + 1, ReadWriteDest::Stack, *local_count);
+            local_format_ptr += 2;
+        }
+
+        // Write locals
+        let mut local_pos = self.reg_frame+ header_len;
+        for (local_type, local_count) in format_of_locals {
+            Interpreter::align_ptr(&mut local_pos, local_type * 8);
+
+            for _count in 0..local_count {
+                let value = match args_in_locals {
+                    true => if args.len() > 0 {args.remove(0)} else {0}
+                    false => 0,
+                };
+
+                println!("Writing local variable ({}, {} bytes) to {}", value, local_type, local_pos);
+
+                match local_type {
+                    1 => self.write_byte(local_pos, ReadWriteDest::Stack, value as u8),
+                    2 => self.write_twobyte(local_pos, ReadWriteDest::Stack, value as u16),
+                    4 => self.write_word(local_pos, ReadWriteDest::Stack, value),
+                    _ => panic!("Invalid local type {local_type}")
+                }
+
+                local_pos += local_type as u32;
+            }
+        }
+        
+        // Write frame len
+        Interpreter::align_ptr(&mut local_pos, 32);
+        println!("Writing frame len ({}) to frame ptr ({})", local_pos - self.reg_frame, self.reg_frame);
+        self.write_word(self.reg_frame, ReadWriteDest::Stack, local_pos - self.reg_frame);
+        self.reg_stack = local_pos;
+
+        // Handle stack args
+        if !args_in_locals {
+            args.reverse();
+            for arg in args {
+                println!("Adding value to stack {arg}");
+                self.push_stack(arg);
+            }
+        }
+
+        println!("Ending frame ptr: {}", self.reg_frame);
+        println!("Ending stack ptr: {}", self.reg_stack);
+
+        for [a, b, c, d] in self.stack[self.reg_frame as usize..self.reg_stack as usize].iter()
+            .array_chunks::<4>() {
+                println!("{a:02x} {b:02x} {c:02x} {d:02x} ({})", u32::from_be_bytes([*a,*b,*c,*d]));
+            }
+
+        return opcode_start;
+    }
+
+    fn load_program_instruction(&mut self) {
+
+    }
+
+    fn push_stack(&mut self, value: u32) {
+        self.write_word(self.reg_stack, ReadWriteDest::Stack, value);
+        self.reg_stack += 4;
+    }
+
+    fn pop_stack(&mut self) -> u32 {
+        self.reg_stack -= 4;
+        self.get_word(self.reg_stack, ReadWriteDest::Stack)
+    }
+
+    fn get_byte(&self, addr: u32, src: ReadWriteDest) -> u8 {
+        let addr = addr as usize;
+        let buffer = match src {
+            ReadWriteDest::Stack => &self.stack,
+            ReadWriteDest::Memory => &self.memory,
+        };
+
+        buffer[addr]
+    }
+
+    fn get_twobyte(&self, addr: u32, src: ReadWriteDest) -> u16 {
+        let addr = addr as usize;
+        let buffer = match src {
+            ReadWriteDest::Stack => &self.stack,
+            ReadWriteDest::Memory => &self.memory,
+        };
+
+        u16::from_be_bytes(buffer[addr..addr+2].try_into().unwrap())
+    }
+
+    fn get_word(&self, addr: u32, src: ReadWriteDest) -> u32 {
+        let addr = addr as usize;
+        let buffer = match src {
+            ReadWriteDest::Stack => &self.stack,
+            ReadWriteDest::Memory => &self.memory,
+        };
+
+        u32::from_be_bytes(buffer[addr..addr+4].try_into().unwrap())
+    }
+
+    fn write_byte(&mut self, addr: u32, dst: ReadWriteDest, value: u8) {
+        let addr = addr as usize;
+        let buffer = match dst {
+            ReadWriteDest::Stack => &mut self.stack,
+            ReadWriteDest::Memory => &mut self.memory,
+        };
+
+        buffer[addr] = value;
+    }
+
+    fn write_twobyte(&mut self, addr: u32, dst: ReadWriteDest, value: u16) {
+        let addr = addr as usize;
+        let buffer = match dst {
+            ReadWriteDest::Stack => &mut self.stack,
+            ReadWriteDest::Memory => &mut self.memory,
+        };
+
+        buffer[addr..addr+2].copy_from_slice(&value.to_be_bytes());
+    }
+
+    fn write_word(&mut self, addr: u32, dst: ReadWriteDest, value: u32) {
+        let addr = addr as usize;
+        let buffer = match dst {
+            ReadWriteDest::Stack => &mut self.stack,
+            ReadWriteDest::Memory => &mut self.memory,
+        };
+
+        buffer[addr..addr+4].copy_from_slice(&value.to_be_bytes());
+    }
+
+    fn align_ptr(ptr: &mut u32, align_size: u8) {
+        let modulo = match align_size {
+            8 => 1,
+            16 => 2,
+            32 => 4,
+            _ => panic!("Invalid alignment: {align_size}"),
+        };
+
+        let bytes_off = *ptr % modulo;
+        *ptr += bytes_off;
+    }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..33df203
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,88 @@
+#![feature(ascii_char, iter_array_chunks)]
+mod interpreter;
+mod glk;
+use interpreter::Interpreter;
+
+use std::{env, fs, process::exit};
+
+const MAJOR_VERSION : u16 = 3;
+const MINOR_VERSION : u8  = 1;
+const PATCH_VERSION : u8  = 3;
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+
+    if args.len() < 2 {
+        println!("No story file specified");
+        exit(1);
+    }
+
+    if args.len() > 2 {
+        println!("Too many arguments");
+        exit(1);
+    }
+
+    let path = &args[1];
+    let file_data: Vec<u8> = fs::read(path).expect("Could not open file");
+
+    let _magic_number = &file_data[0..4]
+        .as_ascii()
+        .map(|mn| if mn.as_str().eq("Glul") {Some(mn.as_str())} else {None})
+        .flatten()
+        .unwrap_or_else(|| panic!("Invalid magic number: {} {} {} {}", file_data[0], file_data[1], file_data[2], file_data[3]));
+
+    let version_major = u16::from_be_bytes(file_data[4..6].try_into().unwrap());
+    let version_minor = file_data[6];
+    let version_patch = file_data[7];
+    let ram_start =     u32::from_be_bytes(file_data[8..12].try_into().unwrap());
+    let ext_start =     u32::from_be_bytes(file_data[12..16].try_into().unwrap());
+    let end_mem =       u32::from_be_bytes(file_data[16..20].try_into().unwrap());
+    let stack_size =    u32::from_be_bytes(file_data[20..24].try_into().unwrap());
+    let start_func =    u32::from_be_bytes(file_data[24..28].try_into().unwrap());
+    let str_decode =    u32::from_be_bytes(file_data[28..32].try_into().unwrap());
+    let exp_checksum =  u32::from_be_bytes(file_data[32..36].try_into().unwrap());
+
+    let actual_sum = file_data.iter()
+        .map(|b| *b)
+        .array_chunks::<4>()
+        .map(|bytes| u32::from_be_bytes(bytes))
+        // Subtract expected as we're supposed to compute it with that blanked
+        .fold(0u32, |a,b| a.overflowing_add(b).0) - exp_checksum;
+
+    if file_data.len() % 4 != 0 {
+        panic!("Invalid checksum: Uneven number of bytes");
+    }
+
+    if exp_checksum != actual_sum {
+        panic!("Invalid checksum: Expected {exp_checksum}, got {actual_sum}");
+    }
+
+    if ram_start % 256 != 0 || ext_start % 256 != 0 || end_mem % 256 != 0 {
+        panic!("Invalid memory boundaries");
+    }
+
+    if stack_size % 256 != 0 {
+        panic!("Invalid stack size");
+    }
+
+    if version_major != 2 && version_major != 3 {
+        panic!("This glulx implementation cannot handle version {version_major}, it is version {MAJOR_VERSION}");
+    }
+
+    if version_major == MAJOR_VERSION && version_minor > MINOR_VERSION {
+        panic!("This file is a newer minor version than this glulx implementation ({version_major}.{version_minor} > {MAJOR_VERSION}.{MINOR_VERSION})");
+    }
+
+    println!("Loaded {path}, ({} bytes, file version {version_major}.{version_minor}.{version_patch}, glulx {MAJOR_VERSION}.{MINOR_VERSION}.{PATCH_VERSION})", file_data.len());
+
+    let mut interpreter = Interpreter::init(
+        file_data,
+        ram_start,
+        ext_start,
+        end_mem,
+        stack_size,
+        str_decode
+    );
+
+    interpreter.run(start_func);
+}