.lzh file format of LHA (AKA LHarc) by Yoshizaki Haruyasu: Go parsing library

LHA (LHarc, LZH) is a file format used by a popular freeware eponymous archiver, created in 1988 by Haruyasu Yoshizaki. Over the years, many ports and implementations were developed, sporting many extensions to original 1988 LZH.

File format is pretty simple and essentially consists of a stream of records.

Application

LHA (AKA LHarc) by Yoshizaki Haruyasu

File extension

lzh

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of .lzh file format of LHA (AKA LHarc) by Yoshizaki Haruyasu 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 .lzh file format of LHA (AKA LHarc) by Yoshizaki Haruyasu

lzh.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"
)


/**
 * LHA (LHarc, LZH) is a file format used by a popular freeware
 * eponymous archiver, created in 1988 by Haruyasu Yoshizaki. Over the
 * years, many ports and implementations were developed, sporting many
 * extensions to original 1988 LZH.
 * 
 * File format is pretty simple and essentially consists of a stream of
 * records.
 */
type Lzh struct {
	Entries []*Lzh_Record
	_io *kaitai.Stream
	_root *Lzh
	_parent kaitai.Struct
}
func NewLzh() *Lzh {
	return &Lzh{
	}
}

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

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

	for i := 0;; i++ {
		tmp1, err := this._io.EOF()
		if err != nil {
			return err
		}
		if tmp1 {
			break
		}
		tmp2 := NewLzh_Record()
		err = tmp2.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.Entries = append(this.Entries, tmp2)
	}
	return err
}
type Lzh_FileRecord struct {
	Header *Lzh_Header
	FileUncomprCrc16 uint16
	Body []byte
	_io *kaitai.Stream
	_root *Lzh
	_parent *Lzh_Record
	_raw_Header []byte
}
func NewLzh_FileRecord() *Lzh_FileRecord {
	return &Lzh_FileRecord{
	}
}

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

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

	tmp3, err := this._io.ReadBytes(int(this._parent.HeaderLen - 1))
	if err != nil {
		return err
	}
	tmp3 = tmp3
	this._raw_Header = tmp3
	_io__raw_Header := kaitai.NewStream(bytes.NewReader(this._raw_Header))
	tmp4 := NewLzh_Header()
	err = tmp4.Read(_io__raw_Header, this, this._root)
	if err != nil {
		return err
	}
	this.Header = tmp4
	if (this.Header.Header1.LhaLevel == 0) {
		tmp5, err := this._io.ReadU2le()
		if err != nil {
			return err
		}
		this.FileUncomprCrc16 = uint16(tmp5)
	}
	tmp6, err := this._io.ReadBytes(int(this.Header.Header1.FileSizeCompr))
	if err != nil {
		return err
	}
	tmp6 = tmp6
	this.Body = tmp6
	return err
}
type Lzh_Header struct {
	Header1 *Lzh_Header1
	FilenameLen uint8
	Filename string
	FileUncomprCrc16 uint16
	Os uint8
	ExtHeaderSize uint16
	_io *kaitai.Stream
	_root *Lzh
	_parent *Lzh_FileRecord
}
func NewLzh_Header() *Lzh_Header {
	return &Lzh_Header{
	}
}

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

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

	tmp7 := NewLzh_Header1()
	err = tmp7.Read(this._io, this, this._root)
	if err != nil {
		return err
	}
	this.Header1 = tmp7
	if (this.Header1.LhaLevel == 0) {
		tmp8, err := this._io.ReadU1()
		if err != nil {
			return err
		}
		this.FilenameLen = tmp8
	}
	if (this.Header1.LhaLevel == 0) {
		tmp9, err := this._io.ReadBytes(int(this.FilenameLen))
		if err != nil {
			return err
		}
		tmp9 = tmp9
		this.Filename = string(tmp9)
	}
	if (this.Header1.LhaLevel == 2) {
		tmp10, err := this._io.ReadU2le()
		if err != nil {
			return err
		}
		this.FileUncomprCrc16 = uint16(tmp10)
	}
	if (this.Header1.LhaLevel == 2) {
		tmp11, err := this._io.ReadU1()
		if err != nil {
			return err
		}
		this.Os = tmp11
	}
	if (this.Header1.LhaLevel == 2) {
		tmp12, err := this._io.ReadU2le()
		if err != nil {
			return err
		}
		this.ExtHeaderSize = uint16(tmp12)
	}
	return err
}

/**
 * Level-neutral header, same for all LHA levels. Subsequent fields order and meaning varies, based on LHA level specified in this header.
 */
type Lzh_Header1 struct {
	HeaderChecksum uint8
	MethodId string
	FileSizeCompr uint32
	FileSizeUncompr uint32
	FileTimestamp *DosDatetime
	Attr uint8
	LhaLevel uint8
	_io *kaitai.Stream
	_root *Lzh
	_parent *Lzh_Header
	_raw_FileTimestamp []byte
}
func NewLzh_Header1() *Lzh_Header1 {
	return &Lzh_Header1{
	}
}

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

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

	tmp13, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.HeaderChecksum = tmp13
	tmp14, err := this._io.ReadBytes(int(5))
	if err != nil {
		return err
	}
	tmp14 = tmp14
	this.MethodId = string(tmp14)
	tmp15, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.FileSizeCompr = uint32(tmp15)
	tmp16, err := this._io.ReadU4le()
	if err != nil {
		return err
	}
	this.FileSizeUncompr = uint32(tmp16)
	tmp17, err := this._io.ReadBytes(int(4))
	if err != nil {
		return err
	}
	tmp17 = tmp17
	this._raw_FileTimestamp = tmp17
	_io__raw_FileTimestamp := kaitai.NewStream(bytes.NewReader(this._raw_FileTimestamp))
	tmp18 := NewDosDatetime()
	err = tmp18.Read(_io__raw_FileTimestamp, nil, nil)
	if err != nil {
		return err
	}
	this.FileTimestamp = tmp18
	tmp19, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.Attr = tmp19
	tmp20, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.LhaLevel = tmp20
	return err
}

/**
 * Compressed file size
 */

/**
 * Uncompressed file size
 */

/**
 * Original file date/time
 */

/**
 * File or directory attribute
 */
type Lzh_Record struct {
	HeaderLen uint8
	FileRecord *Lzh_FileRecord
	_io *kaitai.Stream
	_root *Lzh
	_parent *Lzh
}
func NewLzh_Record() *Lzh_Record {
	return &Lzh_Record{
	}
}

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

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

	tmp21, err := this._io.ReadU1()
	if err != nil {
		return err
	}
	this.HeaderLen = tmp21
	if (this.HeaderLen > 0) {
		tmp22 := NewLzh_FileRecord()
		err = tmp22.Read(this._io, this, this._root)
		if err != nil {
			return err
		}
		this.FileRecord = tmp22
	}
	return err
}