gruesome 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ require 'bit-struct'
2
+
3
+ module Gruesome
4
+ module Z
5
+
6
+ # Z-Story File Header
7
+ class Header < BitStruct
8
+ default_options :endian => :big
9
+
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
+ end
@@ -0,0 +1,50 @@
1
+ module Gruesome
2
+ module Z
3
+
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
+
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
+
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
+
38
+ if @destination != nil
39
+ line = line + " -> %" + sprintf("%02x", @destination)
40
+ end
41
+
42
+ if @branch_to != nil
43
+ line = line + " goto $" + sprintf("%04x", @branch_to) + " on " + @branch_on.to_s
44
+ end
45
+
46
+ line
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,71 @@
1
+ require_relative 'header'
2
+ require_relative 'memory'
3
+ require_relative 'decoder'
4
+ require_relative 'processor'
5
+ require_relative 'abbreviation_table'
6
+ require_relative 'object_table'
7
+
8
+ module Gruesome
9
+ module Z
10
+
11
+ # The class that initializes and maintains a Z-Machine
12
+ class Machine
13
+
14
+ # Will create a new virtual machine for the game file
15
+ def initialize(game_file)
16
+ file = File.open(game_file, "r")
17
+
18
+ # I. Create memory space
19
+
20
+ memory_size = file.size
21
+ @memory = Memory.new(file.read(memory_size))
22
+
23
+ # Set flags
24
+ flags = @memory.force_readb(0x01)
25
+ flags &= ~(0b1110000)
26
+ @memory.force_writeb(0x01, flags)
27
+
28
+ # Set flags 2
29
+ flags = @memory.force_readb(0x10)
30
+ flags &= ~(0b11111100)
31
+ @memory.force_writeb(0x10, flags)
32
+
33
+ # II. Read header (at address 0x0000) and associated tables
34
+ @header = Header.new(@memory.contents)
35
+ @object_table = ObjectTable.new(@memory)
36
+
37
+ # III. Instantiate CPU
38
+ @decoder = Decoder.new(@memory)
39
+ @processor = Processor.new(@memory)
40
+ end
41
+
42
+ def execute
43
+ while true do
44
+ i = @decoder.fetch
45
+ #var = @memory.readv(0)
46
+ #if var != nil
47
+ # puts "var %00 = " + sprintf("%04x", var)
48
+ # @memory.writev(0, var)
49
+ #end
50
+ #var = @memory.readv(1)
51
+ #if var != nil
52
+ # puts "var %01 = " + sprintf("%04x", @memory.readv(0x01))
53
+ #end
54
+ #puts "at $" + sprintf("%04x", @memory.program_counter) + ": " + i.to_s(@header.version)
55
+ @memory.program_counter += i.length
56
+
57
+ if i.opcode == Opcode::QUIT
58
+ break
59
+ end
60
+
61
+ begin
62
+ @processor.execute(i)
63
+ rescue RuntimeError => fuh
64
+ "error at $" + sprintf("%04x", @memory.program_counter) + ": " + i.to_s(@header.version)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,268 @@
1
+ require_relative 'header'
2
+
3
+ # The Z-Machine Memory is a simple array of bytes
4
+ #
5
+ # There are three regions: Dynamic, Static, and High
6
+ #
7
+ # Dynamic Memory can be read and written to by a program
8
+ # Static Memory can only be read
9
+ # High Memory cannot be accessed by load/store instructions
10
+ #
11
+ # High Memory can overlap Static, but never Dynamic
12
+ #
13
+ # Memory is stored in big endian
14
+
15
+ # Also included as memory space, yet separated from the
16
+ # RAM itself: the stack, program counter, and a routine
17
+ # call stack which holds the stacks of the currently
18
+ # invocated routines
19
+
20
+ # The stack is weird. Every function call starts with an empty stack
21
+ # and any work left in the stack upon a return is lost. So there are
22
+ # actually many stacks... one stack to hold the stacks in play, and
23
+ # a stack for each active function.
24
+ #
25
+ # The stack holds the return address and the (up to) 15 local variables
26
+ # for the routine as accessed by variables %01 to %0f.
27
+ #
28
+ # Variable %00 is the top of the stack, writing to it pushes, reading
29
+ # from it pulls.
30
+ #
31
+ # Illegal access to variables will halt the machine. Such as illegally
32
+ # accessing local variables that do not exist as the routine header
33
+ # will specify an exact number.
34
+
35
+ module Gruesome
36
+ module Z
37
+
38
+ # This class holds the memory for the virtual machine
39
+ class Memory
40
+ attr_accessor :program_counter
41
+ attr_reader :num_locals
42
+
43
+ def initialize(contents)
44
+ @call_stack = []
45
+ @stack = []
46
+ @memory = contents
47
+ @num_locals = 0
48
+
49
+ # Get the header information
50
+ @header = Header.new(@memory)
51
+ @program_counter = @header.entry
52
+
53
+ # With the header info, discover the bounds of each memory region
54
+ @dyn_base = 0x0
55
+ @dyn_limit = @header.static_mem_base
56
+
57
+ # Cannot Write to Static Memory
58
+ @static_base = @header.static_mem_base
59
+ @static_limit = @memory.length
60
+
61
+ # Cannot Access High Memory
62
+ @high_base = @header.high_mem_base
63
+ @high_limit = @memory.length
64
+
65
+ # Error if high memory overlaps dynamic memory
66
+ if @high_base < @dyn_limit
67
+ # XXX: ERROR
68
+ end
69
+
70
+ # Check machine endianess
71
+ @endian = [1].pack('S')[0] == 1 ? 'little' : 'big'
72
+ end
73
+
74
+ def packed_address_to_byte_address(address)
75
+ if @header.version <=3
76
+ address * 2
77
+ else
78
+ end
79
+ end
80
+
81
+ # Sets up the environment for a new routine
82
+ def push_routine(return_addr, num_locals, destination)
83
+ # pushes the stack onto the call stack
84
+ @call_stack.push @num_locals
85
+ @call_stack.push destination
86
+ @call_stack.push @stack
87
+
88
+ # empties the current stack
89
+ @stack = Array.new()
90
+
91
+ # pushes the return address onto the stack
92
+ @stack.push(return_addr)
93
+
94
+ # push locals
95
+ num_locals.times do
96
+ @stack.push 0
97
+ end
98
+
99
+ @num_locals = num_locals
100
+ end
101
+
102
+ # Tears down the environment for the current routine
103
+ def pop_routine()
104
+ # return the return address
105
+ return_addr = @stack[0]
106
+ @stack = @call_stack.pop
107
+ destination = @call_stack.pop
108
+ @num_locals = @call_stack.pop
109
+
110
+ {:destination => destination, :return_address => return_addr}
111
+ end
112
+
113
+ def readb(address)
114
+ if address < @high_base
115
+ force_readb(address)
116
+ else
117
+ # XXX: Access violation
118
+ raise "Access Violation accessing $" + sprintf("%04x", address)
119
+ nil
120
+ end
121
+ end
122
+
123
+ def readw(address)
124
+ if (address + 1) < @high_base
125
+ force_readw(address)
126
+ else
127
+ # XXX: Access violation
128
+ raise "Access Violation accessing $" + sprintf("%04x", address)
129
+ nil
130
+ end
131
+ end
132
+
133
+ def writeb(address, value)
134
+ if address < @static_base
135
+ force_writeb(address, value)
136
+ else
137
+ # XXX: Access violation
138
+ raise "Access Violation (W) accessing $" + sprintf("%04x", address)
139
+ nil
140
+ end
141
+ end
142
+
143
+ def writew(address, value)
144
+ if (address + 1) < @static_base
145
+ force_writew(address, value)
146
+ else
147
+ # XXX: Access violation
148
+ raise "Access Violation (W) accessing $" + sprintf("%04x", address)
149
+ nil
150
+ end
151
+ end
152
+
153
+ def force_readb(address)
154
+ if address < @memory.size
155
+ @memory.getbyte(address)
156
+ else
157
+ # XXX: Access Violation
158
+ raise "Major Access Violation accessing $" + sprintf("%04x", address)
159
+ nil
160
+ end
161
+ end
162
+
163
+ def force_readw(address)
164
+ if (address + 1) < @memory.size
165
+ if @endian == 'little'
166
+ (@memory.getbyte(address+1) << 8) | @memory.getbyte(address)
167
+ else
168
+ (@memory.getbyte(address) << 8) | @memory.getbyte(address+1)
169
+ end
170
+ else
171
+ # XXX: Access Violation
172
+ raise "Major Access Violation accessing $" + sprintf("%04x", address)
173
+ nil
174
+ end
175
+ end
176
+
177
+ def force_writeb(address, value)
178
+ if address < @memory.size
179
+ @memory.setbyte(address, (value & 255))
180
+ else
181
+ # XXX: Access Violation
182
+ raise "Major Access (W) Violation accessing $" + sprintf("%04x", address)
183
+ nil
184
+ end
185
+ end
186
+
187
+ def force_writew(address, value)
188
+ if (address + 1) < @memory.size
189
+ low_byte = value & 255
190
+ high_byte = (value >> 8) & 255
191
+
192
+ if @endian == 'little'
193
+ tmp = high_byte
194
+ high_byte = low_byte
195
+ low_byte = tmp
196
+ end
197
+
198
+ @memory.setbyte(address, high_byte)
199
+ @memory.setbyte(address+1, low_byte)
200
+ else
201
+ # XXX: Access Violation
202
+ raise "Major Access (W) Violation accessing $" + sprintf("%04x", address)
203
+ nil
204
+ end
205
+ end
206
+
207
+ def contents
208
+ @memory
209
+ end
210
+
211
+ # Read from variable number index
212
+ def readv(index)
213
+ if index == 0
214
+ # pop from stack
215
+ @stack.pop
216
+ elsif index >= 16
217
+ index -= 16
218
+ readw(@header.global_var_addr + (index*2))
219
+ elsif index <= @num_locals
220
+ @stack[index]
221
+ else
222
+ # XXX: Error
223
+ end
224
+ end
225
+
226
+ # Write value to variable number index
227
+ def writev(index, value)
228
+ value &= 65535
229
+ if index == 0
230
+ # push to stack
231
+ @stack.push value
232
+ elsif index >= 16
233
+ index -= 16
234
+ writew(@header.global_var_addr + (index*2), value)
235
+ elsif index <= @num_locals
236
+ @stack[index] = value
237
+ else
238
+ # XXX: Error
239
+ end
240
+ end
241
+
242
+ def force_readzstr(index, max_len = -1)
243
+ chrs = []
244
+ continue = true
245
+ orig_index = index
246
+
247
+ until continue == false do
248
+ if max_len != -1 and (index + 2 - orig_index) > max_len
249
+ break
250
+ end
251
+
252
+ byte1 = force_readb(index)
253
+ byte2 = force_readb(index+1)
254
+
255
+ index += 2
256
+
257
+ chrs << ((byte1 >> 2) & 0b11111)
258
+ chrs << (((byte1 & 0b11) << 3) | (byte2 >> 5))
259
+ chrs << (byte2 & 0b11111)
260
+
261
+ continue = (byte1 & 0b10000000) == 0
262
+ end
263
+
264
+ return [index - orig_index, chrs]
265
+ end
266
+ end
267
+ end
268
+ end