class Ms::BinaryResources::Reader

Attributes

manager_length[R]
manager_magic[R]
manager_version[R]
resource_count[R]
resource_version[R]
type_count[R]

Public Class Methods

new(uri) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 13
def initialize(uri)
  @file = open(uri, 'rb')
  read_headers
rescue => e
  fail ArgumentError, "file does not appear to be a resources file (#{e})"
end

Public Instance Methods

[](name) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 40
def [](name)
  info = info_for_name(name)
  info ? read_value(info) : nil
end
close() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 45
def close
  @file.close
  @file = nil
end
each(&block) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 20
def each(&block)
  keys.each { |k| block.call(k, (self[k] rescue nil)) }
end
key?(name) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 24
def key?(name)
  info_for_name(name)
end
keys() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 28
def keys
  @keys ||= @resource_infos.map(&:name)
end
type_of(name) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 32
def type_of(name)
  info = info_for_name(name)
  if info
    type = RESOURCE_TYPES.find { |_, v| info.type_index == v }
    type && type.first
  end
end

Private Instance Methods

info_for_name(name) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 52
def info_for_name(name)
  @resource_infos.find { |info| info.name.encode(name.encoding) == name }
end
read(bytes) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 105
def read(bytes)
  @file.read(bytes)
end
read_7bit_encoded_int() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 137
def read_7bit_encoded_int
  ret = 0
  shift = 0

  begin
    b = read_byte
    ret |= (b & 0x7f) << shift
    shift += 7
  end while (b & 0x80) == 0x80

  ret
end
read_byte() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 109
def read_byte
  read(1).unpack('C').first
end
read_headers() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 56
def read_headers
  @manager_magic = read_uint32
  fail 'magic (%08X)' % manager_magic unless MAGIC_NUMBER == manager_magic

  @manager_version = read_uint32
  @manager_length = read_uint32

  if 1 == manager_version
    @reader_class = @file.read(@manager_length)
  else
    fail 'Unsupported manager version (%d)' % manager_version
  end

  @resource_version = read_uint32
  unless [1, 2].include?(resource_version)
    fail 'Unsupported resource version (%d)' % resource_version
  end

  @resource_count = read_uint32
  @type_count = read_uint32

  @type_names = @type_count.times.map { read_string }

  # next section is 8-byte aligned, there will be PADPADPAD characters to make it so
  pad_alignment = @file.pos & 0x07
  pad_count = pad_alignment > 0 ? 8 - pad_alignment : 0
  padding = @file.read(pad_count)

  @hashes = resource_count.times.map { read_uint32 }
  @positions = resource_count.times.map { read_uint32 }

  @data_section_offset = read_uint32
  @name_section_offset = @file.pos

  # cache the file location and type of all resources
  @resource_infos = @positions.map { |pos| read_resource_info(pos) }
end
read_int16() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 113
def read_int16
  read(2).unpack('s<').first
end
read_int32() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 121
def read_int32
  read(4).unpack('l<').first
end
read_int64() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 129
def read_int64
  read(8).unpack('q<').first
end
read_resource_info(name_position) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 94
def read_resource_info(name_position)
  seek_to_name(name_position)
  length = read_7bit_encoded_int
  name = read(length).encode('UTF-16LE', 'UTF-16LE')

  seek_to_data(read_uint32)
  type_index = read_7bit_encoded_int

  OpenStruct.new(name: name, value_position: @file.pos, type_index: type_index)
end
read_string() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 150
def read_string
  read(read_7bit_encoded_int)
end
read_uint16() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 117
def read_uint16
  read(2).unpack('S<').first
end
read_uint32() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 125
def read_uint32
  read(4).unpack('L<').first
end
read_uint64() click to toggle source
# File lib/ms/binary/resources/reader.rb, line 133
def read_uint64
  read(8).unpack('Q<').first
end
read_value(resource_info) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 154
def read_value(resource_info)
  @file.seek(resource_info.value_position)

  case resource_info.type_index
  when RESOURCE_TYPES[:string]; read_string
  when RESOURCE_TYPES[:int16];  read_int16
  when RESOURCE_TYPES[:uint16]; read_uint16
  when RESOURCE_TYPES[:int32];  read_int32
  when RESOURCE_TYPES[:uint32]; read_uint32
  when RESOURCE_TYPES[:int64];  read_int64
  when RESOURCE_TYPES[:uint64]; read_uint64
  else
    fail 'Unsupported type index: %d' % resource_info.type_index
  end
end
seek_to_data(position) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 174
def seek_to_data(position)
  @file.seek(@data_section_offset + position)
end
seek_to_name(position) click to toggle source
# File lib/ms/binary/resources/reader.rb, line 170
def seek_to_name(position)
  @file.seek(@name_section_offset + position)
end