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.
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.
// 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
}