This page hosts a formal specification of Quake 1 (idtech2) model format (MDL version 6) 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 = QuakeMdl.from_file("path/to/local/file.mdl")
Or parse structure from a string of bytes:
bytes = "\x00\x01\x02..."
data = QuakeMdl.new(Kaitai::Struct::Stream.new(bytes))
After that, one can get various attributes from the structure by invoking getter methods like:
data.header # => get header
# 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 QuakeMdl < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@header = MdlHeader.new(@_io, self, @_root)
@skins = Array.new(header.num_skins)
(header.num_skins).times { |i|
@skins[i] = MdlSkin.new(@_io, self, @_root)
}
@texture_coordinates = Array.new(header.num_verts)
(header.num_verts).times { |i|
@texture_coordinates[i] = MdlTexcoord.new(@_io, self, @_root)
}
@triangles = Array.new(header.num_tris)
(header.num_tris).times { |i|
@triangles[i] = MdlTriangle.new(@_io, self, @_root)
}
@frames = Array.new(header.num_frames)
(header.num_frames).times { |i|
@frames[i] = MdlFrame.new(@_io, self, @_root)
}
self
end
class MdlVertex < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@values = Array.new(3)
(3).times { |i|
@values[i] = @_io.read_u1
}
@normal_index = @_io.read_u1
self
end
attr_reader :values
attr_reader :normal_index
end
class MdlTexcoord < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@on_seam = @_io.read_s4le
@s = @_io.read_s4le
@t = @_io.read_s4le
self
end
attr_reader :on_seam
attr_reader :s
attr_reader :t
end
class MdlHeader < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@ident = @_io.read_bytes(4)
raise Kaitai::Struct::ValidationNotEqualError.new([73, 68, 80, 79].pack('C*'), ident, _io, "/types/mdl_header/seq/0") if not ident == [73, 68, 80, 79].pack('C*')
@version_must_be_6 = @_io.read_bytes(4)
raise Kaitai::Struct::ValidationNotEqualError.new([6, 0, 0, 0].pack('C*'), version_must_be_6, _io, "/types/mdl_header/seq/1") if not version_must_be_6 == [6, 0, 0, 0].pack('C*')
@scale = Vec3.new(@_io, self, @_root)
@origin = Vec3.new(@_io, self, @_root)
@radius = @_io.read_f4le
@eye_position = Vec3.new(@_io, self, @_root)
@num_skins = @_io.read_s4le
@skin_width = @_io.read_s4le
@skin_height = @_io.read_s4le
@num_verts = @_io.read_s4le
@num_tris = @_io.read_s4le
@num_frames = @_io.read_s4le
@synctype = @_io.read_s4le
@flags = @_io.read_s4le
@size = @_io.read_f4le
self
end
def version
return @version unless @version.nil?
@version = 6
@version
end
def skin_size
return @skin_size unless @skin_size.nil?
@skin_size = (skin_width * skin_height)
@skin_size
end
attr_reader :ident
attr_reader :version_must_be_6
attr_reader :scale
attr_reader :origin
attr_reader :radius
attr_reader :eye_position
attr_reader :num_skins
attr_reader :skin_width
attr_reader :skin_height
attr_reader :num_verts
attr_reader :num_tris
attr_reader :num_frames
attr_reader :synctype
attr_reader :flags
attr_reader :size
end
class MdlSkin < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@group = @_io.read_s4le
if group == 0
@single_texture_data = @_io.read_bytes(_root.header.skin_size)
end
if group != 0
@num_frames = @_io.read_u4le
end
if group != 0
@frame_times = Array.new(num_frames)
(num_frames).times { |i|
@frame_times[i] = @_io.read_f4le
}
end
if group != 0
@group_texture_data = Array.new(num_frames)
(num_frames).times { |i|
@group_texture_data[i] = @_io.read_bytes(_root.header.skin_size)
}
end
self
end
attr_reader :group
attr_reader :single_texture_data
attr_reader :num_frames
attr_reader :frame_times
attr_reader :group_texture_data
end
class MdlFrame < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@type = @_io.read_s4le
if type != 0
@min = MdlVertex.new(@_io, self, @_root)
end
if type != 0
@max = MdlVertex.new(@_io, self, @_root)
end
if type != 0
@time = Array.new(type)
(type).times { |i|
@time[i] = @_io.read_f4le
}
end
@frames = Array.new(num_simple_frames)
(num_simple_frames).times { |i|
@frames[i] = MdlSimpleFrame.new(@_io, self, @_root)
}
self
end
def num_simple_frames
return @num_simple_frames unless @num_simple_frames.nil?
@num_simple_frames = (type == 0 ? 1 : type)
@num_simple_frames
end
attr_reader :type
attr_reader :min
attr_reader :max
attr_reader :time
attr_reader :frames
end
class MdlSimpleFrame < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@bbox_min = MdlVertex.new(@_io, self, @_root)
@bbox_max = MdlVertex.new(@_io, self, @_root)
@name = (Kaitai::Struct::Stream::bytes_terminate(Kaitai::Struct::Stream::bytes_strip_right(@_io.read_bytes(16), 0), 0, false)).force_encoding("ASCII")
@vertices = Array.new(_root.header.num_verts)
(_root.header.num_verts).times { |i|
@vertices[i] = MdlVertex.new(@_io, self, @_root)
}
self
end
attr_reader :bbox_min
attr_reader :bbox_max
attr_reader :name
attr_reader :vertices
end
class MdlTriangle < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@faces_front = @_io.read_s4le
@vertices = Array.new(3)
(3).times { |i|
@vertices[i] = @_io.read_s4le
}
self
end
attr_reader :faces_front
attr_reader :vertices
end
class Vec3 < Kaitai::Struct::Struct
def initialize(_io, _parent = nil, _root = self)
super(_io, _parent, _root)
_read
end
def _read
@x = @_io.read_f4le
@y = @_io.read_f4le
@z = @_io.read_f4le
self
end
attr_reader :x
attr_reader :y
attr_reader :z
end
attr_reader :header
attr_reader :skins
attr_reader :texture_coordinates
attr_reader :triangles
attr_reader :frames
end