gruesome 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -2
- data/lib/gruesome/cli.rb +62 -62
- data/lib/gruesome/logo.rb +8 -8
- data/lib/gruesome/machine.rb +11 -11
- data/lib/gruesome/version.rb +1 -1
- data/lib/gruesome/z/abbreviation_table.rb +17 -17
- data/lib/gruesome/z/decoder.rb +273 -273
- data/lib/gruesome/z/dictionary.rb +144 -144
- data/lib/gruesome/z/header.rb +40 -40
- data/lib/gruesome/z/instruction.rb +42 -42
- data/lib/gruesome/z/machine.rb +51 -50
- data/lib/gruesome/z/memory.rb +301 -232
- data/lib/gruesome/z/object_table.rb +424 -424
- data/lib/gruesome/z/opcode.rb +489 -491
- data/lib/gruesome/z/opcode_class.rb +9 -9
- data/lib/gruesome/z/operand_type.rb +10 -10
- data/lib/gruesome/z/processor.rb +378 -361
- data/lib/gruesome/z/zscii.rb +307 -307
- data/lib/gruesome.rb +3 -3
- data/spec/z/memory_spec.rb +72 -72
- data/spec/z/processor_spec.rb +1883 -1883
- metadata +3 -3
@@ -9,148 +9,148 @@ require_relative 'header'
|
|
9
9
|
require_relative 'zscii'
|
10
10
|
|
11
11
|
module Gruesome
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
12
|
+
module Z
|
13
|
+
class Dictionary
|
14
|
+
def initialize(memory)
|
15
|
+
@memory = memory
|
16
|
+
@header = Header.new(@memory.contents)
|
17
|
+
|
18
|
+
@abbreviation_table = AbbreviationTable.new(@memory)
|
19
|
+
|
20
|
+
@lookup_cache = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def address
|
24
|
+
@header.dictionary_addr
|
25
|
+
end
|
26
|
+
|
27
|
+
def address_of_entries
|
28
|
+
addr = self.address
|
29
|
+
num_input_codes = @memory.force_readb(addr)
|
30
|
+
addr += 1
|
31
|
+
addr += num_input_codes
|
32
|
+
addr += 3
|
33
|
+
|
34
|
+
addr
|
35
|
+
end
|
36
|
+
|
37
|
+
def entry_length
|
38
|
+
addr = self.address_of_entries - 3
|
39
|
+
return @memory.force_readb(addr)
|
40
|
+
end
|
41
|
+
|
42
|
+
def number_of_words
|
43
|
+
addr = self.address_of_entries - 2
|
44
|
+
return @memory.force_readw(addr)
|
45
|
+
end
|
46
|
+
|
47
|
+
def word_address(index)
|
48
|
+
addr = address_of_entries
|
49
|
+
addr += entry_length * index
|
50
|
+
|
51
|
+
return addr
|
52
|
+
end
|
53
|
+
|
54
|
+
def word(index)
|
55
|
+
addr = word_address(index)
|
56
|
+
|
57
|
+
codes = nil
|
58
|
+
|
59
|
+
if @header.version <= 3
|
60
|
+
codes = @memory.force_readzstr(addr, 4)[1]
|
61
|
+
else
|
62
|
+
codes = @memory.force_readzstr(addr, 6)[1]
|
63
|
+
end
|
64
|
+
|
65
|
+
str = ZSCII.translate(0, @header.version, codes, @abbreviation_table)
|
66
|
+
@lookup_cache[str] = index
|
67
|
+
return str
|
68
|
+
end
|
69
|
+
|
70
|
+
def lookup_word(token)
|
71
|
+
if token.length > 6 and @header.version <= 3
|
72
|
+
token = token[0..5]
|
73
|
+
elsif token.length > 9
|
74
|
+
token = token[0..8]
|
75
|
+
end
|
76
|
+
cached_index = @lookup_cache[token]
|
77
|
+
if cached_index != nil
|
78
|
+
if self.word(cached_index) == token
|
79
|
+
return cached_index
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
number_of_words.times do |i|
|
84
|
+
if word(i) == token
|
85
|
+
cached_index = i
|
86
|
+
break
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
cached_index
|
91
|
+
end
|
92
|
+
|
93
|
+
def word_separators
|
94
|
+
addr = self.address
|
95
|
+
|
96
|
+
# the number of word separators
|
97
|
+
num_input_codes = @memory.force_readb(addr)
|
98
|
+
addr += 1
|
99
|
+
|
100
|
+
codes = []
|
101
|
+
num_input_codes.times do
|
102
|
+
codes << @memory.force_readb(addr)
|
103
|
+
addr += 1
|
104
|
+
end
|
105
|
+
|
106
|
+
codes = codes.map do |i|
|
107
|
+
ZSCII.translate_Zchar(i)
|
108
|
+
end
|
109
|
+
|
110
|
+
return codes
|
111
|
+
end
|
112
|
+
|
113
|
+
def tokenize(line)
|
114
|
+
orig_line = line
|
115
|
+
|
116
|
+
# I. ensure that word separators are surrounded by spaces
|
117
|
+
self.word_separators.each do |sep|
|
118
|
+
line = line.gsub(sep, " " + sep + " ")
|
119
|
+
end
|
120
|
+
|
121
|
+
# II. break up the line into words delimited by spaces
|
122
|
+
tokens = line.split(' ')
|
123
|
+
|
124
|
+
line = orig_line
|
125
|
+
last_pos = 0
|
126
|
+
ret = tokens.map do |token|
|
127
|
+
index = line.index(token)
|
128
|
+
line = line[index+token.size..-1]
|
129
|
+
|
130
|
+
index = index + last_pos
|
131
|
+
last_pos = index + token.size
|
132
|
+
|
133
|
+
index
|
134
|
+
{ :position => index, :string => token }
|
135
|
+
end
|
136
|
+
|
137
|
+
ret
|
138
|
+
end
|
139
|
+
|
140
|
+
def parse(tokens)
|
141
|
+
ret = []
|
142
|
+
tokens.each do |token|
|
143
|
+
index = lookup_word(token[:string])
|
144
|
+
if index != nil
|
145
|
+
addr = word_address(index)
|
146
|
+
else
|
147
|
+
addr = 0
|
148
|
+
end
|
149
|
+
ret << { :size => token[:string].size, :address => addr, :position => token[:position] }
|
150
|
+
end
|
151
|
+
|
152
|
+
ret
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
156
|
end
|
data/lib/gruesome/z/header.rb
CHANGED
@@ -1,46 +1,46 @@
|
|
1
1
|
require 'bit-struct'
|
2
2
|
|
3
3
|
module Gruesome
|
4
|
-
|
4
|
+
module Z
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
# Z-Story File Header
|
7
|
+
class Header < BitStruct
|
8
|
+
default_options :endian => :big
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
10
|
+
unsigned :version, 8, "Z-Machine Version"
|
11
|
+
unsigned :availablity_flags, 8, "Availability Flags"
|
12
|
+
unsigned :reservedw1, 16, "Reserved Word 1"
|
13
|
+
unsigned :high_mem_base, 16, "High Memory Base"
|
14
|
+
unsigned :entry, 16, "Entry Point"
|
15
|
+
unsigned :dictionary_addr, 16, "Address of Dictionary"
|
16
|
+
unsigned :object_tbl_addr, 16, "Address of Object Table"
|
17
|
+
unsigned :global_var_addr, 16, "Address of Global Variables Table"
|
18
|
+
unsigned :static_mem_base, 16, "Static Memory Base"
|
19
|
+
unsigned :capability_flags, 8, "Desired Capability Flags"
|
20
|
+
unsigned :reservedb1, 8, "Reserved Byte 1"
|
21
|
+
unsigned :reservedw2, 16, "Reserved Word 2"
|
22
|
+
unsigned :reservedw3, 16, "Reserved Word 3"
|
23
|
+
unsigned :reservedw4, 16, "Reserved Word 4"
|
24
|
+
unsigned :abbrev_tbl_addr, 16, "Address of Abbreviations Table"
|
25
|
+
unsigned :file_length, 16, "Length of File"
|
26
|
+
unsigned :checksum, 16, "Checksum of File"
|
27
|
+
unsigned :interpreter_number, 8, "Interpreter Number"
|
28
|
+
unsigned :interpreter_version, 8, "Interpreter Version"
|
29
|
+
unsigned :screen_height, 8, "Screen Height (in Lines)"
|
30
|
+
unsigned :screen_width, 8, "Screen Height (in Characters)"
|
31
|
+
unsigned :screen_width_units, 16, "Screen Width (in Units)"
|
32
|
+
unsigned :screen_height_units, 16, "Screen Height (in Units)"
|
33
|
+
unsigned :font_width, 8, "Font Width (in Units)"
|
34
|
+
unsigned :font_height, 8, "Font Height (in Units)"
|
35
|
+
unsigned :routines_addr, 16, "Offset to Routines (Divided by 8)"
|
36
|
+
unsigned :static_str_addr, 16, "Offset to Static Strings (Divided by 8)"
|
37
|
+
unsigned :background_color, 8, "Default Background Color"
|
38
|
+
unsigned :foreground_color, 8, "Default Foreground Color"
|
39
|
+
unsigned :terminating_chars_tbl_addr, 16, "Address of Terminating Characters Table"
|
40
|
+
unsigned :output_stream_3_width, 16, "Total Width of Characters Send to Output Stream 3"
|
41
|
+
unsigned :revision_number, 16, "Standard Revision Number"
|
42
|
+
unsigned :alphabet_tbl_addr, 16, "Address of Alphabet Table"
|
43
|
+
unsigned :header_ext_tbl_addr, 16, "Address of Header Extension Table"
|
44
|
+
end
|
45
|
+
end
|
46
46
|
end
|
@@ -1,50 +1,50 @@
|
|
1
1
|
module Gruesome
|
2
|
-
|
2
|
+
module Z
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
4
|
+
# A Z-Machine instruction
|
5
|
+
class Instruction
|
6
|
+
attr_reader :opcode # the opcode
|
7
|
+
attr_reader :types # the types of the operands
|
8
|
+
attr_reader :operands # the operands given to the instruction
|
9
|
+
attr_reader :destination # the destination variable to place the result
|
10
|
+
attr_reader :branch_to # the address to set the pc when branch is taken
|
11
|
+
# also... if 0, return true from routine
|
12
|
+
# if 1, return false from routine
|
13
|
+
attr_reader :branch_on # the condition is matched against this
|
14
|
+
attr_reader :length # instruction size in number of bytes
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
16
|
+
def initialize(opcode, types, operands, destination, branch_destination, branch_condition, length)
|
17
|
+
@opcode = opcode
|
18
|
+
@types = types
|
19
|
+
@operands = operands
|
20
|
+
@destination = destination
|
21
|
+
@branch_to = branch_destination
|
22
|
+
@branch_on = branch_condition
|
23
|
+
@length = length
|
24
|
+
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
def to_s(version)
|
27
|
+
line = Opcode.name(@opcode, version)
|
28
|
+
idx = -1
|
29
|
+
line = line + @operands.inject("") do |result, element|
|
30
|
+
idx += 1
|
31
|
+
if @types[idx] == OperandType::VARIABLE
|
32
|
+
result + " %" + sprintf("%02x", element)
|
33
|
+
else
|
34
|
+
result + " " + element.to_s
|
35
|
+
end
|
36
|
+
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
if @destination != nil
|
39
|
+
line = line + " -> %" + sprintf("%02x", @destination)
|
40
|
+
end
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
if @branch_to != nil
|
43
|
+
line = line + " goto $" + sprintf("%04x", @branch_to) + " on " + @branch_on.to_s
|
44
|
+
end
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
line
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
50
|
end
|
data/lib/gruesome/z/machine.rb
CHANGED
@@ -6,66 +6,67 @@ require_relative 'abbreviation_table'
|
|
6
6
|
require_relative 'object_table'
|
7
7
|
|
8
8
|
module Gruesome
|
9
|
-
|
9
|
+
module Z
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
# The class that initializes and maintains a Z-Machine
|
12
|
+
class Machine
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
# Will create a new virtual machine for the game file
|
15
|
+
def initialize(game_file)
|
16
|
+
file = File.open(game_file, "rb")
|
17
17
|
|
18
|
-
|
18
|
+
# I. Create memory space
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
memory_size = File.size(game_file)
|
21
|
+
save_file_name = File.basename(file, File.extname(file)) + ".sav"
|
22
|
+
@memory = Memory.new(file.read(memory_size), save_file_name)
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
# Set flags
|
25
|
+
flags = @memory.force_readb(0x01)
|
26
|
+
flags &= ~(0b1110000)
|
27
|
+
@memory.force_writeb(0x01, flags)
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
# Set flags 2
|
30
|
+
flags = @memory.force_readb(0x10)
|
31
|
+
flags &= ~(0b11111100)
|
32
|
+
@memory.force_writeb(0x10, flags)
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
# II. Read header (at address 0x0000) and associated tables
|
35
|
+
@header = Header.new(@memory.contents)
|
36
|
+
@object_table = ObjectTable.new(@memory)
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
# III. Instantiate CPU
|
39
|
+
@decoder = Decoder.new(@memory)
|
40
|
+
@processor = Processor.new(@memory)
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
43
|
+
def execute
|
44
|
+
while true do
|
45
|
+
i = @decoder.fetch
|
46
|
+
# var = @memory.readv(0)
|
47
|
+
# if var != nil
|
48
|
+
# puts "var %00 = " + sprintf("%04x", var)
|
49
|
+
# @memory.writev(0, var)
|
50
|
+
# end
|
51
|
+
# var = @memory.readv(1)
|
52
|
+
# if var != nil
|
53
|
+
# puts "var %01 = " + sprintf("%04x", @memory.readv(0x01))
|
54
|
+
# end
|
55
|
+
# puts "at $" + sprintf("%04x", @memory.program_counter) + ": " + i.to_s(@header.version)
|
56
|
+
@memory.program_counter += i.length
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
58
|
+
if i.opcode == Opcode::QUIT
|
59
|
+
break
|
60
|
+
end
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
62
|
+
begin
|
63
|
+
@processor.execute(i)
|
64
|
+
rescue RuntimeError => fuh
|
65
|
+
puts "error at $" + sprintf("%04x", @memory.program_counter) + ": " + i.to_s(@header.version)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
70
71
|
end
|
71
72
|
|