Class: Gruesome::Z::ObjectTable

Inherits:
Object
  • Object
show all
Defined in:
lib/gruesome/z/object_table.rb

Instance Method Summary collapse

Constructor Details

#initialize(memory) ⇒ ObjectTable

Returns a new instance of ObjectTable.



8
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
46
47
48
49
50
51
52
53
# File 'lib/gruesome/z/object_table.rb', line 8

def initialize(memory)
  @memory = memory
  @header = Header.new(@memory.contents)
  @abbreviation_table = AbbreviationTable.new(@memory)

  @address = @header.object_tbl_addr

  if @header.version <= 3
    # versions 1-3 have 32 entries
    @num_properties = 31
  else
    # versions 4+ have 32 entries
    @num_properties = 63
  end

  @object_tree_address = @address + @num_properties*2

  if @header.version <= 3
    @attributes_size = 4
    @object_id_size = 1

    # Entry:
    #
    # Attributes (4 bytes)
    # Parent Object ID (1 byte)
    # Sibling Object ID (1 byte)
    # Child Object ID (1 byte)
    # Properties Address (2 bytes)
  else
    @attributes_size = 6
    @object_id_size = 2

    # Entry: (Object IDs are 2 bytes)
    #
    # Attributes (6 bytes)
    # Parent Object ID (2 byte)
    # Sibling Object ID (2 byte)
    # Child Object ID (2 byte)
    # Properties Address (2 bytes)
  end

  @obj_entry_size = @attributes_size + (@object_id_size * 3) + 2

  # Check machine endianess
  @endian = [1].pack('S')[0] == 1 ? 'little' : 'big'
end

Instance Method Details

#object_clear_attribute(index, attribute_number) ⇒ Object



246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/gruesome/z/object_table.rb', line 246

def object_clear_attribute(index, attribute_number)
  entry = object_entry(index)

  location = attribute_number_to_byte_bit_pair(attribute_number)
  attribute_byte = entry[:attributes][location[:byte]]
  mask = 1 << location[:bit]

  attribute_byte &= ~mask

  byte_addr = entry[:attributes_address] + location[:byte]
  @memory.force_writeb(byte_addr, attribute_byte)
end

#object_entry(index) ⇒ Object



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
# File 'lib/gruesome/z/object_table.rb', line 65

def object_entry(index)
  index -= 1
  obj_entry_addr = @object_tree_address + (index * @obj_entry_size)

  attributes_address = obj_entry_addr
  cur_addr = attributes_address

  # read the attributes
  attribute_bytes = []
  @attributes_size.times do
    attribute_bytes << @memory.force_readb(cur_addr)
    cur_addr += 1
  end

  # read the object ids of associated objects
  ids = (0..2).map do |i|
    if @object_id_size == 2
      id = @memory.force_readw(cur_addr)
      cur_addr += 2
    else
      id = @memory.force_readb(cur_addr)
      cur_addr += 1
    end
    id
  end

  properties_address = @memory.force_readw(cur_addr)

  # return a hash with the information of the entry
  {  :attributes_address => attributes_address,
    :attributes => attribute_bytes, 
    :parent_id => ids[0], 
    :sibling_id => ids[1], 
    :child_id => ids[2], 
    :properties_address => properties_address  }
end

#object_get_child(index) ⇒ Object



102
103
104
# File 'lib/gruesome/z/object_table.rb', line 102

def object_get_child(index)
  object_entry(index)[:child_id]
end

#object_get_next_property(index, property_number) ⇒ Object



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/gruesome/z/object_table.rb', line 408

def object_get_next_property(index, property_number)
 properties = object_properties_array(index)
 if property_number == 0
  next_number = properties[0][:property_number]
 else
  cur_prop_index = nil
  properties.each_with_index do |p, i|
	  if p[:property_number] == property_number
		  cur_prop_index = i
	  end
  end

  if cur_prop_index == nil
	  # Error: Cannot get the next property when the property given is invalid
	  raise "get_prop_addr gave incorrect property_number"
  end

  next_prop_index = cur_prop_index + 1

  if next_prop_index == properties.length
	  next_number = 0
  else
	  next_number = properties[next_prop_index][:property_number]
  end
 end
 next_number
end

#object_get_parent(index) ⇒ Object



110
111
112
# File 'lib/gruesome/z/object_table.rb', line 110

def object_get_parent(index)
  object_entry(index)[:parent_id]
end

#object_get_property(index, property_number) ⇒ Object



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/gruesome/z/object_table.rb', line 376

def object_get_property(index, property_number)
  properties = object_properties(index)

  property_data = []

  property_info = properties[property_number]

  if property_info == nil
    property_data = nil
  else
    address = property_info[:property_data_address]
    properties[property_number][:size].times do
      property_data << @memory.force_readb(address)
      address += 1
    end
  end

  property_data
end

