Allegro data file: Go parsing library

Allegro library for C (mostly used for game and multimedia apps programming) used its own container file format.

In general, it allows storage of arbitrary binary data blocks bundled together with some simple key-value style metadata ("properties") for every block. Allegro also pre-defines some simple formats for bitmaps, fonts, MIDI music, sound samples and palettes. Allegro library v4.0+ also support LZSS compression.

This spec applies to Allegro data files for library versions 2.2 up to 4.4.

Application

Allegro library (v2.2-v4.4)

File extension

dat

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of Allegro data file using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Go source code to parse Allegro data file

allegro_dat.go

// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.

import (
	"github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"
	"bytes"
)


/**
 * Allegro library for C (mostly used for game and multimedia apps
 * programming) used its own container file format.
 * 
 * In general, it allows storage of arbitrary binary data blocks
 * bundled together with some simple key-value style metadata
 * ("properties") for every block. Allegro also pre-defines some simple
 * formats for bitmaps, fonts, MIDI music, sound samples and
 * palettes. Allegro library v4.0+ also support LZSS compression.
 * 
 * This spec applies to Allegro data files for library versions 2.2 up
 * to 4.4.
 * @see <a href="https://liballeg.org/stabledocs/en/datafile.html">Source</a>
 */

type AllegroDat_PackEnum int
const (
	AllegroDat_PackEnum__Unpacked AllegroDat_PackEnum = 1936484398
)
var values_AllegroDat_PackEnum = map[AllegroDat_PackEnum]struct{}{1936484398: {}}
func (v AllegroDat_PackEnum) isDefined() bool {
	_, ok := values_AllegroDat_PackEnum[v]
	return ok
}
type AllegroDat struct {
	PackMagic AllegroDat_PackEnum
	DatMagic []byte
	NumObjects uint32
	Objects []*AllegroDat_DatObject
	_io *kaitai.Stream
	_root *AllegroDat
	_parent kaitai.Struct
}
func NewAllegroDat() *AllegroDat {
	return &AllegroDat{
	}
}

func (this AllegroDat) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat) Read(io *kaitai.Stream, parent kaitai.Struct, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp1, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.PackMagic = AllegroDat_PackEnum(tmp1)
	tmp2, err := this._io.ReadBytes(int(4))
	if err != nil {
		return err
	}
	tmp2 = tmp2
	this.DatMagic = tmp2
	if !(bytes.Equal(this.DatMagic, []uint8{65, 76, 76, 46})) {
		return kaitai.NewValidationNotEqualError([]uint8{65, 76, 76, 46}, this.DatMagic, this._io, "/seq/1")
	}
	tmp3, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.NumObjects = uint32(tmp3)
	for i := 0; i < int(this.NumObjects); i++ {
		_ = i
		tmp4 := NewAllegroDat_DatObject()
		err = tmp4.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Objects = append(this.Objects, tmp4)
	}
	return err
}
type AllegroDat_DatBitmap struct {
	BitsPerPixel int16
	Width uint16
	Height uint16
	Image []byte
	_io *kaitai.Stream
	_root *AllegroDat
	_parent *AllegroDat_DatObject
}
func NewAllegroDat_DatBitmap() *AllegroDat_DatBitmap {
	return &AllegroDat_DatBitmap{
	}
}

func (this AllegroDat_DatBitmap) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat_DatBitmap) Read(io *kaitai.Stream, parent *AllegroDat_DatObject, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp5, err := this._io.ReadS2be()
	if err != nil {
		return err
	}
	this.BitsPerPixel = int16(tmp5)
	tmp6, err := this._io.ReadU2be()
	if err != nil {
		return err
	}
	this.Width = uint16(tmp6)
	tmp7, err := this._io.ReadU2be()
	if err != nil {
		return err
	}
	this.Height = uint16(tmp7)
	tmp8, err := this._io.ReadBytesFull()
	if err != nil {
		return err
	}
	tmp8 = tmp8
	this.Image = tmp8
	return err
}
type AllegroDat_DatFont struct {
	FontSize int16
	Body kaitai.Struct
	_io *kaitai.Stream
	_root *AllegroDat
	_parent *AllegroDat_DatObject
}
func NewAllegroDat_DatFont() *AllegroDat_DatFont {
	return &AllegroDat_DatFont{
	}
}

