gruesome 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +35 -0
- data/Rakefile +15 -0
- data/bin/gruesome +9 -0
- data/gruesome.gemspec +25 -0
- data/lib/gruesome/cli.rb +70 -0
- data/lib/gruesome/logo.rb +28 -0
- data/lib/gruesome/machine.rb +17 -0
- data/lib/gruesome/version.rb +3 -0
- data/lib/gruesome/z/abbreviation_table.rb +30 -0
- data/lib/gruesome/z/decoder.rb +292 -0
- data/lib/gruesome/z/dictionary.rb +156 -0
- data/lib/gruesome/z/header.rb +46 -0
- data/lib/gruesome/z/instruction.rb +50 -0
- data/lib/gruesome/z/machine.rb +71 -0
- data/lib/gruesome/z/memory.rb +268 -0
- data/lib/gruesome/z/object_table.rb +430 -0
- data/lib/gruesome/z/opcode.rb +519 -0
- data/lib/gruesome/z/opcode_class.rb +15 -0
- data/lib/gruesome/z/operand_type.rb +15 -0
- data/lib/gruesome/z/processor.rb +399 -0
- data/lib/gruesome/z/zscii.rb +337 -0
- data/lib/gruesome.rb +7 -0
- data/spec/z/memory_spec.rb +90 -0
- data/spec/z/processor_spec.rb +1956 -0
- data/test/logo.txt +77 -0
- metadata +118 -0
@@ -0,0 +1,430 @@
|
|
1
|
+
require_relative 'header'
|
2
|
+
require_relative 'memory'
|
3
|
+
require_relative 'abbreviation_table'
|
4
|
+
|
5
|
+
module Gruesome
|
6
|
+
module Z
|
7
|
+
class ObjectTable
|
8
|
+
def initialize(memory)
|
9
|
+
@memory = memory
|
10
|
+
@header = Header.new(@memory.contents)
|
11
|
+
@abbreviation_table = AbbreviationTable.new(@memory)
|
12
|
+
|
13
|
+
@address = @header.object_tbl_addr
|
14
|
+
|
15
|
+
if @header.version <= 3
|
16
|
+
# versions 1-3 have 32 entries
|
17
|
+
@num_properties = 31
|
18
|
+
else
|
19
|
+
# versions 4+ have 32 entries
|
20
|
+
@num_properties = 63
|
21
|
+
end
|
22
|
+
|
23
|
+
@object_tree_address = @address + @num_properties*2
|
24
|
+
|
25
|
+
if @header.version <= 3
|
26
|
+
@attributes_size = 4
|
27
|
+
@object_id_size = 1
|
28
|
+
|
29
|
+
# Entry:
|
30
|
+
#
|
31
|
+
# Attributes (4 bytes)
|
32
|
+
# Parent Object ID (1 byte)
|
33
|
+
# Sibling Object ID (1 byte)
|
34
|
+
# Child Object ID (1 byte)
|
35
|
+
# Properties Address (2 bytes)
|
36
|
+
else
|
37
|
+
@attributes_size = 6
|
38
|
+
@object_id_size = 2
|
39
|
+
|
40
|
+
# Entry: (Object IDs are 2 bytes)
|
41
|
+
#
|
42
|
+
# Attributes (6 bytes)
|
43
|
+
# Parent Object ID (2 byte)
|
44
|
+
# Sibling Object ID (2 byte)
|
45
|
+
# Child Object ID (2 byte)
|
46
|
+
# Properties Address (2 bytes)
|
47
|
+
end
|
48
|
+
|
49
|
+
@obj_entry_size = @attributes_size + (@object_id_size * 3) + 2
|
50
|
+
|
51
|
+
# Check machine endianess
|
52
|
+
@endian = [1].pack('S')[0] == 1 ? 'little' : 'big'
|
53
|
+
end
|
54
|
+
|
55
|
+
def property_default(index)
|
56
|
+
index -= 1
|
57
|
+
index %= @num_properties
|
58
|
+
|
59
|
+
# The first thing in the table is the property defaults list
|
60
|
+
# So, simply lookup the 16-bit word at the entry given by index
|
61
|
+
prop_default_addr = @address + index*2
|
62
|
+
@memory.force_readw(prop_default_addr)
|
63
|
+
end
|
64
|
+
|
65
|
+
def object_entry(index)
|
66
|
+
index -= 1
|
67
|
+
obj_entry_addr = @object_tree_address + (index * @obj_entry_size)
|
68
|
+
|
69
|
+
attributes_address = obj_entry_addr
|
70
|
+
cur_addr = attributes_address
|
71
|
+
|
72
|
+
# read the attributes
|
73
|
+
attribute_bytes = []
|
74
|
+
@attributes_size.times do
|
75
|
+
attribute_bytes << @memory.force_readb(cur_addr)
|
76
|
+
cur_addr += 1
|
77
|
+
end
|
78
|
+
|
79
|
+
# read the object ids of associated objects
|
80
|
+
ids = (0..2).map do |i|
|
81
|
+
if @object_id_size == 2
|
82
|
+
id = @memory.force_readw(cur_addr)
|
83
|
+
cur_addr += 2
|
84
|
+
else
|
85
|
+
id = @memory.force_readb(cur_addr)
|
86
|
+
cur_addr += 1
|
87
|
+
end
|
88
|
+
id
|
89
|
+
end
|
90
|
+
|
91
|
+
properties_address = @memory.force_readw(cur_addr)
|
92
|
+
|
93
|
+
# return a hash with the information of the entry
|
94
|
+
{ :attributes_address => attributes_address,
|
95
|
+
:attributes => attribute_bytes,
|
96
|
+
:parent_id => ids[0],
|
97
|
+
:sibling_id => ids[1],
|
98
|
+
:child_id => ids[2],
|
99
|
+
:properties_address => properties_address }
|
100
|
+
end
|
101
|
+
|
102
|
+
def object_get_child(index)
|
103
|
+
object_entry(index)[:child_id]
|
104
|
+
end
|
105
|
+
|
106
|
+
def object_get_sibling(index)
|
107
|
+
object_entry(index)[:sibling_id]
|
108
|
+
end
|
109
|
+
|
110
|
+
def object_get_parent(index)
|
111
|
+
object_entry(index)[:parent_id]
|
112
|
+
end
|
113
|
+
|
114
|
+
def object_set_child(index, child_id)
|
115
|
+
entry = object_entry(index)
|
116
|
+
addr = entry[:attributes_address] + @attributes_size + @object_id_size*2
|
117
|
+
|
118
|
+
if @object_id_size == 1
|
119
|
+
@memory.force_writeb(addr, child_id)
|
120
|
+
else
|
121
|
+
@memory.force_writew(addr, child_id)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def object_set_sibling(index, sibling_id)
|
126
|
+
entry = object_entry(index)
|
127
|
+
addr = entry[:attributes_address] + @attributes_size + @object_id_size
|
128
|
+
|
129
|
+
if @object_id_size == 1
|
130
|
+
@memory.force_writeb(addr, sibling_id)
|
131
|
+
else
|
132
|
+
@memory.force_writew(addr, sibling_id)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def object_set_parent(index, parent_id)
|
137
|
+
entry = object_entry(index)
|
138
|
+
addr = entry[:attributes_address] + @attributes_size
|
139
|
+
|
140
|
+
if @object_id_size == 1
|
141
|
+
@memory.force_writeb(addr, parent_id)
|
142
|
+
else
|
143
|
+
@memory.force_writew(addr, parent_id)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def object_insert_object(index, new_child)
|
148
|
+
# remove child from old parent
|
149
|
+
if object_get_parent(new_child) != 0
|
150
|
+
object_remove_object new_child
|
151
|
+
end
|
152
|
+
|
153
|
+
# add to new parent
|
154
|
+
object_set_sibling(new_child, object_get_child(index))
|
155
|
+
object_set_child(index, new_child)
|
156
|
+
object_set_parent(new_child, index)
|
157
|
+
end
|
158
|
+
|
159
|
+
def object_remove_object(index)
|
160
|
+
old_parent = object_get_parent(index)
|
161
|
+
object_set_parent(index, 0)
|
162
|
+
|
163
|
+
if old_parent != 0
|
164
|
+
current_child = object_get_child(old_parent)
|
165
|
+
if current_child == index
|
166
|
+
# if we are the primary child of our parent, we removed
|
167
|
+
# ourselves... so we have to set a new primary child
|
168
|
+
# to be my sibling
|
169
|
+
object_set_child(old_parent, object_get_sibling(index))
|
170
|
+
else
|
171
|
+
# if not, we are within some list of siblings
|
172
|
+
# we need to remove ourself from this linked list
|
173
|
+
# whose head is current_child
|
174
|
+
|
175
|
+
last = current_child
|
176
|
+
sibling = object_get_sibling(current_child)
|
177
|
+
while sibling != 0
|
178
|
+
if sibling == index
|
179
|
+
# found me!
|
180
|
+
# now, set the sibling of the previous one
|
181
|
+
# to my sibling (removing me from the chain)
|
182
|
+
object_set_sibling(last, object_get_sibling(index))
|
183
|
+
|
184
|
+
# break out of the loop
|
185
|
+
break
|
186
|
+
end
|
187
|
+
last = sibling
|
188
|
+
sibling = object_get_sibling(sibling)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def object_short_text(index)
|
195
|
+
entry = object_entry(index)
|
196
|
+
prop_address = entry[:properties_address]
|
197
|
+
|
198
|
+
# text_len is the maximum number of bytes the string can have
|
199
|
+
text_len = @memory.force_readb(prop_address) * 2
|
200
|
+
chrs = @memory.force_readzstr(prop_address+1, text_len)[1]
|
201
|
+
|
202
|
+
ZSCII.translate(0, @header.version, chrs, @abbreviation_table)
|
203
|
+
end
|
204
|
+
|
205
|
+
def attribute_number_to_byte_bit_pair(attribute_number)
|
206
|
+
# get the byte and bit number
|
207
|
+
#
|
208
|
+
# Note: it counts from the MSB, so it has to
|
209
|
+
# reverse the bit_number by subtracting from 7
|
210
|
+
#
|
211
|
+
# That is, attribute 7 is byte 0, bit 0
|
212
|
+
# attribute 17 is byte 2, bit 6
|
213
|
+
# etc
|
214
|
+
|
215
|
+
byte_number = (attribute_number / 8).to_i
|
216
|
+
bit_number = attribute_number % 8
|
217
|
+
bit_number = 7 - bit_number
|
218
|
+
|
219
|
+
{ :byte => byte_number, :bit => bit_number }
|
220
|
+
end
|
221
|
+
private :attribute_number_to_byte_bit_pair
|
222
|
+
|
223
|
+
def object_has_attribute?(index, attribute_number)
|
224
|
+
entry = object_entry(index)
|
225
|
+
|
226
|
+
location = attribute_number_to_byte_bit_pair(attribute_number)
|
227
|
+
attribute_byte = entry[:attributes][location[:byte]]
|
228
|
+
mask = 1 << location[:bit]
|
229
|
+
|
230
|
+
(attribute_byte & mask) > 0
|
231
|
+
end
|
232
|
+
|
233
|
+
def object_set_attribute(index, attribute_number)
|
234
|
+
entry = object_entry(index)
|
235
|
+
|
236
|
+
location = attribute_number_to_byte_bit_pair(attribute_number)
|
237
|
+
attribute_byte = entry[:attributes][location[:byte]]
|
238
|
+
mask = 1 << location[:bit]
|
239
|
+
|
240
|
+
attribute_byte |= mask
|
241
|
+
|
242
|
+
byte_addr = entry[:attributes_address] + location[:byte]
|
243
|
+
@memory.force_writeb(byte_addr, attribute_byte)
|
244
|
+
end
|
245
|
+
|
246
|
+
def object_clear_attribute(index, attribute_number)
|
247
|
+
entry = object_entry(index)
|
248
|
+
|
249
|
+
location = attribute_number_to_byte_bit_pair(attribute_number)
|
250
|
+
attribute_byte = entry[:attributes][location[:byte]]
|
251
|
+
mask = 1 << location[:bit]
|
252
|
+
|
253
|
+
attribute_byte &= ~mask
|
254
|
+
|
255
|
+
byte_addr = entry[:attributes_address] + location[:byte]
|
256
|
+
@memory.force_writeb(byte_addr, attribute_byte)
|
257
|
+
end
|
258
|
+
|
259
|
+
def object_get_size_and_number(prop_address)
|
260
|
+
size = 0
|
261
|
+
property_number = 0
|
262
|
+
if @header.version <= 3
|
263
|
+
size = @memory.force_readb(prop_address)
|
264
|
+
prop_address += 1
|
265
|
+
|
266
|
+
# when size is 0, this is the end of the list
|
267
|
+
if size == 0
|
268
|
+
return nil
|
269
|
+
end
|
270
|
+
|
271
|
+
# size is considered 32 times the data size minus one
|
272
|
+
# property number is the first 5 bits
|
273
|
+
property_number = size & 0b11111
|
274
|
+
|
275
|
+
size = size >> 5
|
276
|
+
size += 1
|
277
|
+
else
|
278
|
+
# in versions 4+, the size and property number are given
|
279
|
+
# as two bytes.
|
280
|
+
#
|
281
|
+
# if bit 7 is set in the first byte:
|
282
|
+
# The second byte is read where first 6 bits
|
283
|
+
# indicate size, bit 6 is ignored, bit 7 is set
|
284
|
+
# if bit 7 is clear in the first byte:
|
285
|
+
# bit 6 is clear = size of 1
|
286
|
+
# bit 6 is set = size of 2
|
287
|
+
#
|
288
|
+
# property number is first 6 bits of first byte
|
289
|
+
|
290
|
+
first_byte = @memory.force_readb(prop_address)
|
291
|
+
prop_address += 1
|
292
|
+
|
293
|
+
property_number = first_byte & 0b111111
|
294
|
+
if first_byte & 0b10000000 > 0
|
295
|
+
# bit 7 is set
|
296
|
+
second_byte = @memory.force_readb(prop_address)
|
297
|
+
prop_address += 1
|
298
|
+
|
299
|
+
size = second_byte & 0b111111
|
300
|
+
|
301
|
+
# a size of 0 is allowed, and will indicate a size of 64
|
302
|
+
if size == 0
|
303
|
+
size = 64
|
304
|
+
end
|
305
|
+
else
|
306
|
+
# bit 7 is clear
|
307
|
+
if first_byte & 0b1000000 > 0
|
308
|
+
# bit 6 is set
|
309
|
+
size = 2
|
310
|
+
else
|
311
|
+
# bit 6 is clear
|
312
|
+
size = 1
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
{ :size => size, :property_number => property_number, :property_data_address => prop_address }
|
318
|
+
end
|
319
|
+
|
320
|
+
def object_properties(index)
|
321
|
+
entry = object_entry(index)
|
322
|
+
prop_address = entry[:properties_address]
|
323
|
+
|
324
|
+
# get the length of the string in bytes
|
325
|
+
text_len = @memory.force_readb(prop_address) * 2
|
326
|
+
prop_address += 1
|
327
|
+
|
328
|
+
prop_address += text_len
|
329
|
+
|
330
|
+
properties = {}
|
331
|
+
|
332
|
+
while true do
|
333
|
+
entry_info = object_get_size_and_number(prop_address)
|
334
|
+
|
335
|
+
if entry_info == nil
|
336
|
+
break
|
337
|
+
end
|
338
|
+
|
339
|
+
# regardless of version, we now have the property size and the number
|
340
|
+
|
341
|
+
properties[entry_info[:property_number]] = {:size => entry_info[:size], :property_data_address => entry_info[:property_data_address]}
|
342
|
+
prop_address = entry_info[:property_data_address] + entry_info[:size]
|
343
|
+
end
|
344
|
+
|
345
|
+
properties
|
346
|
+
end
|
347
|
+
|
348
|
+
def object_get_property(index, property_number)
|
349
|
+
properties = object_properties(index)
|
350
|
+
|
351
|
+
property_data = []
|
352
|
+
|
353
|
+
property_info = properties[property_number]
|
354
|
+
|
355
|
+
if property_info == nil
|
356
|
+
property_data = nil
|
357
|
+
else
|
358
|
+
address = property_info[:property_data_address]
|
359
|
+
properties[property_number][:size].times do
|
360
|
+
property_data << @memory.force_readb(address)
|
361
|
+
address += 1
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
property_data
|
366
|
+
end
|
367
|
+
|
368
|
+
def object_get_property_addr(index, property_number)
|
369
|
+
properties = object_properties(index)
|
370
|
+
|
371
|
+
property_info = properties[property_number]
|
372
|
+
|
373
|
+
if property_info == nil
|
374
|
+
0
|
375
|
+
else
|
376
|
+
property_info[:property_data_address]
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def object_get_property_data_size_from_address(address)
|
381
|
+
if @header.version <= 3
|
382
|
+
prop_address = address - 1
|
383
|
+
else
|
384
|
+
first_byte = @memory.force_readb(address-1)
|
385
|
+
|
386
|
+
if first_byte & 0b10000000 > 0
|
387
|
+
prop_address = address - 2
|
388
|
+
else
|
389
|
+
prop_address = address - 1
|
390
|
+
end
|
391
|
+
end
|
392
|
+
object_get_size_and_number(prop_address)[:size]
|
393
|
+
end
|
394
|
+
|
395
|
+
def object_get_property_word(index, property_number)
|
396
|
+
property_data = object_get_property(index, property_number)
|
397
|
+
if property_data == nil
|
398
|
+
property_default(property_number)
|
399
|
+
elsif property_data.size > 1
|
400
|
+
if @endian == 'little'
|
401
|
+
(property_data[1] << 8) | property_data[0]
|
402
|
+
else
|
403
|
+
(property_data[0] << 8) | property_data[1]
|
404
|
+
end
|
405
|
+
else
|
406
|
+
property_data[0]
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def object_set_property_word(index, property_number, value)
|
411
|
+
properties = object_properties(index)
|
412
|
+
|
413
|
+
property_info = properties[property_number]
|
414
|
+
|
415
|
+
if property_info == nil
|
416
|
+
raise "put_prop attempted to set a property value for a property that did not exist"
|
417
|
+
else
|
418
|
+
if property_info[:size] == 1
|
419
|
+
value &= 0xff
|
420
|
+
@memory.force_writeb(property_info[:property_data_address], value)
|
421
|
+
elsif property_info[:size] == 2
|
422
|
+
@memory.force_writew(property_info[:property_data_address], value)
|
423
|
+
else
|
424
|
+
raise "put_prop attempted to set a property whose data size is larger than a word"
|
425
|
+
end
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|