#object_get_property_addr(index, property_number) ⇒ Object



396
397
398
399
400
401
402
403
404
405
406
# File 'lib/gruesome/z/object_table.rb', line 396

def object_get_property_addr(index, property_number)
  properties = object_properties(index)

  property_info = properties[property_number]

  if property_info == nil
    0
  else
    property_info[:property_data_address]
  end
end

#object_get_property_data_size_from_address(address) ⇒ Object



436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/gruesome/z/object_table.rb', line 436

def object_get_property_data_size_from_address(address)
  if @header.version <= 3
    prop_address = address - 1
  else
    first_byte = @memory.force_readb(address-1)

    if first_byte & 0b10000000 > 0
      prop_address = address - 2
    else
      prop_address = address - 1
    end
  end
  object_get_size_and_number(prop_address)[:size]
end

#object_get_property_word(index, property_number) ⇒ Object



451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/gruesome/z/object_table.rb', line 451

def object_get_property_word(index, property_number)
  property_data = object_get_property(index, property_number)
  if property_data == nil
    property_default(property_number)
  elsif property_data.size > 1
    if @endian == 'little'
      (property_data[1] << 8) | property_data[0]
    else
      (property_data[0] << 8) | property_data[1]
    end
  else
    property_data[0]
  end
end

#object_get_sibling(index) ⇒ Object



106
107
108
# File 'lib/gruesome/z/object_table.rb', line 106

def object_get_sibling(index)
  object_entry(index)[:sibling_id]
end

#object_get_size_and_number(prop_address) ⇒ Object



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/gruesome/z/object_table.rb', line 259

def object_get_size_and_number(prop_address)
  size = 0
  property_number = 0
  if @header.version <= 3
    size = @memory.force_readb(prop_address)
    prop_address += 1

    # when size is 0, this is the end of the list
    if size == 0
      return nil
    end

    # size is considered 32 times the data size minus one
    # property number is the first 5 bits
    property_number = size & 0b11111

    size = size >> 5
    size += 1
  else
    # in versions 4+, the size and property number are given
    # as two bytes.
    #
    # if bit 7 is set in the first byte:
    #    The second byte is read where first 6 bits 
    #    indicate size, bit 6 is ignored, bit 7 is set
    # if bit 7 is clear in the first byte:
    #    bit 6 is clear = size of 1
    #    bit 6 is set = size of 2
    #
    # property number is first 6 bits of first byte

    first_byte = @memory.force_readb(prop_address)
    prop_address += 1

    property_number = first_byte & 0b111111
    if first_byte & 0b10000000 > 0
      # bit 7 is set
      second_byte = @memory.force_readb(prop_address)
      prop_address += 1

      size = second_byte & 0b111111

      # a size of 0 is allowed, and will indicate a size of 64
      if size == 0
        size = 64
      end
    else
      # bit 7 is clear
      if first_byte & 0b1000000 > 0
        # bit 6 is set
        size = 2
      else
        # bit 6 is clear
        size = 1
      end
    end
  end

  { :size => size, :property_number => property_number, :property_data_address => prop_address }
end

#object_has_attribute?(index, attribute_number) ⇒ Boolean

Returns:

  • (Boolean)


223
224
225
226
227
228
229
230
231
# File 'lib/gruesome/z/object_table.rb', line 223

def object_has_attribute?(index, attribute_number)
  entry = object_entry(index)

  location = attribute_number_to_byte_bit_pair(attribute_number)
  attribute_byte = entry[:attributes][location[:byte]]
  mask = 1 << location[:bit]

  (attribute_byte & mask) > 0
end

#object_insert_object(index, new_child) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
# File 'lib/gruesome/z/object_table.rb', line 147

def object_insert_object(index, new_child)
  # remove child from old parent
  if object_get_parent(new_child) != 0
    object_remove_object new_child
  end

  # add to new parent
  object_set_sibling(new_child, object_get_child(index))
  object_set_child(index, new_child)
  object_set_parent(new_child, index)
end

#object_properties(index) ⇒ Object



348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/gruesome/z/object_table.rb', line 348

def object_properties(index)
  entry = object_entry(index)
  prop_address = entry[:properties_address]

  # get the length of the string in bytes
  text_len = @memory.force_readb(prop_address) * 2
  prop_address += 1

  prop_address += text_len

  properties = {}

  while true do
     = object_get_size_and_number(prop_address)

    if  == nil
      break
    end

    # regardless of version, we now have the property size and the number

    properties[[:property_number]] = {:size => [:size], :property_data_address => [:property_data_address]}
    prop_address = [:property_data_address] + [:size]
  end

  properties
end

#object_properties_array(index) ⇒ Object



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/gruesome/z/object_table.rb', line 320

