diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/glk.rs | 3 | ||||
-rw-r--r-- | src/interpreter.rs | 302 | ||||
-rw-r--r-- | src/main.rs | 88 |
3 files changed, 393 insertions, 0 deletions
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); +} |