func (this AllegroDat_DatFont) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat_DatFont) Read(io *kaitai.Stream, parent *AllegroDat_DatObject, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp9, err := this._io.ReadS2be()
	if err != nil {
		return err
	}
	this.FontSize = int16(tmp9)
	switch (this.FontSize) {
	case 0:
		tmp10 := NewAllegroDat_DatFont39()
		err = tmp10.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp10
	case 16:
		tmp11 := NewAllegroDat_DatFont16()
		err = tmp11.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp11
	case 8:
		tmp12 := NewAllegroDat_DatFont8()
		err = tmp12.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp12
	}
	return err
}

/**
 * Simple monochrome monospaced font, 95 characters, 8x16 px
 * characters.
 */
type AllegroDat_DatFont16 struct {
	Chars [][]byte
	_io *kaitai.Stream
	_root *AllegroDat
	_parent *AllegroDat_DatFont
}
func NewAllegroDat_DatFont16() *AllegroDat_DatFont16 {
	return &AllegroDat_DatFont16{
	}
}

func (this AllegroDat_DatFont16) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat_DatFont16) Read(io *kaitai.Stream, parent *AllegroDat_DatFont, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	for i := 0; i < int(95); i++ {
		_ = i
		tmp13, err := this._io.ReadBytes(int(16))
		if err != nil {
			return err
		}
		tmp13 = tmp13
		this.Chars = append(this.Chars, tmp13)
	}
	return err
}

/**
 * New bitmap font format introduced since Allegro 3.9: allows
 * flexible designation of character ranges, 8-bit colored
 * characters, etc.
 */
type AllegroDat_DatFont39 struct {
	NumRanges int16
	Ranges []*AllegroDat_DatFont39_Range
	_io *kaitai.Stream
	_root *AllegroDat
	_parent *AllegroDat_DatFont
}
func NewAllegroDat_DatFont39() *AllegroDat_DatFont39 {
	return &AllegroDat_DatFont39{
	}
}

func (this AllegroDat_DatFont39) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat_DatFont39) Read(io *kaitai.Stream, parent *AllegroDat_DatFont, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp14, err := this._io.ReadS2be()
	if err != nil {
		return err
	}
	this.NumRanges = int16(tmp14)
	for i := 0; i < int(this.NumRanges); i++ {
		_ = i
		tmp15 := NewAllegroDat_DatFont39_Range()
		err = tmp15.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Ranges = append(this.Ranges, tmp15)
	}
	return err
}
type AllegroDat_DatFont39_FontChar struct {
	Width uint16
	Height uint16
	Body []byte
	_io *kaitai.Stream
	_root *AllegroDat
	_parent *AllegroDat_DatFont39_Range
}
func NewAllegroDat_DatFont39_FontChar() *AllegroDat_DatFont39_FontChar {
	return &AllegroDat_DatFont39_FontChar{
	}
}

func (this AllegroDat_DatFont39_FontChar) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat_DatFont39_FontChar) Read(io *kaitai.Stream, parent *AllegroDat_DatFont39_Range, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp16, err := this._io.ReadU2be()
	if err != nil {
		return err
	}
	this.Width = uint16(tmp16)
	tmp17, err := this._io.ReadU2be()
	if err != nil {
		return err
	}
	this.Height = uint16(tmp17)
	tmp18, err := this._io.ReadBytes(int(this.Width * this.Height))
	if err != nil {
		return err
	}
	tmp18 = tmp18
	this.Body = tmp18
	return err
}
type AllegroDat_DatFont39_Range struct {
	Mono uint8
	StartChar uint32
	EndChar uint32
	Chars []*AllegroDat_DatFont39_FontChar
	_io *kaitai.Stream
	_root *AllegroDat
	_parent *AllegroDat_DatFont39
}
func NewAllegroDat_DatFont39_Range() *AllegroDat_DatFont39_Range {
	return &AllegroDat_DatFont39_Range{
	}
}

