use std::fs::File;
use std::path::PathBuf;
use std::io::prelude::*;
use std::fmt;
use std::fmt::Write as FmtWrite;
use std::collections::{HashSet, HashMap};
use std::boxed::Box;
use std::process;
use enum_primitive::FromPrimitive;
use rand;
use rand::{Rng, SeedableRng};
use base64;
use serde_json;
use buffer::Buffer;
use frame::Frame;
use instruction::Instruction;
use instruction::Opcode;
use instruction::OperandType;
use instruction::Operand;
use instruction::Branch;
use quetzal::QuetzalSave;
use options::Options;
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 mut zvm = Zmachine {
version,
ui,
save_dir: format!("{}", &options.save_dir),
save_name: format!("{}.sav", &options.save_name),
options,
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: Zmachine::default_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([90, 111, 114, 107]),
memory,
};
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 % 0x10000) as u16
}
#[allow(non_snake_case)]
fn default_alphabet() -> [Vec<String>; 3] {
let A0 = " .....abcdefghijklmnopqrstuvwxyz";
let A1 = " .....ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let A2 = " ......\n0123456789.,!?_#'\"/\\-:()";
[
A0.chars().map(|c| c.to_string()).collect(),
A1.chars().map(|c| c.to_string()).collect(),
A2.chars().map(|c| c.to_string()).collect(),
]
}
fn unpack(&self, addr: u16) -> usize {
let addr = addr as usize;
match self.version {
1...3 => addr * 2,
4...7 => addr * 4,
_ => addr * 8,
}
}
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 packed_addr = self.memory.read_word(self.abbrev_table + offset);
let addr = self.unpack(packed_addr);
self.read_zstring(addr)
}
fn read_zstring(&self, addr: usize) -> String {
use self::ZStringState::*;
let mut state = Alphabet(0);
let mut index = addr;
let mut zstring = String::new();
{
let mut step = |zchar: u8| {
state = match (zchar, &state) {
(1, &Alphabet(_)) => Abbrev(1),
(2, &Alphabet(_)) => Abbrev(2),
(3, &Alphabet(_)) => Abbrev(3),
(4, &Alphabet(_)) => Alphabet(1),
(5, &Alphabet(_)) => Alphabet(2),
(6, &Alphabet(2)) => Tenbit1,
(_, &Tenbit1) => Tenbit2(zchar),
(_, &Tenbit2(first)) => {
let letter = ((first << 5) + zchar) as char;
zstring.push_str(&letter.to_string());
Alphabet(0)
}
(_, &Abbrev(num)) => {
let abbrev = self.get_abbrev((num-1) * 32 + zchar);
zstring.push_str(&abbrev);
Alphabet(0)
}
(_, &Alphabet(num)) => {
let letter = &self.alphabet[num][zchar as usize];
zstring.push_str(letter);
Alphabet(0)
}
};
};
loop {
let word = self.memory.read_word(index);
index += 2;
step(((word >> 10) & 0b00011111) as u8);
step(((word >> 5) & 0b00011111) as u8);
step( (word & 0b00011111) as u8);
if word & 0x8000 != 0 { break; }
}
}
zstring
}
fn zstring_length(&self, addr: usize) -> usize {
let mut length = 0;
loop {
let word = self.memory.read_word(addr + length);
length += 2;
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) {
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 = input.split(' ')
.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::<Vec<_>>();
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 {
panic!("Trying to get the address for the INVALID_OBJECT 0");
}
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)
+ self.attr_width
+ if self.version <= 3 { 3 } else { 9 };
self.memory.read_word(addr) as usize
}
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 {
let addr = self.get_object_addr(object) + self.attr_width;
if self.version <= 3 {
self.memory.read_byte(addr) as u16
} 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 {
let addr = self.get_object_addr(object) + self.attr_width;
if self.version <= 3 {
self.memory.read_byte(addr + 1) as u16
} 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 {
let addr = self.get_object_addr(object) + self.attr_width;
if self.version <= 3 {
self.memory.read_byte(addr + 2) as u16
} 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; }
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 {
self.set_child(parent, younger_sibling);
} else {
let older_sibling = get_older(self, object, parents_first_child);
self.set_sibling(older_sibling, younger_sibling);
}
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);
if parents_first_child == object { return; }
self.remove_obj(object);
self.set_parent(object, destination);
self.set_child(destination, object);
self.set_sibling(object, parents_first_child);
}
fn get_total_object_count(&self) -> u16 {
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;
((obj_table_end - self.obj_table_addr) / obj_size) as u16
}
fn add_object_children(&self, parent: &mut Object) {
let mut next = self.get_child(parent.number);
while next > 0 {
parent.children.push(Object::new(next, self));
next = self.get_sibling(next);
}
for child in &mut parent.children {
self.add_object_children(&mut *child);
}
}
pub fn get_object_tree(&self) -> Object {
let mut root = Object::new(0, self);
for i in 1..self.get_total_object_count()+1 {
if self.get_parent(i) == 0 {
root.children.push(Object::new(i, self));
}
}
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(self.find_object("you"))
.or(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 len;
let num;
let value_addr;
match self.version {
1...3 => {
num = header % 32;
len = header / 32 + 1;
value_addr = addr + 1; }
_ => {
num = header & 0b00111111;
if header & 0b10000000 != 0 {
len = self.memory.read_byte(addr + 1) & 0b00111111;
value_addr = addr + 2; } else {
len = if header & 0b01000000 != 0 { 2 } else { 1 };
value_addr = addr + 1; }
}
}
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; let first_addr = addr + str_length + 1;
let property_number = property_number as u8;
let mut prop = self.read_object_prop(first_addr);
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 {
self.memory.read_byte(prop.addr) as u16
} 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 {
if prop_data_addr == 0 { return 0; }
let prop_header = self.memory.read_byte(prop_data_addr - 1);
if self.version <= 3 {
prop_header / 32 + 1
} else if prop_header & 0b10000000 != 0 {
prop_header & 0b00111111
} else if prop_header & 0b01000000 != 0 {
2
} else {
1
}
}
fn get_next_prop(&self, object: u16, property_number: u16) -> u16 {
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;
self.read_object_prop(first_prop).num as u16
} else {
let prop = self.find_prop(object, property_number);
self.read_object_prop(prop.next).num as u16
}
}
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);
}
}
#[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);
let right = if self.memory.read_byte(0x01) & 0b00000010 == 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) {
if self.version > 3 { return; }
let (left, right) = self.get_status();
self.ui.set_status_bar(&left, &right);
}
fn save_state(&self, pc: usize) -> Vec<u8> {
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[..]);
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) => val as u16,
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) {
if let Some(index) = instr.store {
self.write_variable(index, value);
}
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 & 0b00001111;
let btm_5 = |num| num & 0b00011111;
let get_types = |bytes: &[u8]| OperandType::from(bytes);
let get_opcode = |code: u8, offset: u16| {
let num = code as u16 + 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...0xbf => (get_opcode(btm_4(first), 176), vec![]), 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 & 0b10000000 != 0 { 1 } else { 0 };
let offset = if byte & 0b01000000 != 0 {
byte & 0b00111111
} else {
((byte & 0b00111111) << 8) + read.byte() as usize
};
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::*;
let args = self.get_arguments(instr.operands.as_slice());
let result = match (instr.opcode, &args[..]) {
(OP2_1, &[a, ref bs..]) => Some( self.do_je(a, bs) ),
(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]) => Some( self.do_not(value) ),
(OP0_189, &[]) => Some( self.do_verify() ),
(VAR_231, &[range]) => Some( self.do_random(range) ),
_ => None,
};
if let Some(value) = result {
self.process_result(instr, value);
return;
}
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]), (OP2_26, &[addr, arg]) => self.do_call(instr, addr, &[arg]), (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, &[]), (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),
(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_187, _) => self.do_newline(),
(OP0_188, _) => self.do_show_status(),
(VAR_224, &[addr, ref rest..]) => self.do_call(instr, addr, rest), (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, &[addr, ref rest..]) => self.do_call(instr, addr, rest), (VAR_249, &[addr, ref rest..]) => self.do_call(instr, addr, rest), (VAR_250, &[addr, ref rest..]) => self.do_call(instr, addr, rest),
(VAR_243, _) | (VAR_244, _) | (VAR_245, _) => (),
_ => panic!("\n\nOpcode not yet implemented: {} ({:?}) @ {:#04x}\n\n",
instr.name, instr.opcode, self.pc)
}
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 = input.split(' ').collect::<Vec<_>>();
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 = input.split(' ').collect::<Vec<_>>();
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),
"$undo" => { should_ask_again = !self.undo() },
"$redo" => { should_ask_again = !self.redo() },
_ => { should_ask_again = false; }
}
should_ask_again
}
#[allow(dead_code)]
pub fn run(&mut self) {
self.ui.clear();
loop {
let instr = self.decode_instruction(self.pc);
if instr.opcode == Opcode::OP0_186 { break; }
self.handle_instruction(&instr);
}
self.ui.reset();
}
#[allow(dead_code)]
pub fn step(&mut self) -> bool {
loop {
let instr = self.decode_instruction(self.pc);
if self.options.log_instructions {
write!(self.instr_log, "\n{}", &instr).unwrap();
}
if instr.opcode == Opcode::OP0_186 {
return true; }
match instr.opcode {
Opcode::OP0_181 => {
let pc = instr.next - 1;
let state = self.save_state(pc);
let b64 = base64::encode(&state);
let (location, info) = self.get_status();
let status = [&location, " - ", &info].concat();
let msg = serde_json::to_string(&(status, b64)).unwrap();
self.ui.message("save", &msg);
self.process_save_result(&instr);
}
Opcode::OP0_182 => {
self.ui.message("restore", "");
self.paused_instr = Some(instr);
return false;
}
Opcode::VAR_228 => {
let (location, info) = self.get_status();
let status = [&location, " - ", &info].concat();
let state = self.save_state(self.pc);
let b64 = base64::encode(&state);
let msg = serde_json::to_string(&(status, b64)).unwrap();
self.ui.message("savestate", &msg);
self.current_state = Some((location, state));
self.paused_instr = Some(instr);
return false;
}
_ => {
self.handle_instruction(&instr);
}
}
}
}
#[allow(dead_code)]
pub fn handle_input(&mut self, input: String) {
let instr = self.paused_instr.take().unwrap();
if self.is_debug_command(&input) {
if self.handle_debug_command(&input) {
self.ui.print("\n>");
}
return;
}
self.redos.clear();
if self.current_state.is_some() {
self.undos.push(self.current_state.take().unwrap());
}
let args = self.get_arguments(instr.operands.as_slice());
self.do_sread_second(args[0], args[1], input);
self.pc = instr.next;
}
#[allow(dead_code)]
pub fn restore(&mut self, data: String) {
let state = base64::decode(&data);
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();
}
}
#[allow(dead_code)]
pub fn load_savestate(&mut self, data: String) {
let state = base64::decode(&data).unwrap();
self.restore_state(state.as_slice());
}
}
impl Zmachine {
fn do_je(&self, a: u16, values: &[u16]) -> u16 {
if values.iter().any(|x| a == *x) { 1 } else { 0 }
}
fn do_jl(&self, a: u16, b: u16) -> u16 {
if (a as i16) < (b as i16) { 1 } else { 0 }
}
fn do_jg(&self, a: u16, b: u16) -> u16 {
if (a as i16) > (b as i16) { 1 } else { 0 }
}
fn do_dec_chk(&mut self, var: u16, value: u16) -> u16 {
let after = self.read_indirect_variable(var as u8) as i16 - 1;
self.write_indirect_variable(var as u8, after as u16);
if after < (value as i16) { 1 } else { 0 }
}
fn do_inc_chk(&mut self, var: u16, value: u16) -> u16 {
let after = self.read_indirect_variable(var as u8) as i16 + 1;
self.write_indirect_variable(var as u8, after as u16);
if after > (value as i16) { 1 } else { 0 }
}
fn do_jin(&self, obj1: u16, obj2: u16) -> u16 {
if self.get_parent(obj1) == obj2 { 1 } else { 0 }
}
fn do_test(&self, bitmap: u16, flags: u16) -> u16 {
if bitmap & flags == flags { 1 } else { 0 }
}
fn do_or(&self, a: u16, b: u16) -> u16 {
a | b
}
fn do_and(&self, a: u16, b: u16) -> u16 {
a & b
}
fn do_test_attr(&self, obj: u16, attr: u16) -> u16 {
self.test_attr(obj, attr)
}
fn do_set_attr(&mut self, obj: u16, attr: u16) {
self.set_attr(obj, attr)
}
fn do_clear_attr(&mut self, obj: u16, attr: u16) {
self.clear_attr(obj, attr)
}
fn do_store(&mut self, var: u16, value: u16) {
self.write_indirect_variable(var as u8, value);
}
fn do_insert_obj(&mut self, object: u16, destination: u16) {
self.insert_obj(object, destination);
}
fn do_loadw(&self, array: u16, index: u16) -> u16 {
self.memory.read_word((array + 2 * index) as usize)
}
fn do_loadb(&self, array: u16, index: u16) -> u16 {
self.memory.read_byte((array + index) as usize) as u16
}
fn do_get_prop(&self, object: u16, property_number: u16) -> u16 {
self.get_prop_value(object, property_number)
}
fn do_get_prop_addr(&self, object: u16, property_number: u16) -> u16 {
self.get_prop_addr(object, property_number) as u16
}
fn do_get_next_prop(&self, object: u16, property_number: u16) -> u16 {
self.get_next_prop(object, property_number)
}
fn do_add(&self, a: u16, b: u16) -> u16 {
(a as i16 + b as i16) as u16
}
fn do_sub(&self, a: u16, b: u16) -> u16 {
(a as i16 - b as i16) as u16
}
fn do_mul(&self, a: u16, b: u16) -> u16 {
(a as i16 * b as i16) as u16
}
fn do_div(&self, a: u16, b: u16) -> u16 {
(a as i16 / b as i16) as u16
}
fn do_mod(&self, a: u16, b: u16) -> u16 {
(a as i16 % b as i16) as u16
}
fn do_jz(&self, a: u16) -> u16 {
if a == 0 { 1 } else { 0 }
}
fn do_get_sibling(&self, object: u16) -> u16 {
self.get_sibling(object)
}
fn do_get_child(&self, object: u16) -> u16 {
self.get_child(object)
}
fn do_get_parent(&self, object: u16) -> u16 {
self.get_parent(object)
}
fn do_get_prop_len(&self, addr: u16) -> u16 {
self.get_prop_len(addr as usize) as u16
}
fn do_inc(&mut self, var: u16) {
let value = self.read_indirect_variable(var as u8);
self.write_indirect_variable(var as u8, (value as i16 + 1) as u16);
}
fn do_dec(&mut self, var: u16) {
let value = self.read_indirect_variable(var as u8);
self.write_indirect_variable(var as u8, (value as i16 - 1) as u16);
}
fn do_print_addr(&mut self, addr: u16) {
let zstring = self.read_zstring(addr as usize);
self.ui.print(&zstring);
}
fn do_remove_obj(&mut self, obj: u16) {
self.remove_obj(obj);
}
fn do_print_obj(&mut self, obj: u16) {
let name = self.get_object_name(obj);
self.ui.print_object(&name);
}
fn do_ret(&mut self, value: u16) {
self.return_from_routine(value);
}
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
};
}
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);
}
fn do_load(&mut self, var: u16) -> u16 {
self.read_indirect_variable(var as u8)
}
fn do_not(&self, value: u16) -> u16 {
!value
}
fn do_rtrue(&mut self) {
self.return_from_routine(1);
}
fn do_rfalse(&mut self) {
self.return_from_routine(0);
}
fn do_print(&mut self, instr: &Instruction) {
let text = instr.text.as_ref().expect("Can't print with no text!");
self.ui.print(text);
}
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);
}
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;
}
self.save_name = path.file_name().unwrap().to_string_lossy().into_owned();
let pc = instr.next - 1;
let data = self.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) {
self.process_result(instr, 1);
}
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;
}
self.save_name = path.file_name().unwrap().to_string_lossy().into_owned();
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) {
let byte = self.memory.read_byte(self.pc);
if self.version <= 3 {
if byte & 0b10000000 != 0 {
self.pc += (byte & 0b00111111) as usize - 2; } else {
self.pc += 1; }
} else {
self.pc += 1;
self.write_variable(byte, 2); }
}
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());
}
fn do_ret_popped(&mut self) {
let value = self.stack_pop();
self.return_from_routine(value);
}
fn do_newline(&mut self) {
self.ui.print("\n");
}
fn do_show_status(&self) {
self.update_status_bar();
}
fn do_verify(&self) -> u16 {
1
}
fn do_call(&mut self, instr: &Instruction, addr: u16, args: &[u16]) {
if addr == 0 {
self.process_result(instr, 0);
return;
}
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);
}
fn do_storew(&mut self, array: u16, index: u16, value: u16) {
self.memory.write_word((array + 2 * index) as usize, value);
}
fn do_storeb(&mut self, array: u16, index: u16, value: u16) {
self.memory.write_byte((array + index) as usize, value as u8);
}
fn do_put_prop(&mut self, obj: u16, prop: u16, value: u16) {
self.put_prop(obj, prop, value);
}
fn do_sread(&mut self, instr: &Instruction, text_addr: u16, parse_addr: u16) {
self.update_status_bar();
self.ui.print(" ");
let input = self.ui.get_user_input();
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);
self.redos.clear();
if self.current_state.is_some() {
self.undos.push(self.current_state.take().unwrap());
}
let location = self.get_object_name(self.read_global(0));
let state = self.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;
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();
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);
}
if parse_addr != 0 {
self.tokenise(input, parse_addr);
}
}
fn do_print_char(&mut self, chr: u16) {
self.ui.print(&(chr as u8 as char).to_string());
}
fn do_print_num(&mut self, signed: u16) {
self.ui.print(&(signed as i16).to_string());
}
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>() * range as f32).ceil() as u16
}
}
fn do_push(&mut self, value: u16) {
self.stack_push(value)
}
fn do_pull(&mut self, var: u16) {
let value = self.stack_pop();
self.write_indirect_variable(var as u8, value);
}
}
#[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(&format!("{}", out));
}
fn debug_dump(&mut self) {
let mut out = String::new();
write!(out, "PC @ {}\n", self.pc).unwrap();
for frame in &self.frames {
write!(out, "{}\n", 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; 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; 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);
write!(out, "{:2} {:?}\n", 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);
write!(out, "{:2} {:?}\n", 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();
return 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(&format!("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(&format!("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));
};
}
}