Quake 1 (idtech2) model format (MDL version 6): Ruby parsing library

Application

Quake 1 (idtech2)

File extension

mdl

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.7

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.

Usage

Runtime 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

Code

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

Ruby source code to parse Quake 1 (idtech2) model format (MDL version 6)

quake_mdl.rb

# 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