func (this AllegroDat_DatFont39_Range) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat_DatFont39_Range) Read(io *kaitai.Stream, parent *AllegroDat_DatFont39, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp19, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.Mono = tmp19
	tmp20, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.StartChar = uint32(tmp20)
	tmp21, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.EndChar = uint32(tmp21)
	for i := 0; i < int((this.EndChar - this.StartChar) + 1); i++ {
		_ = i
		tmp22 := NewAllegroDat_DatFont39_FontChar()
		err = tmp22.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Chars = append(this.Chars, tmp22)
	}
	return err
}

/**
 * First character in range
 */

/**
 * Last character in range (inclusive)
 */

/**
 * Simple monochrome monospaced font, 95 characters, 8x8 px
 * characters.
 */
type AllegroDat_DatFont8 struct {
	Chars [][]byte
	_io *kaitai.Stream
	_root *AllegroDat
	_parent *AllegroDat_DatFont
}
func NewAllegroDat_DatFont8() *AllegroDat_DatFont8 {
	return &AllegroDat_DatFont8{
	}
}

func (this AllegroDat_DatFont8) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat_DatFont8) Read(io *kaitai.Stream, parent *AllegroDat_DatFont, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	for i := 0; i < int(95); i++ {
		_ = i
		tmp23, err := this._io.ReadBytes(int(8))
		if err != nil {
			return err
		}
		tmp23 = tmp23
		this.Chars = append(this.Chars, tmp23)
	}
	return err
}
type AllegroDat_DatObject struct {
	Properties []*AllegroDat_Property
	LenCompressed int32
	LenUncompressed int32
	Body interface{}
	_io *kaitai.Stream
	_root *AllegroDat
	_parent *AllegroDat
	_raw_Body []byte
	_f_type bool
	type string
}
func NewAllegroDat_DatObject() *AllegroDat_DatObject {
	return &AllegroDat_DatObject{
	}
}

