This page hosts a formal specification of Gran Turismo File System (GTFS) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.
All parsing code for Ruby generated by Kaitai Struct depends on the Ruby runtime library. You have to install it before you can parse data.
The Ruby runtime library can be installed from RubyGems:
gem install kaitai-struct
Parse a local file and get structure in memory:
data = GranTurismoVol.from_file("path/to/local/file.vol")
Or parse structure from a string of bytes:
bytes = "\x00\x01\x02..."
data = GranTurismoVol.new(Kaitai::Struct::Stream.new(bytes))
After that, one can get various attributes from the structure by invoking getter methods like:
data.magic # => get magic
# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
require 'kaitai/struct/struct'
unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('0.9')
raise "Incompatible Kaitai Struct Ruby API: 0.9 or later is required, but you have #{Kaitai::Struct::VERSION}"
end
class GranTurismoVol < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@magic = @_io.read_bytes(8)
raise Kaitai::Struct::ValidationNotEqualError.new([71, 84, 70, 83, 0, 0, 0, 0].pack('C*'), magic, _io, "/seq/0") if not magic == [71, 84, 70, 83, 0, 0, 0, 0].pack('C*')
@num_files = @_io.read_u2le
@num_entries = @_io.read_u2le
@reserved = @_io.read_bytes(4)
raise Kaitai::Struct::ValidationNotEqualError.new([0, 0, 0, 0].pack('C*'), reserved, _io, "/seq/3") if not reserved == [0, 0, 0, 0].pack('C*')
@offsets = []
(num_files).times { |i|
@offsets << @_io.read_u4le
}
self
end
class FileInfo < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@timestamp = @_io.read_u4le
@offset_idx = @_io.read_u2le
@flags = @_io.read_u1
@name = (Kaitai::Struct::Stream::bytes_terminate(Kaitai::Struct::Stream::bytes_strip_right(@_io.read_bytes(25), 0), 0, false)).force_encoding("ASCII")
self
end
def size
return @size unless @size.nil?
@size = ((_root.offsets[(offset_idx + 1)] & 4294965248) - _root.offsets[offset_idx])
@size
end
def body
return @body unless @body.nil?
if !(is_dir)
_pos = @_io.pos
@_io.seek((_root.offsets[offset_idx] & 4294965248))
@body = @_io.read_bytes(size)
@_io.seek(_pos)
end
@body
end
def is_dir
return @is_dir unless @is_dir.nil?
@is_dir = (flags & 1) != 0
@is_dir
end
def is_last_entry
return @is_last_entry unless @is_last_entry.nil?
@is_last_entry = (flags & 128) != 0
@is_last_entry
end
attr_reader :timestamp
attr_reader :offset_idx
attr_reader :flags
attr_reader :name
end
def ofs_dir
return @ofs_dir unless @ofs_dir.nil?
@ofs_dir = offsets[1]
@ofs_dir
end
def files
return @files unless @files.nil?
_pos = @_io.pos
@_io.seek((ofs_dir & 4294965248))
@files = []
(_root.num_entries).times { |i|
@files << FileInfo.new(@_io, self, @_root)
}
@_io.seek(_pos)
@files
end
attr_reader :magic
attr_reader :num_files
attr_reader :num_entries
attr_reader :reserved
attr_reader :offsets
end