Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| use std::boxed::Box; | |
| use std::collections::{HashMap, HashSet}; | |
| use std::env; | |
| use std::fmt; | |
| use std::fmt::Write as FmtWrite; | |
| use std::fs::File; | |
| use std::io::prelude::*; | |
| use std::path::PathBuf; | |
| use std::process; | |
| use std::str; | |
| use base64; | |
| use enum_primitive::FromPrimitive; | |
| use rand; | |
| use rand::{Rng, SeedableRng}; | |
| use serde_json; | |
| use buffer::Buffer; | |
| use frame::Frame; | |
| use instruction::Branch; | |
| use instruction::Instruction; | |
| use instruction::Opcode; | |
| use instruction::Operand; | |
| use instruction::OperandType; | |
| use options::Options; | |
| use quetzal::QuetzalSave; | |
| use traits::UI; | |
| #[derive(Debug)] | |
| enum ZStringState { | |
| Alphabet(usize), | |
| Abbrev(u8), | |
| Tenbit1, | |
| Tenbit2(u8), | |
| } | |
| #[derive(Debug, Serialize)] | |
| pub struct Object { | |
| number: u16, | |
| name: String, | |
| children: Vec<Box<Object>>, | |
| } | |
| impl Object { | |
| fn new(number: u16, zvm: &Zmachine) -> Box<Object> { | |
| let mut name = if number > 0 { | |
| zvm.get_object_name(number) | |
| } else { | |
| String::from("(Null Object)") | |
| }; | |
| if name == "" { | |
| name += "(No Name)"; | |
| } | |
| Box::new(Object { | |
| number, | |
| name, | |
| children: Vec::new(), | |
| }) | |
| } | |
| fn print_tree(&self, indent: &str, mut depth: u8, is_last: bool) -> String { | |
| let mut next = String::from(indent); | |
| let mut out = String::new(); | |
| if depth == 0 { | |
| out += &format!("{} ({})\n", self.name, self.number); | |
| } else { | |
| out += &format!( | |
| "{}{}── {} ({})\n", | |
| indent, | |
| if is_last { "└" } else { "├" }, | |
| self.name, | |
| self.number | |
| ); | |
| next += if is_last { " " } else { "| " }; | |
| } | |
| depth += 1; | |
| for (i, child) in self.children.iter().enumerate() { | |
| let is_last_child = i == self.children.len() - 1; | |
| out += &(**child).print_tree(&next, depth, is_last_child); | |
| } | |
| out | |
| } | |
| fn to_string(&self) -> String { | |
| if !self.children.is_empty() { | |
| self.print_tree("", 0, false) | |
| } else { | |
| format!("{} ({})", self.name, self.number) | |
| } | |
| } | |
| } | |
| impl fmt::Display for Object { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| write!(f, "{}", self.to_string()) | |
| } | |
| } | |
| #[derive(Debug)] | |
| struct ObjectProperty { | |
| num: u8, | |
| len: u8, | |
| addr: usize, | |
| next: usize, | |
| } | |
| impl ObjectProperty { | |
| fn zero() -> ObjectProperty { | |
| ObjectProperty { | |
| num: 0, | |
| addr: 0, | |
| len: 0, | |
| next: 0, | |
| } | |
| } | |
| } | |
| pub struct Zmachine { | |
| pub ui: Box<UI>, | |
| pub options: Options, | |
| pub instr_log: String, | |
| version: u8, | |
| memory: Buffer, | |
| original_dynamic: Vec<u8>, | |
| save_dir: String, | |
| save_name: String, | |
| static_start: usize, | |
| routine_offset: usize, | |
| string_offset: usize, | |
| alphabet: [Vec<String>; 3], | |
| abbrev_table: usize, | |
| separators: Vec<char>, | |
| dictionary: HashMap<String, usize>, | |
| frames: Vec<Frame>, | |
| initial_pc: usize, | |
| pc: usize, | |
| globals_addr: usize, | |
| prop_defaults: usize, | |
| obj_table_addr: usize, | |
| obj_size: usize, | |
| attr_width: usize, | |
| paused_instr: Option<Instruction>, | |
| current_state: Option<(String, Vec<u8>)>, | |
| undos: Vec<(String, Vec<u8>)>, | |
| redos: Vec<(String, Vec<u8>)>, | |
| rng: rand::XorShiftRng, | |
| } | |
| impl Zmachine { | |
| pub fn new(data: Vec<u8>, ui: Box<UI>, options: Options) -> Zmachine { | |
| let memory = Buffer::new(data); | |
| let version = memory.read_byte(0x00); | |
| let initial_pc = memory.read_word(0x06) as usize; | |
| let prop_defaults = memory.read_word(0x0A) as usize; | |
| let static_start = memory.read_word(0x0E) as usize; | |
| let alphabet = if version >= 5 { | |
| Zmachine::load_alphabet(&memory) | |
| } else { | |
| Zmachine::default_alphabet() | |
| }; | |
| let mut zvm = Zmachine { | |
| version, | |
| ui, | |
| save_dir: options.save_dir.clone(), | |
| save_name: format!("{}.sav", &options.save_name), | |
| instr_log: String::new(), | |
| original_dynamic: memory.slice(0, static_start).to_vec(), | |
| globals_addr: memory.read_word(0x0C) as usize, | |
| routine_offset: memory.read_word(0x28) as usize, | |
| string_offset: memory.read_word(0x2A) as usize, | |
| static_start, | |
| initial_pc, | |
| pc: initial_pc, | |
| frames: vec![Frame::empty()], | |
| alphabet, | |
| abbrev_table: memory.read_word(0x18) as usize, | |
| separators: Vec::new(), | |
| dictionary: HashMap::new(), | |
| prop_defaults, | |
| obj_table_addr: prop_defaults + (if version <= 3 { 31 } else { 63 }) * 2, | |
| obj_size: if version <= 3 { 9 } else { 14 }, | |
| attr_width: if version <= 3 { 4 } else { 6 }, | |
| paused_instr: None, | |
| current_state: None, | |
| undos: Vec::new(), | |
| redos: Vec::new(), | |
| rng: rand::SeedableRng::from_seed(options.rand_seed.clone()), | |
| memory, | |
| options, | |
| }; | |
| // read into dictionary & word separators | |
| zvm.populate_dictionary(); | |
| zvm | |
| } | |
| #[allow(dead_code)] | |
| fn calculate_checksum(memory: &Buffer) -> u16 { | |
| let mut sum: usize = 0; | |
| let len = memory.read_byte(0x1A) as usize * 2; | |
| for i in 0x40..len { | |
| sum += memory.read_byte(i) as usize; | |
| } | |
| (sum % 0x1_0000) as u16 | |
| } | |
| fn to_alphabet_entry(s: &str) -> Vec<String> { | |
| s.chars().map(|c| c.to_string()).collect() | |
| } | |
| #[allow(non_snake_case)] | |
| fn default_alphabet() -> [Vec<String>; 3] { | |
| let A0 = " .....abcdefghijklmnopqrstuvwxyz"; | |
| let A1 = " .....ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | |
| let A2 = " ......\n0123456789.,!?_#'\"/\\-:()"; | |
| [ | |
| Zmachine::to_alphabet_entry(A0), | |
| Zmachine::to_alphabet_entry(A1), | |
| Zmachine::to_alphabet_entry(A2), | |
| ] | |
| } | |
| #[allow(non_snake_case)] | |
| fn load_alphabet(memory: &Buffer) -> [Vec<String>; 3] { | |
| let alphabet_addr = memory.read_word(0x34) as usize; | |
| if alphabet_addr == 0 { | |
| Zmachine::default_alphabet() | |
| } else { | |
| let A0 = format!(" .....{}", str::from_utf8(memory.read(alphabet_addr, 26)).expect("bad alphabet table A0!")); | |
| let A1 = format!(" .....{}", str::from_utf8(memory.read(alphabet_addr + 26, 26)).expect("bad alphabet table A1!")); | |
| // First two characters are ignored and accounted for in our padding. | |
| let A2 = format!(" ......\n{}", str::from_utf8(memory.read(alphabet_addr + 26 + 26 + 2, 24)).expect("Bad alphabet table A2!")); | |
| [ | |
| Zmachine::to_alphabet_entry(&A0), | |
| Zmachine::to_alphabet_entry(&A1), | |
| Zmachine::to_alphabet_entry(&A2), | |
| ] | |
| } | |
| } | |
| fn unpack(&self, addr: u16) -> usize { | |
| let addr = addr as usize; | |
| match self.version { | |
| 1...3 => addr * 2, | |
| 4...7 => addr * 4, | |
| 8 => addr * 8, | |
| _ => unreachable!(), | |
| } | |
| } | |
| fn unpack_routine_addr(&self, addr: u16) -> usize { | |
| match self.unpack(addr) { | |
| x @ 6...7 => x + self.routine_offset * 8, | |
| x => x, | |
| } | |
| } | |
| fn unpack_print_paddr(&self, addr: u16) -> usize { | |
| match self.unpack(addr) { | |
| x @ 6...7 => x + self.string_offset * 8, | |
| x => x, | |
| } | |
| } | |
| fn read_global(&self, index: u8) -> u16 { | |
| if index > 240 { | |
| panic!("Can't read global{}!", index); | |
| } | |
| let addr = self.globals_addr + index as usize * 2; | |
| self.memory.read_word(addr) | |
| } | |
| fn write_global(&mut self, index: u8, value: u16) { | |
| if index > 240 { | |
| panic!("Can't write global{}!", index); | |
| } | |
| let addr = self.globals_addr + index as usize * 2; | |
| self.memory.write_word(addr, value); | |
| } | |
| fn read_local(&self, index: u8) -> u16 { | |
| self.frames | |
| .last() | |
| .expect("Can't write local, no frames!") | |
| .read_local(index) | |
| } | |
| fn write_local(&mut self, index: u8, value: u16) { | |
| self.frames | |
| .last_mut() | |
| .expect("Can't write local, no frames!") | |
| .write_local(index, value); | |
| } | |
| fn stack_push(&mut self, value: u16) { | |
| self.frames | |
| .last_mut() | |
| .expect("Can't push to stack, no frames!") | |
| .stack_push(value); | |
| } | |
| fn stack_pop(&mut self) -> u16 { | |
| self.frames | |
| .last_mut() | |
| .expect("Can't pop stack, no frames!") | |
| .stack_pop() | |
| } | |
| fn stack_peek(&mut self) -> u16 { | |
| self.frames | |
| .last_mut() | |
| .expect("Can't peek stack, no frames!") | |
| .stack_peek() | |
| } | |
| fn read_variable(&mut self, index: u8) -> u16 { | |
| match index { | |
| 0 => self.stack_pop(), | |
| 1...15 => self.read_local(index - 1), | |
| 16...255 => self.read_global(index - 16), | |
| _ => unreachable!(), | |
| } | |
| } | |
| fn read_indirect_variable(&mut self, index: u8) -> u16 { | |
| match index { | |
| 0 => self.stack_peek(), | |
| 1...15 => self.read_local(index - 1), | |
| 16...255 => self.read_global(index - 16), | |
| _ => unreachable!(), | |
| } | |
| } | |
| fn write_variable(&mut self, index: u8, value: u16) { | |
| match index { | |
| 0 => self.stack_push(value), | |
| 1...15 => self.write_local(index - 1, value), | |
| 16...255 => self.write_global(index - 16, value), | |
| _ => unreachable!(), | |
| } | |
| } | |
| fn write_indirect_variable(&mut self, index: u8, value: u16) { | |
| match index { | |
| 0 => { | |
| self.stack_pop(); | |
| self.stack_push(value); | |
| } | |
| 1...15 => self.write_local(index - 1, value), | |
| 16...255 => self.write_global(index - 16, value), | |
| _ => unreachable!(), | |
| } | |
| } | |
| fn get_abbrev(&self, index: u8) -> String { | |
| if index > 96 { | |
| panic!("Bad abbrev index: {}", index); | |
| } | |
| let offset = 2 * index as usize; | |
| let word_addr = self.memory.read_word(self.abbrev_table + offset); | |
| let addr = word_addr * 2; // "Word addresses are used only in the abbreviations table" - 1.2.2 | |
| self.read_zstring_from_abbrev(addr as usize) | |
| } | |
| fn read_zstring_from_abbrev(&self, addr: usize) -> String { | |
| self.read_zstring_impl(addr, false) | |
| } | |
| fn read_zstring(&self, addr: usize) -> String { | |
| self.read_zstring_impl(addr, true) | |
| } | |
| fn read_zstring_impl(&self, addr: usize, allow_abbrevs: bool) -> String { | |
| use self::ZStringState::*; | |
| let mut state = Alphabet(0); | |
| let mut index = addr; | |
| let mut zstring = String::new(); | |
| // this closure borrows the zstring while it steps through each zchar. | |
| // (wrapped here in its own scope to force the borrow to end) | |
| { | |
| let mut step = |zchar: u8| { | |
| state = match (zchar, &state) { | |
| // the next zchar will be an abbrev index | |
| (zch, &Alphabet(_)) if zch >= 1 && zch <= 3 => { | |
| assert!(allow_abbrevs, "Abbrev at {} contained recursive abbrev!", addr); | |
| Abbrev(zch) | |
| } | |
| // shift character for the next zchar | |
| (4, &Alphabet(_)) => Alphabet(1), | |
| (5, &Alphabet(_)) => Alphabet(2), | |
| // special 10bit case, next 2 zchars = one 10bit zscii char | |
| (6, &Alphabet(2)) => Tenbit1, | |
| (_, &Tenbit1) => Tenbit2(zchar), | |
| (_, &Tenbit2(first)) => { | |
| let letter = ((first << 5) + zchar) as char; | |
| zstring.push_str(&letter.to_string()); | |
| Alphabet(0) | |
| } | |
| // get the abbrev at this addr | |
| (_, &Abbrev(num)) => { | |
| let abbrev = self.get_abbrev((num - 1) * 32 + zchar); | |
| zstring.push_str(&abbrev); | |
| Alphabet(0) | |
| } | |
| // normal case, adds letter from correct alphabet and resets to A0 | |
| (_, &Alphabet(num)) => { | |
| let letter = &self.alphabet[num][zchar as usize]; | |
| zstring.push_str(letter); | |
| Alphabet(0) | |
| } | |
| }; | |
| }; | |
| // 3 zchars per each 16 bit word + a "stop" bit on top | |
| // 0 10101 01010 10101 | |
| loop { | |
| let word = self.memory.read_word(index); | |
| index += 2; | |
| step(((word >> 10) & 0b0001_1111) as u8); | |
| step(((word >> 5) & 0b0001_1111) as u8); | |
| step((word & 0b0001_1111) as u8); | |
| // stop bit | |
| if word & 0x8000 != 0 { | |
| break; | |
| } | |
| } | |
| } // <- drop process closure, ending zstring borrow | |
| zstring | |
| } | |
| // reads the ENCODED byte length of a zstring, how many consecutive | |
| // bytes in memory it is (not just the number of characters) | |
| fn zstring_length(&self, addr: usize) -> usize { | |
| let mut length = 0; | |
| loop { | |
| let word = self.memory.read_word(addr + length); | |
| length += 2; | |
| // stop bit | |
| if word & 0x8000 != 0 { | |
| break; | |
| } | |
| } | |
| length | |
| } | |
| fn populate_dictionary(&mut self) { | |
| let dictionary_start = self.memory.read_word(0x08) as usize; | |
| let mut read = self.memory.get_reader(dictionary_start); | |
| let separator_count = read.byte(); | |
| for _ in 0..separator_count { | |
| self.separators.push(read.byte() as char); | |
| } | |
| let entry_length = read.byte() as usize; | |
| let entry_count = read.word() as usize; | |
| let entry_start = read.position(); | |
| for n in 0..entry_count { | |
| let addr = entry_start + n * entry_length; | |
| let entry = self.read_zstring(addr); | |
| self.dictionary.insert(entry, addr); | |
| } | |
| } | |
| fn check_dict(&self, word: &str) -> usize { | |
| let length = if self.version <= 3 { 6 } else { 9 }; | |
| let mut short = word.to_string(); | |
| short.truncate(length); | |
| match self.dictionary.get(&short) { | |
| Some(addr) => *addr, | |
| None => 0, | |
| } | |
| } | |
| fn tokenise(&mut self, text: &str, parse_addr: usize) { | |
| // v1-4 start storing @ byte 1, v5+ start @2; | |
| let start = if self.version <= 4 { 1 } else { 2 }; | |
| let mut input = String::from(text); | |
| let mut found = HashMap::new(); | |
| for sep in &self.separators { | |
| input = input.replace(&sep.to_string(), &format!(" {} ", sep)) | |
| } | |
| let tokens: Vec<_> = input | |
| .split_whitespace() | |
| .filter(|token| !token.is_empty()) | |
| .map(|token| { | |
| let offset = found.entry(token).or_insert(0); | |
| let position = text[*offset..].find(token).unwrap(); | |
| let dict_addr = self.check_dict(token); | |
| let token_addr = *offset + position + start; | |
| *offset += position + token.len(); | |
| (dict_addr, token.len(), token_addr) | |
| }) | |
| .collect(); | |
| let mut write = self.memory.get_writer(parse_addr + 1); | |
| write.byte(tokens.len() as u8); | |
| tokens.iter().for_each(|&(dict_addr, len, token_addr)| { | |
| write.word(dict_addr as u16); | |
| write.byte(len as u8); | |
| write.byte(token_addr as u8); | |
| }); | |
| } | |
| fn get_object_addr(&self, object: u16) -> usize { | |
| if object == 0 { | |
| return self.obj_table_addr; | |
| } | |
| self.obj_table_addr + ((object as usize - 1) * self.obj_size) | |
| } | |
| fn get_object_prop_table_addr(&self, object: u16) -> usize { | |
| let addr = self.get_object_addr(object) | |
| // skip attributes | |
| + self.attr_width | |
| // ship parent/child/sibling data | |
| + if self.version <= 3 { 3 } else { 6 }; | |
| // the property table address is in the next word: | |
| self.memory.read_word(addr) as usize | |
| } | |
| // Object name is found at the start the object's property table: | |
| // text-length text of short name of object | |
| // ---byte---- --some even number of bytes-- | |
| fn get_object_name(&self, object: u16) -> String { | |
| let addr = self.get_object_prop_table_addr(object); | |
| let text_length = self.memory.read_byte(addr); | |
| if text_length > 0 { | |
| self.read_zstring(addr + 1) | |
| } else { | |
| String::new() | |
| } | |
| } | |
| fn get_parent(&self, object: u16) -> u16 { | |
| if object == 0 { | |
| return 0; | |
| } | |
| let addr = self.get_object_addr(object) + self.attr_width; | |
| if self.version <= 3 { | |
| u16::from(self.memory.read_byte(addr)) | |
| } else { | |
| self.memory.read_word(addr) | |
| } | |
| } | |
| fn set_parent(&mut self, object: u16, parent: u16) { | |
| let addr = self.get_object_addr(object) + self.attr_width; | |
| if self.version <= 3 { | |
| self.memory.write_byte(addr, parent as u8); | |
| } else { | |
| self.memory.write_word(addr, parent); | |
| } | |
| } | |
| fn get_sibling(&self, object: u16) -> u16 { | |
| if object == 0 { | |
| return 0; | |
| } | |
| let addr = self.get_object_addr(object) + self.attr_width; | |
| if self.version <= 3 { | |
| u16::from(self.memory.read_byte(addr + 1)) | |
| } else { | |
| self.memory.read_word(addr + 2) | |
| } | |
| } | |
| fn set_sibling(&mut self, object: u16, sibling: u16) { | |
| let addr = self.get_object_addr(object) + self.attr_width; | |
| if self.version <= 3 { | |
| self.memory.write_byte(addr + 1, sibling as u8); | |
| } else { | |
| self.memory.write_word(addr + 2, sibling); | |
| } | |
| } | |
| fn get_child(&self, object: u16) -> u16 { | |
| if object == 0 { | |
| return 0; | |
| } | |
| let addr = self.get_object_addr(object) + self.attr_width; | |
| if self.version <= 3 { | |
| u16::from(self.memory.read_byte(addr + 2)) | |
| } else { | |
| self.memory.read_word(addr + 4) | |
| } | |
| } | |
| fn set_child(&mut self, object: u16, child: u16) { | |
| let addr = self.get_object_addr(object) + self.attr_width; | |
| if self.version <= 3 { | |
| self.memory.write_byte(addr + 2, child as u8); | |
| } else { | |
| self.memory.write_word(addr + 4, child); | |
| } | |
| } | |
| fn remove_obj(&mut self, object: u16) { | |
| let parent = self.get_parent(object); | |
| if parent == 0 { | |
| return; | |
| } | |
| // fix the tree to patch any holes: | |
| // 1- if the obj is the first child, make the obj's sibling the new child | |
| // 2- otherwise, connect the two siblings on each side of the obj | |
| let parents_first_child = self.get_child(parent); | |
| let younger_sibling = self.get_sibling(object); | |
| fn get_older(this: &Zmachine, obj: u16, prev: u16) -> u16 { | |
| let next = this.get_sibling(prev); | |
| if next == obj { | |
| prev | |
| } else { | |
| get_older(this, obj, next) | |
| } | |
| } | |
| if object == parents_first_child { | |
| // fix the parent / first child relationship, upgrade the younger sibling | |
| // A A | |
| // | => | | |
| // B--C--D C--D | |
| self.set_child(parent, younger_sibling); | |
| } else { | |
| // fix the hole between two siblings ( A--B--C -> A--C ) | |
| let older_sibling = get_older(self, object, parents_first_child); | |
| self.set_sibling(older_sibling, younger_sibling); | |
| } | |
| // remove the object by settings its parent to the null object | |
| // and clear its sibling reference, since it was moved above | |
| self.set_parent(object, 0); | |
| self.set_sibling(object, 0); | |
| } | |
| fn insert_obj(&mut self, object: u16, destination: u16) { | |
| let parents_first_child = self.get_child(destination); | |
| // skip if object is already in the right place | |
| if parents_first_child == object { | |
| return; | |
| } | |
| // first remove the object from its position and fix that change | |
| self.remove_obj(object); | |
| // set parent/child relationship (object becomes the new first child) | |
| self.set_parent(object, destination); | |
| self.set_child(destination, object); | |
| // move the previous first child into this object's sibling spot | |
| self.set_sibling(object, parents_first_child); | |
| } | |
| fn get_total_object_count(&self) -> u16 { | |
| // by convention, the property table for object #1 is located AFTER | |
| // the last object in the object table: | |
| let obj_table_end = self.get_object_prop_table_addr(1); | |
| let obj_size = self.attr_width + if self.version <= 3 { 3 } else { 9 } + 2; | |
| // v1-3 have a max of 255 objects, v4+ can have up to 65535 | |
| ((obj_table_end - self.obj_table_addr) / obj_size) as u16 | |
| } | |
| fn add_object_children(&self, parent: &mut Object) { | |
| // follow linked list of siblings to get all children: | |
| // Parent | |
| // | | |
| // Child -- Sibling -- Sibling -- Sibling ... | |
| let mut next = self.get_child(parent.number); | |
| while next > 0 { | |
| parent.children.push(Object::new(next, self)); | |
| next = self.get_sibling(next); | |
| } | |
| // get the children of each child | |
| for child in &mut parent.children { | |
| self.add_object_children(&mut *child); | |
| } | |
| } | |
| pub fn get_object_tree(&self) -> Object { | |
| // start using the INVALID_OBJECT 0 as the root | |
| let mut root = Object::new(0, self); | |
| // find all top level objects (objects with no parents) | |
| for i in 1..self.get_total_object_count() + 1 { | |
| if self.get_parent(i) == 0 { | |
| root.children.push(Object::new(i, self)); | |
| } | |
| } | |
| // recursively fetch children for each top level object | |
| for object in &mut root.children { | |
| self.add_object_children(&mut *object); | |
| } | |
| *root | |
| } | |
| fn find_object(&self, name: &str) -> Option<u16> { | |
| for i in 1..self.get_total_object_count() + 1 { | |
| if self.get_object_name(i).to_lowercase() == name.to_lowercase() { | |
| return Some(i); | |
| } | |
| } | |
| None | |
| } | |
| fn find_yourself(&self) -> Option<u16> { | |
| self.find_object("cretin") | |
| .or_else(|| self.find_object("you")) | |
| .or_else(|| self.find_object("yourself")) | |
| } | |
| fn test_attr(&self, object: u16, attr: u16) -> u16 { | |
| if attr as usize > self.attr_width * 8 { | |
| panic!("Can't test out-of-bounds attribute: {}", attr); | |
| } | |
| let addr = self.get_object_addr(object) + attr as usize / 8; | |
| let byte = self.memory.read_byte(addr); | |
| let bit = attr % 8; | |
| if byte & (128 >> bit) != 0 { 1 } else { 0 } | |
| } | |
| fn set_attr(&mut self, object: u16, attr: u16) { | |
| if attr as usize > self.attr_width * 8 { | |
| panic!("Can't set out-of-bounds attribute: {}", attr); | |
| } | |
| let addr = self.get_object_addr(object) + attr as usize / 8; | |
| let byte = self.memory.read_byte(addr); | |
| let bit = attr % 8; | |
| self.memory.write_byte(addr, byte | (128 >> bit)); | |
| } | |
| fn clear_attr(&mut self, object: u16, attr: u16) { | |
| if attr as usize > self.attr_width * 8 { | |
| panic!("Can't clear out-of-bounds attribute: {}", attr); | |
| } | |
| let addr = self.get_object_addr(object) + attr as usize / 8; | |
| let byte = self.memory.read_byte(addr); | |
| let bit = attr % 8; | |
| self.memory.write_byte(addr, byte & !(128 >> bit)); | |
| } | |
| fn get_default_prop(&self, property_number: u16) -> u16 { | |
| let word_index = (property_number - 1) as usize; | |
| let addr = self.prop_defaults + word_index * 2; | |
| self.memory.read_word(addr) | |
| } | |
| fn read_object_prop(&self, addr: usize) -> ObjectProperty { | |
| let header = self.memory.read_byte(addr); | |
| let mut len; | |
| let num; | |
| let value_addr; | |
| match self.version { | |
| 1...3 => { | |
| num = header % 32; | |
| len = header / 32 + 1; | |
| value_addr = addr + 1; // 1 byte header | |
| } | |
| _ => { | |
| num = header & 0b0011_1111; // prop num is bottom 6 bits | |
| if header & 0b1000_0000 != 0 { | |
| len = self.memory.read_byte(addr + 1) & 0b0011_1111; | |
| if len == 0 { len = 64; } // Z-Machine standard section 12.4.2.1.1 | |
| value_addr = addr + 2; // 2 byte header | |
| } else { | |
| len = if header & 0b0100_0000 != 0 { 2 } else { 1 }; | |
| value_addr = addr + 1; // 1 byte header | |
| } | |
| } | |
| } | |
| ObjectProperty { | |
| num, | |
| len, | |
| addr: value_addr, | |
| next: value_addr + len as usize, | |
| } | |
| } | |
| fn find_prop(&self, object: u16, property_number: u16) -> ObjectProperty { | |
| if property_number == 0 { | |
| return ObjectProperty::zero(); | |
| } | |
| let addr = self.get_object_prop_table_addr(object); | |
| let str_length = self.memory.read_byte(addr) as usize * 2; // words in name | |
| let first_addr = addr + str_length + 1; | |
| let property_number = property_number as u8; | |
| let mut prop = self.read_object_prop(first_addr); | |
| // linear prop read until property_number is found or until we run out | |
| // props are listed in decreasing order, check to make | |
| // sure the requested property even exists | |
| while prop.num != 0 && prop.num != property_number { | |
| if property_number > prop.num { | |
| return ObjectProperty::zero(); | |
| } | |
| prop = self.read_object_prop(prop.next); | |
| } | |
| prop | |
| } | |
| fn get_prop_value(&self, object: u16, property_number: u16) -> u16 { | |
| let prop = self.find_prop(object, property_number); | |
| if prop.num == 0 { | |
| self.get_default_prop(property_number) | |
| } else if prop.len == 1 { | |
| u16::from(self.memory.read_byte(prop.addr)) | |
| } else { | |
| self.memory.read_word(prop.addr) | |
| } | |
| } | |
| fn get_prop_addr(&self, object: u16, property_number: u16) -> usize { | |
| let prop = self.find_prop(object, property_number); | |
| if prop.num != 0 { prop.addr } else { 0 } | |
| } | |
| fn get_prop_len(&self, prop_data_addr: usize) -> u8 { | |
| // weird required edge case | |
| if prop_data_addr == 0 { | |
| return 0; | |
| } | |
| // address given is the property DATA, the property HEADER is right before | |
| let prop_header = self.memory.read_byte(prop_data_addr - 1); | |
| if self.version <= 3 { | |
| prop_header / 32 + 1 | |
| } else if prop_header & 0b1000_0000 != 0 { | |
| // This is already the *second* header byte. | |
| let len = prop_header & 0b0011_1111; | |
| if len == 0 { 64 } else { len } | |
| } else if prop_header & 0b0100_0000 != 0 { | |
| 2 | |
| } else { | |
| 1 | |
| } | |
| } | |
| fn get_next_prop(&self, object: u16, property_number: u16) -> u16 { | |
| // if property 0 is requested, give the first property present | |
| if property_number == 0 { | |
| let addr = self.get_object_prop_table_addr(object); | |
| let str_length = self.memory.read_byte(addr) as usize * 2; | |
| let first_prop = addr + str_length + 1; | |
| u16::from(self.read_object_prop(first_prop).num) | |
| } else { | |
| let prop = self.find_prop(object, property_number); | |
| u16::from(self.read_object_prop(prop.next).num) | |
| } | |
| } | |
| fn put_prop(&mut self, object: u16, property_number: u16, value: u16) { | |
| let prop = self.find_prop(object, property_number); | |
| if prop.len == 1 { | |
| self.memory.write_byte(prop.addr, value as u8); | |
| } else { | |
| self.memory.write_word(prop.addr, value); | |
| } | |
| } | |
| // Web UI only | |
| #[allow(dead_code)] | |
| pub fn get_current_room(&self) -> (u16, String) { | |
| let num = self.read_global(0); | |
| let name = self.get_object_name(num); | |
| (num, name) | |
| } | |
| fn get_status(&self) -> (String, String) { | |
| let num = self.read_global(0); | |
| let left = self.get_object_name(num); | |
| // bit 1 in header flags: | |
| // 0 => score/turns | |
| // 1 => AM/PM | |
| let right = if self.memory.read_byte(0x01) & 0b0000_0010 == 0 { | |
| let score = self.read_global(1) as i16; | |
| let turns = self.read_global(2); | |
| format!("{}/{}", score, turns) | |
| } else { | |
| let mut hours = self.read_global(1); | |
| let minutes = self.read_global(2); | |
| let am_pm = if hours >= 12 { "PM" } else { "AM" }; | |
| if hours > 12 { | |
| hours -= 12; | |
| } | |
| format!("{:02}:{:02} {}", hours, minutes, am_pm) | |
| }; | |
| (left, right) | |
| } | |
| pub fn update_status_bar(&self) { | |
| // status bar only used in v1-3 | |
| if self.version > 3 { | |
| return; | |
| } | |
| let (left, right) = self.get_status(); | |
| self.ui.set_status_bar(&left, &right); | |
| } | |
| fn make_save_state(&self, pc: usize) -> Vec<u8> { | |
| // save the whole dynamic memory region (between 0 and the start of static) | |
| let dynamic = self.memory.slice(0, self.static_start); | |
| let original = self.original_dynamic.as_slice(); | |
| let frames = &self.frames; | |
| let chksum = self.memory.read_word(0x1c); | |
| let release = self.memory.read_word(0x02); | |
| let serial = self.memory.read(0x12, 6); | |
| QuetzalSave::make(pc, dynamic, original, frames, chksum, release, serial) | |
| } | |
| fn restore_state(&mut self, data: &[u8]) { | |
| let save = QuetzalSave::from_bytes(&data[..], &self.original_dynamic[..]); | |
| // verify that the save if so the right game and that the memory is ok | |
| if save.chksum != self.memory.read_word(0x1C) { | |
| panic!("Invalid save, checksum is different"); | |
| } | |
| if self.static_start < save.memory.len() { | |
| panic!("Invalid save, memory is too long"); | |
| } | |
| self.pc = save.pc; | |
| self.frames = save.frames; | |
| self.memory.write(0, save.memory.as_slice()); | |
| } | |
| pub fn undo(&mut self) -> bool { | |
| if let Some(ref instr) = self.paused_instr { | |
| if instr.opcode != Opcode::VAR_228 { | |
| return false; | |
| } | |
| } | |
| if self.undos.is_empty() { | |
| self.ui.print("\n[Can't undo that far.]\n"); | |
| return false; | |
| } | |
| let new_current = self.undos.pop().unwrap(); | |
| self.redos.push(self.current_state.take().unwrap()); | |
| self.restore_state(new_current.1.as_slice()); | |
| self.current_state = Some(new_current); | |
| true | |
| } | |
| pub fn redo(&mut self) -> bool { | |
| if let Some(ref instr) = self.paused_instr { | |
| if instr.opcode != Opcode::VAR_228 { | |
| return false; | |
| } | |
| } | |
| if self.redos.is_empty() { | |
| self.ui.print("\n[Nothing to redo.]\n"); | |
| return false; | |
| } | |
| let new_current = self.redos.pop().unwrap(); | |
| self.undos.push(self.current_state.take().unwrap()); | |
| self.restore_state(new_current.1.as_slice()); | |
| self.current_state = Some(new_current); | |
| true | |
| } | |
| fn get_arguments(&mut self, operands: &[Operand]) -> Vec<u16> { | |
| operands | |
| .iter() | |
| .map(|operand| match *operand { | |
| Operand::Small(val) => u16::from(val), | |
| Operand::Large(val) => val, | |
| Operand::Variable(val) => self.read_variable(val), | |
| }) | |
| .collect() | |
| } | |
| fn return_from_routine(&mut self, value: u16) { | |
| let frame = self.frames.pop().expect("Can't pop off last frame!"); | |
| self.pc = frame.resume; | |
| if let Some(index) = frame.store { | |
| self.write_variable(index, value); | |
| } | |
| } | |
| fn process_branch(&mut self, branch: &Branch, next: usize, result: u16) { | |
| let Branch { | |
| address, | |
| returns, | |
| condition, | |
| } = *branch; | |
| let result = if result >= 1 { 1 } else { 0 }; | |
| if let Some(index) = address { | |
| self.pc = if result == condition { index } else { next }; | |
| } | |
| if let Some(value) = returns { | |
| if result == condition { | |
| self.return_from_routine(value); | |
| } else { | |
| self.pc = next | |
| } | |
| } | |
| } | |
| fn process_result(&mut self, instr: &Instruction, value: u16) { | |
| // store the result if needed | |
| if let Some(index) = instr.store { | |
| self.write_variable(index, value); | |
| } | |
| // check if we need to branch | |
| if let Some(ref branch) = instr.branch { | |
| self.process_branch(branch, instr.next, value); | |
| } else { | |
| self.pc = instr.next; | |
| } | |
| } | |
| fn decode_instruction(&self, addr: usize) -> Instruction { | |
| let mut read = self.memory.get_reader(addr); | |
| let first = read.byte(); | |
| let btm_4 = |num| num & 0b0000_1111; | |
| let btm_5 = |num| num & 0b0001_1111; | |
| let get_types = |bytes: &[u8]| OperandType::from(bytes); | |
| let get_opcode = |code: u8, offset: u16| { | |
| let num = u16::from(code) + offset; | |
| match Opcode::from_u16(num) { | |
| Some(val) => val, | |
| None => panic!("Opcode not found: {:?}", num), | |
| } | |
| }; | |
| use self::OperandType::*; | |
| let (opcode, optypes) = match first { | |
| 0xbe => (get_opcode(read.byte(), 1000), get_types(&[read.byte()])), | |
| 0x00...0x1f => (get_opcode(btm_5(first), 0), vec![Small, Small]), | |
| 0x20...0x3f => (get_opcode(btm_5(first), 0), vec![Small, Variable]), | |
| 0x40...0x5f => (get_opcode(btm_5(first), 0), vec![Variable, Small]), | |
| 0x60...0x7f => (get_opcode(btm_5(first), 0), vec![Variable, Variable]), | |
| 0x80...0x8f => (get_opcode(btm_4(first), 128), vec![Large]), | |
| 0x90...0x9f => (get_opcode(btm_4(first), 128), vec![Small]), | |
| 0xa0...0xaf => (get_opcode(btm_4(first), 128), vec![Variable]), | |
| 0xb0...0xbd | 0xbf => (get_opcode(btm_4(first), 176), vec![]), // OP_0 | |
| 0xc0...0xdf => (get_opcode(btm_5(first), 0), get_types(&[read.byte()])), | |
| 0xe0...0xff => { | |
| let opcode = get_opcode(btm_5(first), 224); | |
| if opcode == Opcode::VAR_236 || opcode == Opcode::VAR_250 { | |
| (opcode, get_types(&[read.byte(), read.byte()])) | |
| } else { | |
| (opcode, get_types(&[read.byte()])) | |
| } | |
| } | |
| _ => unreachable!(), | |
| }; | |
| let operands = optypes | |
| .iter() | |
| .map(|optype| match *optype { | |
| OperandType::Small => Operand::Small(read.byte()), | |
| OperandType::Large => Operand::Large(read.word()), | |
| OperandType::Variable => Operand::Variable(read.byte()), | |
| OperandType::Omitted => unreachable!(), | |
| }) | |
| .collect(); | |
| let store = if Instruction::does_store(opcode, self.version) { | |
| Some(read.byte()) | |
| } else { | |
| None | |
| }; | |
| let branch = if Instruction::does_branch(opcode, self.version) { | |
| let byte = read.byte() as usize; | |
| let condition = if byte & 0b1000_0000 != 0 { 1 } else { 0 }; | |
| let offset = if byte & 0b0100_0000 != 0 { | |
| byte & 0b0011_1111 | |
| } else { | |
| ((byte & 0b0011_1111) << 8) + read.byte() as usize | |
| }; | |
| // the offset (if two bytes) is a 14 bit unsigned int: 2^14 = 16384 | |
| let address = if offset > (16384 / 2) { | |
| Some(read.position() + offset - 16384 - 2) | |
| } else { | |
| Some(read.position() + offset - 2) | |
| }; | |
| match offset { | |
| 0 => Some(Branch { | |
| condition, | |
| address: None, | |
| returns: Some(0), | |
| }), | |
| 1 => Some(Branch { | |
| condition, | |
| address: None, | |
| returns: Some(1), | |
| }), | |
| _ => Some(Branch { | |
| condition, | |
| address, | |
| returns: None, | |
| }), | |
| } | |
| } else { | |
| None | |
| }; | |
| let text = if Instruction::does_text(opcode) { | |
| Some(self.read_zstring(read.position())) | |
| } else { | |
| None | |
| }; | |
| let text_length = if text.is_some() { | |
| self.zstring_length(read.position()) | |
| } else { | |
| 0 | |
| }; | |
| let name = Instruction::name(opcode, self.version); | |
| let next = read.position() + text_length; | |
| Instruction { | |
| addr, | |
| opcode, | |
| name, | |
| operands, | |
| store, | |
| branch, | |
| text, | |
| next, | |
| } | |
| } | |
| pub fn handle_instruction(&mut self, instr: &Instruction) { | |
| use self::Opcode::*; | |
| // ~mutably~ gets the arguments (might pop stack) | |
| let args = self.get_arguments(instr.operands.as_slice()); | |
| if env::var("DEBUG").is_ok() { | |
| println!("\x1B[97m{}\x1B[0m", instr); | |
| } | |
| // Match instructions that return values for storing or branching (or both) | |
| // `result` is an option. either a matched instruction or none (no match) | |
| let result = match (instr.opcode, &args[..]) { | |
| (OP2_1, _) if !args.is_empty() => Some(self.do_je(args[0], &args[1..])), | |
| (OP2_2, &[a, b]) => Some(self.do_jl(a, b)), | |
| (OP2_3, &[a, b]) => Some(self.do_jg(a, b)), | |
| (OP2_4, &[var, value]) => Some(self.do_dec_chk(var, value)), | |
| (OP2_5, &[var, value]) => Some(self.do_inc_chk(var, value)), | |
| (OP2_6, &[obj1, obj2]) => Some(self.do_jin(obj1, obj2)), | |
| (OP2_7, &[map, flags]) => Some(self.do_test(map, flags)), | |
| (OP2_8, &[a, b]) => Some(self.do_or(a, b)), | |
| (OP2_9, &[a, b]) => Some(self.do_and(a, b)), | |
| (OP2_10, &[obj, attr]) => Some(self.do_test_attr(obj, attr)), | |
| (OP2_15, &[array, index]) => Some(self.do_loadw(array, index)), | |
| (OP2_16, &[array, index]) => Some(self.do_loadb(array, index)), | |
| (OP2_17, &[obj, prop]) => Some(self.do_get_prop(obj, prop)), | |
| (OP2_18, &[obj, prop]) => Some(self.do_get_prop_addr(obj, prop)), | |
| (OP2_19, &[obj, prop]) => Some(self.do_get_next_prop(obj, prop)), | |
| (OP2_20, &[a, b]) => Some(self.do_add(a, b)), | |
| (OP2_21, &[a, b]) => Some(self.do_sub(a, b)), | |
| (OP2_22, &[a, b]) => Some(self.do_mul(a, b)), | |
| (OP2_23, &[a, b]) => Some(self.do_div(a, b)), | |
| (OP2_24, &[a, b]) => Some(self.do_mod(a, b)), | |
| (OP1_128, &[a]) => Some(self.do_jz(a)), | |
| (OP1_129, &[obj]) => Some(self.do_get_sibling(obj)), | |
| (OP1_130, &[obj]) => Some(self.do_get_child(obj)), | |
| (OP1_131, &[obj]) => Some(self.do_get_parent(obj)), | |
| (OP1_132, &[addr]) => Some(self.do_get_prop_len(addr)), | |
| (OP1_142, &[var]) => Some(self.do_load(var)), | |
| (OP1_143, &[value]) if self.version <= 4 => Some(self.do_not(value)), | |
| (OP0_189, &[]) => Some(self.do_verify()), | |
| (OP0_191, &[]) => Some(1), // piracy | |
| (VAR_231, &[range]) => Some(self.do_random(range)), | |
| (VAR_233, &[var]) if self.version == 6 => Some(self.do_pull(var)), | |
| (VAR_248, &[val]) if self.version >= 5 => Some(self.do_not(val)), | |
| (VAR_255, &[num]) => Some(self.do_check_arg_count(num)), | |
| (EXT_1002, &[num, places]) => Some(self.do_log_shift(num, places)), | |
| (EXT_1003, &[num, places]) => Some(self.do_art_shift(num, places)), | |
| _ => None, | |
| }; | |
| // If one of the above instructions matched, handle its result by | |
| // either storing it / branching on it / advancing the program counter. | |
| // Then return early since this instruction is done. | |
| if let Some(value) = result { | |
| self.process_result(instr, value); | |
| return; | |
| } | |
| // All other instructions (don't produce a value, only a side effect) | |
| match (instr.opcode, &args[..]) { | |
| (OP2_11, &[obj, attr]) => self.do_set_attr(obj, attr), | |
| (OP2_12, &[obj, attr]) => self.do_clear_attr(obj, attr), | |
| (OP2_13, &[var, value]) => self.do_store(var, value), | |
| (OP2_14, &[obj, dest]) => self.do_insert_obj(obj, dest), | |
| (OP2_25, &[addr, arg]) => self.do_call(instr, addr, &[arg]), // call_2s | |
| (OP2_26, &[addr, arg]) => self.do_call(instr, addr, &[arg]), // call_2n | |
| (OP1_133, &[var]) => self.do_inc(var), | |
| (OP1_134, &[var]) => self.do_dec(var), | |
| (OP1_135, &[addr]) => self.do_print_addr(addr), | |
| (OP1_136, &[addr]) => self.do_call(instr, addr, &[]), // call_1s | |
| (OP1_137, &[obj]) => self.do_remove_obj(obj), | |
| (OP1_138, &[obj]) => self.do_print_obj(obj), | |
| (OP1_139, &[value]) => self.do_ret(value), | |
| (OP1_140, &[offset]) => self.do_jump(offset, instr), | |
| (OP1_141, &[addr]) => self.do_print_paddr(addr), | |
| (OP1_143, &[addr]) if self.version >= 5 => self.do_call(instr, addr, &[]), // call_1n | |
| (OP0_176, _) => self.do_rtrue(), | |
| (OP0_177, _) => self.do_rfalse(), | |
| (OP0_178, _) => self.do_print(instr), | |
| (OP0_179, _) => self.do_print_ret(instr), | |
| (OP0_181, _) => self.do_save(instr), | |
| (OP0_182, _) => self.do_restore(instr), | |
| (OP0_183, _) => self.do_restart(), | |
| (OP0_184, _) => self.do_ret_popped(), | |
| (OP0_185, _) => self.do_pop(), | |
| (OP0_187, _) => self.do_newline(), | |
| (OP0_188, _) => self.do_show_status(), | |
| (VAR_224, _) if !args.is_empty() => self.do_call(instr, args[0], &args[1..]), // call | |
| (VAR_225, &[array, index, value]) => self.do_storew(array, index, value), | |
| (VAR_226, &[array, index, value]) => self.do_storeb(array, index, value), | |
| (VAR_227, &[obj, prop, value]) => self.do_put_prop(obj, prop, value), | |
| (VAR_228, &[text, parse]) => self.do_sread(instr, text, parse), | |
| (VAR_229, &[chr]) => self.do_print_char(chr), | |
| (VAR_230, &[num]) => self.do_print_num(num), | |
| (VAR_232, &[value]) => self.do_push(value), | |
| (VAR_233, &[var]) => { self.do_pull(var); } | |
| (VAR_236, _) if !args.is_empty() => self.do_call(instr, args[0], &args[1..]), // call_vs2 | |
| (VAR_249, _) if !args.is_empty() => self.do_call(instr, args[0], &args[1..]), // call_vn | |
| (VAR_250, _) if !args.is_empty() => self.do_call(instr, args[0], &args[1..]), // call_vn2 | |
| // special cases to no-op: (input/output streams & sound effects) | |
| // these might be present in some v3 games but aren't implemented yet | |
| (VAR_243, _) | (VAR_244, _) | (VAR_245, _) => (), | |
| _ => panic!( | |
| "\n\nOpcode not yet implemented: {} ({:?}) @ {:#04x}\n\n", | |
| instr.name, instr.opcode, self.pc | |
| ), | |
| } | |
| // advance pc to the next instruction | |
| // (but not for jumps, calls, save/restore, or anything with special needs) | |
| if instr.advances() && instr.should_advance(self.version) { | |
| self.pc = instr.next; | |
| } | |
| } | |
| fn is_debug_command(&self, input: &str) -> bool { | |
| if !input.starts_with('$') { | |
| return false; | |
| } | |
| let parts: Vec<_> = input.split_whitespace().collect(); | |
| let command = parts.first().unwrap(); | |
| let valid = [ | |
| "$dump", | |
| "$dict", | |
| "$tree", | |
| "$room", | |
| "$you", | |
| "$find", | |
| "$object", | |
| "$parent", | |
| "$simple", | |
| "$attrs", | |
| "$props", | |
| "$header", | |
| "$history", | |
| "$have_attr", | |
| "$have_prop", | |
| "$undo", | |
| "$redo", | |
| "$redo", | |
| "$teleport", | |
| "$steal", | |
| "$help", | |
| ]; | |
| valid.contains(command) | |
| } | |
| fn print_command_help(&mut self) { | |
| self.ui.debug( | |
| "\ | |
| Available debug commands: \n\n\ | |
| $dump (list stack frames and PC) \n\ | |
| $dict (show games's dictionary) \n\ | |
| $tree (list current object tree) \n\ | |
| $room (show current room's sub-tree) \n\ | |
| $you (show your sub-tree) \n\ | |
| $find name (find object number from name) \n\ | |
| $object num/name (show object's sub-tree) \n\ | |
| $parent num/name (show object's parent sub-tree) \n\ | |
| $simple num (object info, simple view) \n\ | |
| $attrs num/name (list object attributes) \n\ | |
| $props num/name (list object properties) \n\ | |
| $header (show header info) \n\ | |
| $history (list saved states) \n\ | |
| $have_attr num (list objects that have given attribute enabled) \n\ | |
| $have_prop num (list objects that have given property) \n\ | |
| $teleport num/name (teleport to a room) \n\ | |
| $steal num/name (takes any item) \n\ | |
| $undo \n\ | |
| $redo \n\ | |
| $quit | |
| ", | |
| ); | |
| } | |
| fn handle_debug_command(&mut self, input: &str) -> bool { | |
| let mut should_ask_again = true; | |
| let parts: Vec<_> = input.split_whitespace().collect(); | |
| let (command, rest) = parts.split_first().unwrap(); | |
| let arg = &rest.join(" "); | |
| match *command { | |
| "$help" => self.print_command_help(), | |
| "$dump" => self.debug_dump(), | |
| "$dict" => self.debug_dictionary(), | |
| "$tree" => self.debug_object_tree(), | |
| "$room" => self.debug_room(), | |
| "$you" => self.debug_yourself(), | |
| "$find" => self.debug_find_object(arg), | |
| "$object" => self.debug_object(arg), | |
| "$parent" => self.debug_parent(arg), | |
| "$attrs" => self.debug_object_attributes(arg), | |
| "$props" => self.debug_object_properties(arg), | |
| "$simple" => self.debug_object_simple(arg.parse().unwrap_or(1)), | |
| "$header" => self.debug_header(), | |
| "$history" => self.debug_history(), | |
| "$have_attr" => self.debug_have_attribute(arg), | |
| "$have_prop" => self.debug_have_property(arg), | |
| "$steal" => self.debug_steal(arg), | |
| "$teleport" => self.debug_teleport(arg), | |
| "$quit" => process::exit(0), | |
| // if undo/redo fails, should ask for input again | |
| // if they succeed, do nothing because zmachine state changed | |
| "$undo" => should_ask_again = !self.undo(), | |
| "$redo" => should_ask_again = !self.redo(), | |
| // unrecognized commands should ask for user input again | |
| _ => { | |
| should_ask_again = false; | |
| } | |
| } | |
| should_ask_again | |
| } | |
| // Terminal UI only | |
| #[allow(dead_code)] | |
| pub fn run(&mut self) { | |
| self.ui.clear(); | |
| // continue instructions until the quit instruction | |
| loop { | |
| let instr = self.decode_instruction(self.pc); | |
| if instr.opcode == Opcode::OP0_186 { | |
| break; | |
| } | |
| self.handle_instruction(&instr); | |
| } | |
| self.ui.reset(); | |
| } | |
| // Web UI only | |
| #[allow(dead_code)] | |
| pub fn step(&mut self) -> bool { | |
| // loop through instructions until user input is needed | |
| // (saves/restores need a save name, read instructions need user input) | |
| // Pauses on these instructions and control is passed back to js | |
| loop { | |
| let instr = self.decode_instruction(self.pc); | |
| if self.options.log_instructions { | |
| write!(self.instr_log, "\n{}", &instr).unwrap(); | |
| } | |
| match instr.opcode { | |
| // SAVE | |
| Opcode::OP0_181 => { | |
| let pc = instr.next - 1; | |
| let state = self.make_save_state(pc); | |
| self.send_save_message("save", &state); | |
| // Advance the pc, assuming that the save was successful | |
| self.process_save_result(&instr); | |
| } | |
| // RESTORE (breaks loop) | |
| Opcode::OP0_182 => { | |
| self.ui.message("restore", ""); | |
| self.paused_instr = Some(instr); | |
| return false; | |
| } | |
| // QUIT (breaks loop) | |
| Opcode::OP0_186 => { | |
| // undo 2x - get to the savestate right before the | |
| // "are you sure?" dialog box that usually shows up: | |
| self.undos.pop(); | |
| if let Some((_, state)) = self.undos.pop() { | |
| self.send_save_message("savestate", &state); | |
| } | |
| return true; // done == true | |
| } | |
| // READ (breaks loop) | |
| Opcode::VAR_228 => { | |
| let state = self.make_save_state(self.pc); | |
| self.send_save_message("savestate", &state); | |
| // web ui saves current state here BEFORE processing user input | |
| let (location, _) = self.get_status(); | |
| self.current_state = Some((location, state)); | |
| self.paused_instr = Some(instr); | |
| return false; | |
| } | |
| _ => { | |
| self.handle_instruction(&instr); | |
| } | |
| } | |
| } | |
| } | |
| // Web UI only - gives user input to the paused read instruction | |
| // (passes control back JS afterwards) | |
| #[allow(dead_code)] | |
| pub fn handle_input(&mut self, input: String) { | |
| let instr = self.paused_instr.take().expect( | |
| "Can't handle input, no paused instruction to resume", | |
| ); | |
| // handle special debugging commands | |
| // these inputs shouldn't be processed normally | |
| if self.is_debug_command(&input) { | |
| if self.handle_debug_command(&input) { | |
| self.ui.print("\n>"); | |
| } | |
| // return execution to JS, which will read user input again: | |
| return; | |
| } | |
| // new input changes timelines, so remove any obsolete redos | |
| self.redos.clear(); | |
| // move current state into the undo list now | |
| if self.current_state.is_some() { | |
| self.undos.push(self.current_state.take().unwrap()); | |
| } | |
| // explicitly handle read (need to get args first) | |
| let args = self.get_arguments(instr.operands.as_slice()); | |
| self.do_sread_second(args[0], args[1], input); | |
| self.pc = instr.next; | |
| } | |
| // Web UI only | |
| #[allow(dead_code)] | |
| pub fn restore(&mut self, data: &str) { | |
| let state = base64::decode(&data); | |
| // cancel restore (sending an empty string or if base64 decode fails) | |
| if data.is_empty() || state.is_err() { | |
| let instr = self.paused_instr.take().unwrap(); | |
| self.process_result(&instr, 0); | |
| } else { | |
| self.restore_state(state.unwrap().as_slice()); | |
| self.process_restore_result(); | |
| } | |
| } | |
| // Web UI only | |
| // Loads a saved state _without_ processing a restore result (like the above) | |
| #[allow(dead_code)] | |
| pub fn load_savestate(&mut self, data: &str) { | |
| let state = base64::decode(data).unwrap(); | |
| self.restore_state(state.as_slice()); | |
| } | |
| // Web UI only | |
| #[allow(dead_code)] | |
| fn send_save_message(&mut self, msg_type: &str, state: &[u8]) { | |
| let b64 = base64::encode(&state); | |
| let (location, info) = self.get_status(); | |
| let status = [&location, " - ", &info].concat(); | |
| let msg_body = serde_json::to_string(&(status, b64)).unwrap(); | |
| self.ui.message(msg_type, &msg_body); | |
| } | |
| } | |
| // Instruction handlers | |
| impl Zmachine { | |
| // OP2_1 | |
| fn do_je(&self, a: u16, values: &[u16]) -> u16 { | |
| if values.iter().any(|x| a == *x) { 1 } else { 0 } | |
| } | |
| // OP2_2 | |
| fn do_jl(&self, a: u16, b: u16) -> u16 { | |
| if (a as i16) < (b as i16) { 1 } else { 0 } | |
| } | |
| // OP2_3 | |
| fn do_jg(&self, a: u16, b: u16) -> u16 { | |
| if (a as i16) > (b as i16) { 1 } else { 0 } | |
| } | |
| // OP2_4 | |
| fn do_dec_chk(&mut self, var: u16, value: u16) -> u16 { | |
| let before = self.read_indirect_variable(var as u8) as i16; | |
| let after = before.wrapping_sub(1); | |
| self.write_indirect_variable(var as u8, after as u16); | |
| if after < (value as i16) { 1 } else { 0 } | |
| } | |
| // OP2_5 | |
| fn do_inc_chk(&mut self, var: u16, value: u16) -> u16 { | |
| let before = self.read_indirect_variable(var as u8) as i16; | |
| let after = before.wrapping_add(1); | |
| self.write_indirect_variable(var as u8, after as u16); | |
| if after > (value as i16) { 1 } else { 0 } | |
| } | |
| // OP2_6 | |
| fn do_jin(&self, obj1: u16, obj2: u16) -> u16 { | |
| if self.get_parent(obj1) == obj2 { 1 } else { 0 } | |
| } | |
| // OP2_7 | |
| fn do_test(&self, bitmap: u16, flags: u16) -> u16 { | |
| if bitmap & flags == flags { 1 } else { 0 } | |
| } | |
| // OP2_8 | |
| fn do_or(&self, a: u16, b: u16) -> u16 { | |
| a | b | |
| } | |
| // OP2_9 | |
| fn do_and(&self, a: u16, b: u16) -> u16 { | |
| a & b | |
| } | |
| // OP2_10 | |
| fn do_test_attr(&self, obj: u16, attr: u16) -> u16 { | |
| self.test_attr(obj, attr) | |
| } | |
| // OP2_11 | |
| fn do_set_attr(&mut self, obj: u16, attr: u16) { | |
| self.set_attr(obj, attr) | |
| } | |
| // OP2_12 | |
| fn do_clear_attr(&mut self, obj: u16, attr: u16) { | |
| self.clear_attr(obj, attr) | |
| } | |
| // OP2_13 | |
| fn do_store(&mut self, var: u16, value: u16) { | |
| self.write_indirect_variable(var as u8, value); | |
| } | |
| // OP2_14 | |
| fn do_insert_obj(&mut self, object: u16, destination: u16) { | |
| self.insert_obj(object, destination); | |
| } | |
| // OP2_15 | |
| fn do_loadw(&self, array_addr: u16, index: u16) -> u16 { | |
| let word_index = index.wrapping_mul(2); | |
| let word_addr = array_addr.wrapping_add(word_index); | |
| self.memory.read_word(word_addr as usize) | |
| } | |
| // OP2_16 | |
| fn do_loadb(&self, array_addr: u16, index: u16) -> u16 { | |
| let byte_addr = array_addr.wrapping_add(index); | |
| u16::from(self.memory.read_byte(byte_addr as usize)) | |
| } | |
| // OP2_17 | |
| fn do_get_prop(&self, object: u16, property_number: u16) -> u16 { | |
| self.get_prop_value(object, property_number) | |
| } | |
| // OP2_18 | |
| fn do_get_prop_addr(&self, object: u16, property_number: u16) -> u16 { | |
| self.get_prop_addr(object, property_number) as u16 | |
| } | |
| // OP2_19 | |
| fn do_get_next_prop(&self, object: u16, property_number: u16) -> u16 { | |
| self.get_next_prop(object, property_number) | |
| } | |
| // OP2_20 | |
| fn do_add(&self, a: u16, b: u16) -> u16 { | |
| (a as i16).wrapping_add(b as i16) as u16 | |
| } | |
| // OP2_21 | |
| fn do_sub(&self, a: u16, b: u16) -> u16 { | |
| (a as i16).wrapping_sub(b as i16) as u16 | |
| } | |
| // OP2_22 | |
| fn do_mul(&self, a: u16, b: u16) -> u16 { | |
| (a as i16).wrapping_mul(b as i16) as u16 | |
| } | |
| // OP2_23 | |
| fn do_div(&self, a: u16, b: u16) -> u16 { | |
| (a as i16).wrapping_div(b as i16) as u16 | |
| } | |
| // OP2_24 | |
| fn do_mod(&self, a: u16, b: u16) -> u16 { | |
| (a as i16 % b as i16) as u16 | |
| } | |
| // OP1_128 | |
| fn do_jz(&self, a: u16) -> u16 { | |
| if a == 0 { 1 } else { 0 } | |
| } | |
| // OP1_129 | |
| fn do_get_sibling(&self, object: u16) -> u16 { | |
| self.get_sibling(object) | |
| } | |
| // OP1_130 | |
| fn do_get_child(&self, object: u16) -> u16 { | |
| self.get_child(object) | |
| } | |
| // OP1_131 | |
| fn do_get_parent(&self, object: u16) -> u16 { | |
| self.get_parent(object) | |
| } | |
| // OP1_132 | |
| fn do_get_prop_len(&self, addr: u16) -> u16 { | |
| u16::from(self.get_prop_len(addr as usize)) | |
| } | |
| // OP1_133 | |
| fn do_inc(&mut self, var: u16) { | |
| let value = self.read_indirect_variable(var as u8); | |
| let inc = (value as i16).wrapping_add(1); | |
| self.write_indirect_variable(var as u8, inc as u16); | |
| } | |
| // OP1_134 | |
| fn do_dec(&mut self, var: u16) { | |
| let value = self.read_indirect_variable(var as u8); | |
| let dec = (value as i16).wrapping_sub(1); | |
| self.write_indirect_variable(var as u8, dec as u16); | |
| } | |
| // OP1_135 | |
| fn do_print_addr(&mut self, addr: u16) { | |
| let zstring = self.read_zstring(addr as usize); | |
| self.ui.print(&zstring); | |
| } | |
| // OP1_136 : call_1s | |
| // OP1_137 | |
| fn do_remove_obj(&mut self, obj: u16) { | |
| self.remove_obj(obj); | |
| } | |
| // OP1_138 | |
| fn do_print_obj(&mut self, obj: u16) { | |
| let name = self.get_object_name(obj); | |
| self.ui.print_object(&name); | |
| } | |
| // OP1_139 | |
| fn do_ret(&mut self, value: u16) { | |
| self.return_from_routine(value); | |
| } | |
| // OP1_140 | |
| fn do_jump(&mut self, offest: u16, instr: &Instruction) { | |
| self.pc = if (offest as i16) < 0 { | |
| instr.next - (-(offest as i16)) as usize - 2 | |
| } else { | |
| instr.next + offest as usize - 2 | |
| }; | |
| } | |
| // OP1_141 | |
| fn do_print_paddr(&mut self, addr: u16) { | |
| let paddr = self.unpack_print_paddr(addr); | |
| let zstring = self.read_zstring(paddr); | |
| self.ui.print(&zstring); | |
| } | |
| // OP1_142 | |
| fn do_load(&mut self, var: u16) -> u16 { | |
| self.read_indirect_variable(var as u8) | |
| } | |
| // OP1_143 | |
| fn do_not(&self, value: u16) -> u16 { | |
| !value | |
| } | |
| // OP0_176 | |
| fn do_rtrue(&mut self) { | |
| self.return_from_routine(1); | |
| } | |
| // OP0_177 | |
| fn do_rfalse(&mut self) { | |
| self.return_from_routine(0); | |
| } | |
| // OP0_178 | |
| fn do_print(&mut self, instr: &Instruction) { | |
| let text = instr.text.as_ref().expect("Can't print with no text!"); | |
| self.ui.print(text); | |
| } | |
| // OP0_179 | |
| fn do_print_ret(&mut self, instr: &Instruction) { | |
| let text = instr.text.as_ref().expect("Can't print with no text!"); | |
| self.ui.print(text); | |
| self.ui.print("\n"); | |
| self.return_from_routine(1); | |
| } | |
| // OP0_180 : nop, never actually used | |
| // OP0_181 | |
| fn do_save(&mut self, instr: &Instruction) { | |
| let prompt = format!("\nFilename [{}]: ", self.save_name); | |
| self.ui.print(&prompt); | |
| let input = self.ui.get_user_input(); | |
| let mut path = PathBuf::from(&self.save_dir); | |
| let mut file; | |
| match input.to_lowercase().as_ref() { | |
| "" | "yes" | "y" => path.push(&self.save_name), | |
| "no" | "n" | "cancel" => { | |
| self.process_result(instr, 0); | |
| return; | |
| } | |
| _ => path.push(input), | |
| } | |
| if let Ok(handle) = File::create(&path) { | |
| file = handle; | |
| } else { | |
| self.ui.print("Can't save to that file, try another?\n"); | |
| self.process_result(instr, 0); | |
| return; | |
| } | |
| // save file name for next use | |
| self.save_name = path.file_name().unwrap().to_string_lossy().into_owned(); | |
| // The save PC points to either the save instructions branch data or store | |
| // data. In either case, this is the last byte of the instruction. (so -1) | |
| let pc = instr.next - 1; | |
| let data = self.make_save_state(pc); | |
| file.write_all(&data[..]).expect("Error saving to file"); | |
| self.process_save_result(instr); | |
| } | |
| fn process_save_result(&mut self, instr: &Instruction) { | |
| // (v1-3): follow branch if needed (value "1" means the save succeeded) | |
| // (v4+): or store the value "1" at the give store position | |
| self.process_result(instr, 1); | |
| } | |
| // OP0_182 | |
| fn do_restore(&mut self, instr: &Instruction) { | |
| let prompt = format!("\nFilename [{}]: ", self.save_name); | |
| self.ui.print(&prompt); | |
| let input = self.ui.get_user_input(); | |
| let mut path = PathBuf::from(&self.save_dir); | |
| let mut data = Vec::new(); | |
| let mut file; | |
| match input.to_lowercase().as_ref() { | |
| "" | "yes" | "y" => path.push(&self.save_name), | |
| "no" | "n" | "cancel" => { | |
| self.process_result(instr, 0); | |
| return; | |
| } | |
| _ => path.push(input), | |
| } | |
| if let Ok(handle) = File::open(&path) { | |
| file = handle; | |
| } else { | |
| self.ui.print("Can't open that file, try another?\n"); | |
| self.process_result(instr, 0); | |
| return; | |
| } | |
| // save file name for next use | |
| self.save_name = path.file_name().unwrap().to_string_lossy().into_owned(); | |
| // restore program counter position, stack frames, and dynamic memory | |
| file.read_to_end(&mut data).expect( | |
| "Error reading save file", | |
| ); | |
| self.restore_state(data.as_slice()); | |
| self.process_restore_result(); | |
| } | |
| fn process_restore_result(&mut self) { | |
| // In versions 1-3 the PC points to the BRANCH data of the save instruction. | |
| // Saves branch if successful, so follow the branch if the topmost bit | |
| // (condition bit) of the branch data is set. Otherwise go to the next | |
| // instruction (the next byte address). | |
| // | |
| // In versions 4+ the PC points to the number that the save result should | |
| // be saved in. (Saves store the value 2 when successful) | |
| // | |
| // (note: this logic only applies to the save/restore instructions) | |
| let byte = self.memory.read_byte(self.pc); | |
| if self.version <= 3 { | |
| if byte & 0b1000_0000 != 0 { | |
| self.pc += (byte & 0b0011_1111) as usize - 2; // follow branch | |
| } else { | |
| self.pc += 1; // next instruction | |
| } | |
| } else { | |
| self.pc += 1; | |
| self.write_variable(byte, 2); // store "we just restored" value | |
| } | |
| } | |
| // OP0_183 | |
| fn do_restart(&mut self) { | |
| self.pc = self.initial_pc; | |
| self.frames.clear(); | |
| self.frames.push(Frame::empty()); | |
| self.memory.write(0, self.original_dynamic.as_slice()); | |
| } | |
| // OP0_184 | |
| fn do_ret_popped(&mut self) { | |
| let value = self.stack_pop(); | |
| self.return_from_routine(value); | |
| } | |
| // OP0_185 | |
| fn do_pop(&mut self) { | |
| self.stack_pop(); | |
| } | |
| // OP0_187 | |
| fn do_newline(&mut self) { | |
| self.ui.print("\n"); | |
| } | |
| // OP0_188 | |
| fn do_show_status(&self) { | |
| self.update_status_bar(); | |
| } | |
| // OP0_189 | |
| fn do_verify(&self) -> u16 { | |
| 1 | |
| } | |
| // All calls: | |
| // OP2_25, OP2_26, OP1_136, VAR_224, VAR_236, VAR_249, VAR_250 | |
| // and OP1_143 when version > 3 | |
| // | |
| // The only difference between the different opcodes is number of arguments | |
| // and whether or not to store or branch based on the result of the call | |
| // | |
| fn do_call(&mut self, instr: &Instruction, addr: u16, args: &[u16]) { | |
| // weird edge case: addr 0 means do nothing, then store/branch on 0 | |
| if addr == 0 { | |
| self.process_result(instr, 0); | |
| return; | |
| } | |
| // decode routine / prepopulate routine local variables | |
| let routine_addr = self.unpack_routine_addr(addr); | |
| let mut read = self.memory.get_reader(routine_addr); | |
| let mut locals = Vec::new(); | |
| let count = read.byte(); | |
| for _ in 0..count { | |
| match self.version { | |
| 1...4 => locals.push(read.word()), | |
| _ => locals.push(0), | |
| }; | |
| } | |
| let first_instr = read.position(); | |
| let frame = Frame::new(instr.next, instr.store, locals, args); | |
| self.pc = first_instr; | |
| self.frames.push(frame); | |
| } | |
| // VAR_225 | |
| fn do_storew(&mut self, array_addr: u16, index: u16, value: u16) { | |
| let word_index = index.wrapping_mul(2); | |
| let word_addr = array_addr.wrapping_add(word_index); | |
| self.memory.write_word(word_addr as usize, value); | |
| } | |
| // VAR_226 | |
| fn do_storeb(&mut self, array: u16, index: u16, value: u16) { | |
| let word_addr = array.wrapping_add(index); | |
| self.memory.write_byte(word_addr as usize, value as u8); | |
| } | |
| // VAR_227 | |
| fn do_put_prop(&mut self, obj: u16, prop: u16, value: u16) { | |
| self.put_prop(obj, prop, value); | |
| } | |
| // VAR_228 | |
| fn do_sread(&mut self, instr: &Instruction, text_addr: u16, parse_addr: u16) { | |
| // need to update the status bar before each read | |
| self.update_status_bar(); | |
| // add extra space so it doesn't look janky (non-spec) | |
| self.ui.print(" "); | |
| let input = self.ui.get_user_input(); | |
| // handle special debugging commands | |
| // these inputs shouldn't be processed normally | |
| if self.is_debug_command(&input) { | |
| if self.handle_debug_command(&input) { | |
| self.ui.print("\n>"); | |
| self.do_sread(instr, text_addr, parse_addr); | |
| } | |
| return; | |
| } | |
| self.do_sread_second(text_addr, parse_addr, input); | |
| // save state JUST after having processed user input | |
| // new input changes timelines, so remove any obsolete redos | |
| self.redos.clear(); | |
| // push the current state into the undo list | |
| if self.current_state.is_some() { | |
| self.undos.push(self.current_state.take().unwrap()); | |
| } | |
| // and save the current state | |
| let location = self.get_object_name(self.read_global(0)); | |
| let state = self.make_save_state(instr.next); | |
| self.current_state = Some((location, state)); | |
| } | |
| fn do_sread_second(&mut self, text_addr: u16, parse_addr: u16, mut raw: String) { | |
| let text_addr = text_addr as usize; | |
| let parse_addr = parse_addr as usize; | |
| // versions 1-4 have to store an extra 0, so the max length is 1 less | |
| let mut max_length = self.memory.read_byte(text_addr as usize); | |
| if self.version <= 4 { | |
| max_length -= 1; | |
| } | |
| raw.truncate(max_length as usize); | |
| let input = &raw.to_lowercase(); | |
| let bytes = input.as_bytes(); | |
| let len = bytes.len(); | |
| // ver 1-4 start storing @ byte 1, ending with a terminating 0 | |
| // ver 5+ save the input length @1, start storing @2, and DON'T end with 0 | |
| if self.version <= 4 { | |
| self.memory.write(text_addr + 1, bytes); | |
| self.memory.write_byte(text_addr + 1 + len, 0); | |
| } else { | |
| self.memory.write_byte(text_addr + 1, len as u8); | |
| self.memory.write(text_addr + 2, bytes); | |
| } | |
| // skip tokenization step if parse_addr is 0 | |
| if parse_addr != 0 { | |
| self.tokenise(input, parse_addr); | |
| } | |
| } | |
| // VAR_229 | |
| fn do_print_char(&mut self, chr: u16) { | |
| self.ui.print(&(chr as u8 as char).to_string()); | |
| } | |
| // VAR_230 | |
| fn do_print_num(&mut self, signed: u16) { | |
| self.ui.print(&(signed as i16).to_string()); | |
| } | |
| // VAR_231 | |
| fn do_random(&mut self, range: u16) -> u16 { | |
| let range = range as i16; | |
| if range <= 0 { | |
| self.rng.reseed([range as u32, 0, 0, 0]); | |
| 0 | |
| } else if range == 1 { | |
| 1 | |
| } else { | |
| (self.rng.gen::<f32>() * f32::from(range)).ceil() as u16 | |
| } | |
| } | |
| // VAR_232 | |
| fn do_push(&mut self, value: u16) { | |
| self.stack_push(value) | |
| } | |
| // VAR_233 | |
| fn do_pull(&mut self, var: u16) -> u16 { | |
| let value = self.stack_pop(); | |
| self.write_indirect_variable(var as u8, value); | |
| value | |
| } | |
| // VAR_248 do_not() (same as OP1_143) | |
| // VAR_255 | |
| fn do_check_arg_count(&self, num: u16) -> u16 { | |
| let count = u16::from( | |
| self.frames | |
| .last() | |
| .expect("Can't check arg count, no frames!") | |
| .arg_count, | |
| ); | |
| if count >= num { 1 } else { 0 } | |
| } | |
| // EXT_1002 | |
| fn do_log_shift(&mut self, number: u16, places: u16) -> u16 { | |
| let number = number as u32; | |
| let places = places as i16; | |
| if places > 0 { | |
| (number << places) as u16 | |
| } else if places < 0 { | |
| (number >> -places) as u16 | |
| } else { | |
| number as u16 | |
| } | |
| } | |
| // EXT_1003 | |
| fn do_art_shift(&mut self, number: u16, places: u16) -> u16 { | |
| let mut number = (number as i16) as i32; | |
| let places = places as i16; | |
| if places > 0 { | |
| number <<= places; | |
| } else if places < 0 { | |
| number >>= -places; | |
| } | |
| (number as i16) as u16 | |
| } | |
| } | |
| // debug functions | |
| #[allow(dead_code)] | |
| impl Zmachine { | |
| fn debug_header(&mut self) { | |
| let version = self.memory.read_byte(0x00); | |
| let release = self.memory.read_word(0x02); | |
| let initial_pc = self.memory.read_word(0x06); | |
| let checksum = self.memory.read_word(0x1C); | |
| let serial = self.memory.read(0x12, 6).to_vec(); | |
| let ascii = String::from_utf8_lossy(&serial[..]); | |
| self.ui.debug(&format!( | |
| "\ | |
| Version: {} \n\ | |
| Release: {} / Serial: {} \n\ | |
| Checksum: {:#x} \n\ | |
| Initial PC: {:#x} \n\ | |
| ", | |
| version, release, ascii, checksum, initial_pc | |
| )); | |
| } | |
| fn debug_dictionary(&mut self) { | |
| let mut out = String::new(); | |
| let mut words = self.dictionary.keys().collect::<Vec<_>>(); | |
| words.sort(); | |
| let width = words.iter().fold(0, |longest, word| { | |
| if word.len() > longest { | |
| word.len() | |
| } else { | |
| longest | |
| } | |
| }); | |
| for word in &words { | |
| write!(out, "{:1$} ", word, width).unwrap() | |
| } | |
| out.push_str("\n"); | |
| self.ui.debug(&out); | |
| } | |
| fn debug_dump(&mut self) { | |
| let mut out = String::new(); | |
| writeln!(out, "PC @ {}", self.pc).unwrap(); | |
| for frame in &self.frames { | |
| writeln!(out, "{}", frame).unwrap(); | |
| } | |
| self.ui.debug(&out); | |
| } | |
| fn debug_object_simple(&mut self, obj_num: u16) { | |
| self.ui.debug(&format!("\nObject #{}", obj_num)); | |
| let addr = self.get_object_addr(obj_num); | |
| let mut read = self.memory.get_reader(addr); | |
| if self.version <= 3 { | |
| self.ui.debug(&format!( | |
| "Attributes: {:08b} {:08b} {:08b} {:08b}", | |
| read.byte(), | |
| read.byte(), | |
| read.byte(), | |
| read.byte() | |
| )); | |
| self.ui.debug(&format!( | |
| "Parent: {}, Sibling: {}, Child: {}", | |
| read.byte(), | |
| read.byte(), | |
| read.byte() | |
| )); | |
| } else { | |
| self.ui.debug(&format!( | |
| "Attributes: {:08b} {:08b} {:08b} {:08b} {:08b} {:08b}", | |
| read.byte(), | |
| read.byte(), | |
| read.byte(), | |
| read.byte(), | |
| read.byte(), | |
| read.byte() | |
| )); | |
| self.ui.debug(&format!( | |
| "Parent: {}, Sibling: {}, Child: {}", | |
| read.word(), | |
| read.word(), | |
| read.word() | |
| )); | |
| } | |
| let prop_addr = read.word() as usize; | |
| self.ui.debug(&format!("Property table @ {:x}", prop_addr)); | |
| let text_length = self.memory.read_byte(prop_addr); | |
| let short_name = if text_length > 0 { | |
| self.read_zstring(prop_addr + 1) | |
| } else { | |
| String::new() | |
| }; | |
| self.ui.debug(&format!("{} (len: {})\n", short_name, text_length)); | |
| } | |
| fn get_object_number(&self, input: &str) -> u16 { | |
| if let Ok(num) = input.parse() { | |
| num | |
| } else if let Some(num) = self.find_object(input) { | |
| num | |
| } else { | |
| 0 | |
| } | |
| } | |
| fn debug_object(&mut self, input: &str) { | |
| let num = self.get_object_number(input); | |
| if num == 0 { | |
| return; | |
| } | |
| let mut obj = Object::new(num, self); | |
| self.add_object_children(&mut obj); | |
| self.ui.debug(&obj.to_string()); | |
| } | |
| fn debug_object_tree(&mut self) { | |
| let tree = self.get_object_tree(); | |
| self.ui.debug(&tree.to_string()); | |
| } | |
| fn debug_object_properties(&mut self, input: &str) { | |
| let num = self.get_object_number(input); | |
| if num == 0 { | |
| return; | |
| } | |
| self.ui.debug(&format!("Object: #{}", num)); | |
| let addr = self.get_object_prop_table_addr(num); | |
| let str_length = self.memory.read_byte(addr) as usize * 2; // words in name | |
| let first_addr = addr + str_length + 1; | |
| let mut prop = self.read_object_prop(first_addr); | |
| let mut slice = self.memory.read(prop.addr, prop.len as usize); | |
| self.ui.debug(&format!("{:2} {:?}", prop.num, slice)); | |
| while prop.num != 0 { | |
| prop = self.read_object_prop(prop.next); | |
| slice = self.memory.read(prop.addr, prop.len as usize); | |
| self.ui.debug(&format!("{:2} {:?}", prop.num, slice)); | |
| } | |
| } | |
| fn debug_object_attributes(&mut self, input: &str) { | |
| let num = self.get_object_number(input); | |
| if num == 0 { | |
| return; | |
| } | |
| let name = self.get_object_name(num); | |
| let mut attributes = Vec::new(); | |
| for i in 0..(self.attr_width * 8) as u16 { | |
| if self.test_attr(num, i) == 1 { | |
| attributes.push(i); | |
| } | |
| } | |
| self.ui.debug(&format!("{} ({})\n{:?}", name, num, attributes)); | |
| } | |
| pub fn debug_object_details(&self, obj_num: u16) -> String { | |
| if obj_num == 0 { | |
| return String::new(); | |
| } | |
| let mut out = String::from("Properties:\n"); | |
| let addr = self.get_object_prop_table_addr(obj_num); | |
| let str_length = self.memory.read_byte(addr) as usize * 2; // words in name | |
| let first_addr = addr + str_length + 1; | |
| let mut prop = self.read_object_prop(first_addr); | |
| let mut slice = self.memory.read(prop.addr, prop.len as usize); | |
| writeln!(out, "{:2} {:?}", prop.num, slice).unwrap(); | |
| while prop.num != 0 { | |
| prop = self.read_object_prop(prop.next); | |
| slice = self.memory.read(prop.addr, prop.len as usize); | |
| writeln!(out, "{:2} {:?}", prop.num, slice).unwrap(); | |
| } | |
| let mut attributes = Vec::new(); | |
| for i in 0..(self.attr_width * 8) as u16 { | |
| if self.test_attr(obj_num, i) == 1 { | |
| attributes.push(i); | |
| } | |
| } | |
| write!(out, "\nAttributes:\n{:?}", attributes).unwrap(); | |
| out | |
| } | |
| fn debug_have_attribute(&mut self, attr_str: &str) { | |
| let attr = attr_str.parse().unwrap_or(0); | |
| let mut objects = Vec::new(); | |
| for obj_num in 1..self.get_total_object_count() + 1 { | |
| if self.test_attr(obj_num, attr) == 1 { | |
| objects.push(Object::new(obj_num, self)); | |
| } | |
| } | |
| for obj in &objects { | |
| self.ui.debug(&obj.to_string()); | |
| } | |
| } | |
| fn debug_have_property(&mut self, prop_str: &str) { | |
| let prop_num = prop_str.parse().unwrap_or(0); | |
| let mut objects = Vec::new(); | |
| for obj_num in 1..self.get_total_object_count() + 1 { | |
| let prop = self.find_prop(obj_num, prop_num); | |
| if prop.num != 0 { | |
| objects.push(Object::new(obj_num, self)); | |
| } | |
| } | |
| for obj in &objects { | |
| self.ui.debug(&obj.to_string()); | |
| } | |
| } | |
| fn debug_room(&mut self) { | |
| let mut room = Object::new(self.read_global(0), self); | |
| self.add_object_children(&mut room); | |
| self.ui.debug(&room.to_string()); | |
| } | |
| fn debug_yourself(&mut self) { | |
| if let Some(num) = self.find_yourself() { | |
| let mut you = Object::new(num, self); | |
| self.add_object_children(&mut you); | |
| self.ui.debug(&you.to_string()); | |
| } | |
| } | |
| fn debug_parent(&mut self, input: &str) { | |
| let num = self.get_object_number(input); | |
| if num == 0 { | |
| return; | |
| } | |
| let parent_num = self.get_parent(num); | |
| if parent_num == 0 { | |
| self.ui.debug(&"Parent if the root object 0"); | |
| } else { | |
| let mut parent = Object::new(parent_num, self); | |
| self.add_object_children(&mut parent); | |
| self.ui.debug(&parent.to_string()); | |
| } | |
| } | |
| fn debug_find_object(&mut self, name: &str) { | |
| if let Some(num) = self.find_object(name) { | |
| let object = Object::new(num, self); | |
| self.ui.debug(&format!("{}", object)); | |
| } | |
| } | |
| fn debug_teleport(&mut self, input: &str) { | |
| let you = self.find_yourself().expect("Can't find you in the object tree"); | |
| let num = self.get_object_number(input); | |
| if num == 0 { | |
| self.ui.print("I can't find that room...\n"); | |
| return; | |
| } else { | |
| self.ui.print("Zzzap! Somehow you are in a different place...\n"); | |
| } | |
| self.insert_obj(you, num); | |
| } | |
| fn debug_steal(&mut self, input: &str) { | |
| let you = self.find_yourself().expect("Can't find you in the object tree"); | |
| let num = self.get_object_number(input); | |
| if num == 0 { | |
| self.ui.print("I can't find that object...\n"); | |
| return; | |
| } else { | |
| self.ui.print(&format!("Zzzing! Somehow you are holding the {}...\n", input)); | |
| } | |
| self.insert_obj(num, you); | |
| } | |
| pub fn debug_history(&mut self) { | |
| let undo_count = self.undos.len(); | |
| let total = self.undos.len() + self.redos.len() + 1; | |
| self.ui.debug(&"History:"); | |
| for (i, state) in self.undos.iter().enumerate() { | |
| let index = i + 1; | |
| self.ui.debug(&format!(" ({}/{}) @ {}", index, total, state.0)); | |
| } | |
| if let Some(ref current) = self.current_state { | |
| let index = undo_count + 1; | |
| self.ui.debug(&format!(" -> ({}/{}) @ {}", index, total, current.0)); | |
| } | |
| for (i, state) in self.redos.iter().rev().enumerate() { | |
| let index = undo_count + i + 2; | |
| self.ui.debug(&format!(" ({}/{}) @ {}", index, total, state.0)); | |
| } | |
| } | |
| fn debug_routine(&mut self, routine_addr: usize) { | |
| let mut read = self.memory.get_reader(routine_addr); | |
| let mut locals = Vec::new(); | |
| let count = read.byte(); | |
| for _ in 0..count { | |
| match self.version { | |
| 1...4 => locals.push(read.word()), | |
| _ => locals.push(0), | |
| }; | |
| } | |
| let first_instr = self.decode_instruction(read.position()); | |
| let mut set: HashSet<Instruction> = HashSet::new(); | |
| fn follow(zvm: &Zmachine, set: &mut HashSet<Instruction>, instr: Instruction) { | |
| if set.contains(&instr) { | |
| return; | |
| } | |
| let branch = match instr.branch { | |
| Some(Branch { address: Some(addr), .. }) => Some(addr), | |
| _ => None, | |
| }; | |
| let next = if instr.advances() { | |
| Some(instr.next) | |
| } else { | |
| None | |
| }; | |
| set.insert(instr); | |
| if let Some(addr) = branch { | |
| follow(zvm, set, zvm.decode_instruction(addr)); | |
| }; | |
| if let Some(addr) = next { | |
| follow(zvm, set, zvm.decode_instruction(addr)); | |
| }; | |
| }; | |
| follow(self, &mut set, first_instr); | |
| let mut instructions = set.iter().collect::<Vec<_>>(); | |
| instructions.sort_by_key(|i| i.addr); | |
| self.ui.debug(&format!("Locals: {:?}", locals)); | |
| for instr in &instructions { | |
| self.ui.debug(&format!("{}", instr)); | |
| } | |
| } | |
| } |