func (this AllegroDat_DatObject) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat_DatObject) Read(io *kaitai.Stream, parent *AllegroDat, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	for i := 1;; i++ {
		tmp24 := NewAllegroDat_Property()
		err = tmp24.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		_it := tmp24
		this.Properties = append(this.Properties, _it)
		tmp25, err := _it.IsValid()
		if err != nil {
			return err
		}
		if !(tmp25) {
			break
		}
	}
	tmp26, err := this._io.ReadS4be()
	if err != nil {
		return err
	}
	this.LenCompressed = int32(tmp26)
	tmp27, err := this._io.ReadS4be()
	if err != nil {
		return err
	}
	this.LenUncompressed = int32(tmp27)
	tmp28, err := this.Type()
	if err != nil {
		return err
	}
	switch (tmp28) {
	case "BMP ":
		tmp29, err := this._io.ReadBytes(int(this.LenCompressed))
		if err != nil {
			return err
		}
		tmp29 = tmp29
		this._raw_Body = tmp29
		_io__raw_Body := kaitai.NewStream(bytes.NewReader(this._raw_Body))
		tmp30 := NewAllegroDat_DatBitmap()
		err = tmp30.Read(_io__raw_Body, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp30
	case "FONT":
		tmp31, err := this._io.ReadBytes(int(this.LenCompressed))
		if err != nil {
			return err
		}
		tmp31 = tmp31
		this._raw_Body = tmp31
		_io__raw_Body := kaitai.NewStream(bytes.NewReader(this._raw_Body))
		tmp32 := NewAllegroDat_DatFont()
		err = tmp32.Read(_io__raw_Body, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp32
	case "RLE ":
		tmp33, err := this._io.ReadBytes(int(this.LenCompressed))
		if err != nil {
			return err
		}
		tmp33 = tmp33
		this._raw_Body = tmp33
		_io__raw_Body := kaitai.NewStream(bytes.NewReader(this._raw_Body))
		tmp34 := NewAllegroDat_DatRleSprite()
		err = tmp34.Read(_io__raw_Body, this, this._root)
		if err != nil {
			return err
		}
		this.Body = tmp34
	default:
		tmp35, err := this._io.ReadBytes(int(this.LenCompressed))
		if err != nil {
			return err
		}
		tmp35 = tmp35
		this._raw_Body = tmp35
	}
	return err
}
func (this *AllegroDat_DatObject) Type() (v string, err error) {
	if (this._f_type) {
		return this.type, nil
	}
	this._f_type = true
	tmp36 := this.Properties
	this.type = string(tmp36[len(tmp36) - 1].Magic)
	return this.type, nil
}
type AllegroDat_DatRleSprite struct {
	BitsPerPixel int16
	Width uint16
	Height uint16
	LenImage uint32
	Image []byte
	_io *kaitai.Stream
	_root *AllegroDat
	_parent *AllegroDat_DatObject
}
func NewAllegroDat_DatRleSprite() *AllegroDat_DatRleSprite {
	return &AllegroDat_DatRleSprite{
	}
}

func (this AllegroDat_DatRleSprite) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat_DatRleSprite) Read(io *kaitai.Stream, parent *AllegroDat_DatObject, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp37, err := this._io.ReadS2be()
	if err != nil {
		return err
	}
	this.BitsPerPixel = int16(tmp37)
	tmp38, err := this._io.ReadU2be()
	if err != nil {
		return err
	}
	this.Width = uint16(tmp38)
	tmp39, err := this._io.ReadU2be()
	if err != nil {
		return err
	}
	this.Height = uint16(tmp39)
	tmp40, err := this._io.ReadU4be()
	if err != nil {
		return err
	}
	this.LenImage = uint32(tmp40)
	tmp41, err := this._io.ReadBytesFull()
	if err != nil {
		return err
	}
	tmp41 = tmp41
	this.Image = tmp41
	return err
}
type AllegroDat_Property struct {
	Magic string
	Type string
	LenBody uint32
	Body string
	_io *kaitai.Stream
	_root *AllegroDat
	_parent *AllegroDat_DatObject
	_f_isValid bool
	isValid bool
}
func NewAllegroDat_Property() *AllegroDat_Property {
	return &AllegroDat_Property{
	}
}

func (this AllegroDat_Property) IO_() *kaitai.Stream {
	return this._io
}

func (this *AllegroDat_Property) Read(io *kaitai.Stream, parent *AllegroDat_DatObject, root *AllegroDat) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	tmp42, err := this._io.ReadBytes(int(4))
	if err != nil {
		return err
	}
	tmp42 = tmp42
	this.Magic = string(tmp42)
	tmp43, err := this.IsValid()
	if err != nil {
		return err
	}
	if (tmp43) {
		tmp44, err := this._io.ReadBytes(int(4))
		if err != nil {
			return err
		}
		tmp44 = tmp44
		this.Type = string(tmp44)
	}
	tmp45, err := this.IsValid()
	if err != nil {
		return err
	}
	if (tmp45) {
		tmp46, err := this._io.ReadU4be()
		if err != nil {
			return err
		}
		this.LenBody = uint32(tmp46)
	}
	tmp47, err := this.IsValid()
	if err != nil {
		return err
	}
	if (tmp47) {
		tmp48, err := this._io.ReadBytes(int(this.LenBody))
		if err != nil {
			return err
		}
		tmp48 = tmp48
		this.Body = string(tmp48)
	}
	return err
}
func (this *AllegroDat_Property) IsValid() (v bool, err error) {
	if (this._f_isValid) {
		return this.isValid, nil
	}
	this._f_isValid = true
	this.isValid = bool(this.Magic == "prop")
	return this.isValid, nil
}