def object_properties_array(index)
     entry = object_entry(index)
     prop_address = entry[:properties_address]

     # get the length of the string in bytes
     text_len = @memory.force_readb(prop_address) * 2
     prop_address += 1

     prop_address += text_len

     properties = []

     while true do
        = object_get_size_and_number(prop_address)

       if  == nil
         break
       end

       # regardless of version, we now have the property size and the number

       properties << {:property_number => [:property_number], :size => [:size], :property_data_address => [:property_data_address]}
       prop_address = [:property_data_address] + [:size]
     end

     properties
end

#object_remove_object(index) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/gruesome/z/object_table.rb', line 159

def object_remove_object(index)
  old_parent = object_get_parent(index)
  object_set_parent(index, 0)

  if old_parent != 0
    current_child = object_get_child(old_parent)
    if current_child == index
      # if we are the primary child of our parent, we removed
      # ourselves... so we have to set a new primary child
      # to be my sibling
      object_set_child(old_parent, object_get_sibling(index))
    else
      # if not, we are within some list of siblings
      # we need to remove ourself from this linked list
      # whose head is current_child
      
      last = current_child
      sibling = object_get_sibling(current_child)
      while sibling != 0
        if sibling == index
          # found me!
          # now, set the sibling of the previous one
          # to my sibling (removing me from the chain)
          object_set_sibling(last, object_get_sibling(index))

          # break out of the loop
          break
        end
        last = sibling
        sibling = object_get_sibling(sibling)
      end
    end
  end
end

#object_set_attribute(index, attribute_number) ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/gruesome/z/object_table.rb', line 233

def object_set_attribute(index, attribute_number)
  entry = object_entry(index)

  location = attribute_number_to_byte_bit_pair(attribute_number)
  attribute_byte = entry[:attributes][location[:byte]]
  mask = 1 << location[:bit]

  attribute_byte |= mask

  byte_addr = entry[:attributes_address] + location[:byte]
  @memory.force_writeb(byte_addr, attribute_byte)
end

#object_set_child(index, child_id) ⇒ Object



114
115
116
117
118
119
120
121
122
123
# File 'lib/gruesome/z/object_table.rb', line 114

def object_set_child(index, child_id)
  entry = object_entry(index)
  addr = entry[:attributes_address] + @attributes_size + @object_id_size*2

  if @object_id_size == 1
    @memory.force_writeb(addr, child_id)
  else
    @memory.force_writew(addr, child_id)
  end
end

#object_set_parent(index, parent_id) ⇒ Object



136
137
138
139
140
141
142
143
144
145
# File 'lib/gruesome/z/object_table.rb', line 136

def object_set_parent(index, parent_id)
  entry = object_entry(index)
  addr = entry[:attributes_address] + @attributes_size

  if @object_id_size == 1
    @memory.force_writeb(addr, parent_id)
  else
    @memory.force_writew(addr, parent_id)
  end
end

#object_set_property_word(index, property_number, value) ⇒ Object



466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# File 'lib/gruesome/z/object_table.rb', line 466

def object_set_property_word(index, property_number, value)
  properties = object_properties(index)

  property_info = properties[property_number]

  if property_info == nil
    raise "put_prop attempted to set a property value for a property that did not exist"
  else
    if property_info[:size] == 1
      value &= 0xff
      @memory.force_writeb(property_info[:property_data_address], value)
    elsif property_info[:size] == 2
      @memory.force_writew(property_info[:property_data_address], value)
    else
      raise "put_prop attempted to set a property whose data size is larger than a word"
    end
  end
end

#object_set_sibling(index, sibling_id) ⇒ Object



125
126
127
128
129
130
131
132
133
134
# File 'lib/gruesome/z/object_table.rb', line 125

def object_set_sibling(index, sibling_id)
  entry = object_entry(index)
  addr = entry[:attributes_address] + @attributes_size + @object_id_size

  if @object_id_size == 1
    @memory.force_writeb(addr, sibling_id)
  else
    @memory.force_writew(addr, sibling_id)
  end
end

#object_short_text(index) ⇒ Object



194
195
196
197
198
199
200
201
202
203
# File 'lib/gruesome/z/object_table.rb', line 194

def object_short_text(index)
  entry = object_entry(index)
  prop_address = entry[:properties_address]

  # text_len is the maximum number of bytes the string can have
  text_len = @memory.force_readb(prop_address) * 2
  chrs = @memory.force_readzstr(prop_address+1, text_len)[1]

  ZSCII.translate(0, @header.version, chrs, @abbreviation_table)
end

#property_default(index) ⇒ Object



55
56
57
58
59
60
61
62
63
# File 'lib/gruesome/z/object_table.rb', line 55

def property_default(index)
  index -= 1
  index %= @num_properties

  # The first thing in the table is the property defaults list
  # So, simply lookup the 16-bit word at the entry given by index
  prop_default_addr = @address + index*2
  @memory.force_readw(prop_default_addr)
end