class NWN::Gff::Reader
A class that parses binary GFF bytes into ruby-friendly data structures.
Attributes
root_struct[R]
Public Class Methods
read(io)
click to toggle source
Create a new Reader
with the given io
and immediately parse it.
# File lib/nwn/gff/reader.rb, line 10 def self.read io t = new(io) t.root_struct end
Private Instance Methods
read_all()
click to toggle source
# File lib/nwn/gff/reader.rb, line 23 def read_all initial_seek = @io.pos type, version, struct_offset, struct_count, field_offset, field_count, label_offset, label_count, field_data_offset, field_data_count, field_indices_offset, field_indices_count, list_indices_offset, list_indices_count = @io.e_read(160, "header").unpack("a4a4 VV VV VV VV VV VV") raise GffError, "struct offset at wrong place, not a gff?" unless struct_offset == 56 struct_len = struct_count * 12 field_len = field_count * 16 label_len = label_count * 16 @io.seek(initial_seek + struct_offset) @structs = @io.e_read(struct_len, "structs") @structs = @structs.unpack("V*") @io.seek(initial_seek + field_offset) @fields = @io.e_read(field_len, "fields") @fields = @fields.unpack("V*") @io.seek(initial_seek + label_offset) @labels = @io.e_read(label_len, "labels") @labels = @labels.unpack("A16" * label_count) @labels.map! {|l| l.force_encoding("ASCII") } @io.seek(initial_seek + field_data_offset) @field_data = @io.e_read(field_data_count, "field_data") @io.seek(initial_seek + field_indices_offset) @field_indices = @io.e_read(field_indices_count, "field_indices") @field_indices = @field_indices.unpack("V*") @io.seek(initial_seek + list_indices_offset) @list_indices = @io.e_read(list_indices_count, "list_indices") @list_indices = @list_indices.unpack("V*") @root_struct = read_struct 0, type.strip, version end
read_field(index, parent_of)
click to toggle source
Reads the field at index
and returns [label_name, Gff::Field]
# File lib/nwn/gff/reader.rb, line 107 def read_field index, parent_of field = {} field.extend(NWN::Gff::Field) index *= 3 type = @fields[index] label_index = @fields[index + 1] data_or_offset = @fields[index + 2] raise GffError, "Label index #{label_index} outside of label array" if label_index > @labels.size label = @labels[label_index] raise GffError, "Unknown field type #{type}." unless Types[type] type = Types[type] raise GffError, "Field '#{label}' (type: #{type} )data offset #{data_or_offset} outside of field data block (#{@field_data.size})" if ComplexTypes.index(type) && data_or_offset > @field_data.size field['type'] = type field['label'] = label field.parent = parent_of value = case type when :byte, :char data_or_offset & 0xff when :word data_or_offset & 0xffff when :short [(data_or_offset & 0xffff)].pack("S").unpack("s")[0] when :dword data_or_offset when :int [data_or_offset].pack("I").unpack("i")[0] when :float [data_or_offset].pack("V").unpack("f")[0] when :dword64 len = 8 v1, v2 = @field_data[data_or_offset, len].unpack("II") v1 * (2**32) + v2 when :int64 len = 8 @field_data[data_or_offset, len].unpack("q")[0] when :double len = 8 @field_data[data_or_offset, len].unpack("d")[0] when :cexostr len = @field_data[data_or_offset, 4].unpack("V")[0] str = @field_data[data_or_offset + 4, len].force_encoding(NWN.setting :in_encoding) str.valid_encoding? or raise "Invalid encoding bytes in cexostr: #{str.inspect}" str when :resref len = @field_data[data_or_offset, 1].unpack("C")[0] str = @field_data[data_or_offset + 1, len].force_encoding(NWN.setting :in_encoding) str.valid_encoding? or raise "Invalid encoding bytes in resref: #{str.inspect}" str when :cexolocstr exostr = {} total_size, str_ref, str_count = @field_data[data_or_offset, 12].unpack("VVV") all = @field_data[data_or_offset + 12, total_size] field.extend(NWN::Gff::Cexolocstr) field.str_ref = str_ref str_count.times { id, len = all.unpack("VV") str = all[8, len].unpack("a*")[0].force_encoding(NWN.setting :in_encoding) str.valid_encoding? or raise "Invalid encoding bytes in cexolocstr: #{str.inspect}" all = all[(8 + len)..-1] exostr[id] = str } len = total_size + 4 exostr when :void len = @field_data[data_or_offset, 4].unpack("V")[0] @field_data[data_or_offset + 4, len].unpack("a*")[0].force_encoding("BINARY") when :struct read_struct data_or_offset, nil, field.parent.data_version when :list list = [] raise GffError, "List index not divisable by 4" unless data_or_offset % 4 == 0 data_or_offset /= 4 raise GffError, "List index outside list indices" if data_or_offset > @list_indices.size count = @list_indices[data_or_offset] raise GffError, "List index overflow the list indices array" if data_or_offset + count > @list_indices.size data_or_offset += 1 for i in data_or_offset...(data_or_offset + count) list << read_struct(@list_indices[i], nil, field.parent.data_version) end list end raise GffError, "Field data overflows from the field data block area\ offset = #{data_or_offset + len}, len = #{@field_data.size}" if len && data_or_offset + len > @field_data.size [value].compact.flatten.each {|iv| iv.element = field if iv.respond_to?('element=') } field['value'] = value # We extend all fields and field_values with matching classes. field.extend_meta_classes field.validate [label, field] end
read_struct(index, file_type = nil, file_version = nil)
click to toggle source
This iterates through a struct and reads all fields into a hash, which it returns.
# File lib/nwn/gff/reader.rb, line 70 def read_struct index, file_type = nil, file_version = nil struct = {} struct.extend(NWN::Gff::Struct) type = @structs[index * 3] data_or_offset = @structs[index * 3 + 1] count = @structs[index * 3 + 2] raise GffError, "struct index #{index} outside of struct_array" if index * 3 + 3 > @structs.size + 1 file_type = file_type.force_encoding('ASCII') if file_type file_version = file_version.force_encoding('ASCII') if file_version struct.struct_id = type struct.data_type = file_type struct.data_version = file_version if count == 1 lbl, vl = * read_field(data_or_offset, struct) struct[lbl] = vl else if count > 0 raise GffError, "struct index not divisable by 4" if data_or_offset % 4 != 0 data_or_offset /= 4 for i in data_or_offset...(data_or_offset+count) lbl, vl = * read_field(@field_indices[i], struct) struct[lbl] = vl end end end struct end