# LZH format

Byte Order: Little-endian
There are some types of LZH header, level-0, level-1, and level-2.

    level-0                   level-1, level-2
    +------------+            +------------+
    | LZH header |            | LZH header |
    +------------+            +------------+
    | compressed |            | extension  |
    | data       |            | header     |
    +------------+            +------------+
    | LZH header |            | extension  |
    +------------+            | header     |
    | compressed |            +------------+
    | data       |            | ...        |
    +------------+            +------------+
    ...                       | compressed |
                              | data       |
                              +------------+
                              | LZH header |
                              +------------+
                              | extension  |
                              | header     |
                              +------------+
                              | extension  |
                              | header     |
                              +------------+
                              | ...        |
                              +------------+
                              | compressed |
                              | data       |
                              +------------+
                              ...

    level-0
    Offset   Length   Contents
      0      1 byte   Size of archived file header (h)
      1      1 byte   Header checksum
      2      5 bytes  Method ID
      7      4 bytes  Compressed size (n)
     11      4 bytes  Uncompressed size
     15      4 bytes  Original file date/time (Generic time stamp)
     19      1 byte   File attribute
     20      1 byte   Level (0x00)
     21      1 byte   Filename / path length in bytes (f)
     22     (f)bytes  Filename / path
     22+(f)  2 bytes  CRC-16 of original file
     24+(f) (n)bytes  Compressed data

    level-1
    Offset   Length   Contents
      0      1 byte   Size of archived file header (h)
      1      1 byte   Header checksum
      2      5 bytes  Method ID
      7      4 bytes  Compressed size (n)
     11      4 bytes  Uncompressed size
     15      4 bytes  Original file date/time (Generic time stamp)
     19      1 byte   0x20
     20      1 byte   Level (0x01)
     21      1 byte   Filename / path length in bytes (f)
     22     (f)bytes  Filename / path
     22+(f)  2 bytes  CRC-16 of original file
     24+(f)  1 byte   OS ID
     25+(f)  2 bytes  Next header size(x) (0 means no extension header)
    [ // Extension headers
             1 byte   Extension type
         (x)-3 bytes  Extension fields
             2 bytes  Next header size(x) (0 means no next extension header)
    ]*
            (n)bytes  Compressed data

    level-2
    Offset   Length   Contents
      0      2 byte   Total size of archived file header (h)
      2      5 bytes  Method ID
      7      4 bytes  Compressed size (n)
     11      4 bytes  Uncompressed size
     15      4 bytes  Original file time stamp(UNIX type, seconds since 1970)
     19      1 byte   Reserved
     20      1 byte   Level (0x02)
     21      2 bytes  CRC-16 of original file
     23      1 byte   OS ID
     24      2 bytes  Next header size(x) (0 means no extension header)
    [
             1 byte   Extension type
         (x)-3 bytes  Extension fields
             2 bytes  Next header size(x) (0 means no next extension header)
    ]*
            (n)bytes  Compressed data

### Extension header

    Common header:
             1 byte   Extension type (0x00)
             2 bytes  CRC-16 of header
            [1 bytes  Information] (Optional)
             2 bytes  Next header size

    File name header:
             1 byte   Extension type (0x01)
             ? bytes  File name
             2 bytes  Next header size

    Directory name header:
             1 byte   Extension type (0x02)
             ? bytes  Directory name
             2 bytes  Next header size

    Comment header:
             1 byte   Extension type (0x3f)
             ? bytes  Comments
             2 bytes  Next header size

    MS-DOS attribute header:
             if (OS ID == EXTEND_MSDOS ||
                 OS ID == EXTEND_HUMAN ||
                 OS ID == EXTEND_GENERIC)

             1 byte   Extension type (0x40)
             2 bytes  Attr
             2 bytes  Next header size

    UNIX file permission:
             if (OS ID == EXTEND_UNIX)

             1 byte   Extension type (0x50)
             2 bytes  Permission value
             2 bytes  Next header size

    UNIX file group/user ID:
             if (OS ID == EXTEND_UNIX)

             1 byte   Extension type (0x51)
             2 bytes  Group ID
             2 bytes  User ID
             2 bytes  Next header size

    UNIX file group name:
             1 byte   Extension type (0x52)
             ? bytes  Group name
             2 bytes  Next header size

    UNIX file user name:
             1 byte   Extension type (0x53)
             ? bytes  User name
             2 bytes  Next header size

    UNIX file last modified time:
             if (OS ID == EXTEND_UNIX)

             1 byte   Extension type (0x54)
             4 bytes  Last modified time in UNIX time
             2 bytes  Next header size

### Method ID

`"-lh0-"` | No compression
`"-lh1-"` | 4k sliding dictionary(max 60 bytes) + dynamic Huffman + fixed encoding of position
`"-lh2-"` | 8k sliding dictionary(max 256 bytes) + dynamic Huffman (Obsoleted)
`"-lh3-"` | 8k sliding dictionary(max 256 bytes) + static Huffman (Obsoleted)
`"-lh4-"` | 4k sliding dictionary(max 256 bytes) + static Huffman + improved encoding of position and trees
`"-lh5-"` | 8k sliding dictionary(max 256 bytes) + static Huffman + improved encoding of position and trees
`"-lh6-"` | 32k sliding dictionary(max 256 bytes) + static Huffman + improved encoding of position and trees
`"-lh7-"` | 64k sliding dictionary(max 256 bytes) + static Huffman + improved encoding of position and trees
`"-lzs-"` | 2k sliding dictionary(max 17 bytes)
`"-lz4-"` | No compression
`"-lz5-"` | 4k sliding dictionary(max 17 bytes)
`"-lhd-"` | Directory (no compressed data)

### Generic time stamp:

     31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
    |<-------- year ------->|<- month ->|<-- day -->|

     15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
    |<--- hour --->|<---- minute --->|<- second/2 ->|

    Offset   Length   Contents
     0       8 bits   year     years since 1980
     8       4 bits   month    [1..12]
    12       4 bits   day      [1..31]
    16       5 bits   hour     [0..23]
    21       6 bits   minite   [0..59]
    27       5 bits   second/2 [0..29]

NOTE: If you don't have `gettimeofday(2)', or your gettimeofday(2) returns bogus timezone information, try FTIME, MKTIME, TIMELOCAL or TZSET.

### OS ID

`'M'` | MS-DOS
`'2'` | OS/2
`'9'` | OS9
`'K'` | OS/68K
`'3'` | OS/386
`'H'` | HUMAN
`'U'` | UNIX
`'C'` | CP/M
`'F'` | FLEX
`'m'` | Mac
`'R'` | Runser

### Calc CRC16

    #define CHAR_BIT   8
    #define UCHAR_MAX  ((1<<(sizeof(unsigned char)*8))-1)
    #define CRCPOLY    0xA001      /* CRC-16 */

    static WORD crctable[UCHAR_MAX + 1];

    void make_crctable(void)
    {
      unsigned int    i, j, r;
      for (i = 0; i <= UCHAR_MAX; i++)
      {
        r = i;
        for (j = 0; j < CHAR_BIT; j++)
         if (r & 1)
            r = (r >> 1) ^ CRCPOLY;
         else
            r >>= 1;
        crctable[i] = r;
      }
    }

    WORD calc_header_crc(BYTE *p, WORD n)
    {
        crc = 0;
        while (n-- > 0)
          crc = crctable[(crc ^ (*p++)) & 0xFF] ^ (crc >> CHAR_BIT)
        return crc;
    }

    WORD calccrc(BYTE *p,WORD n)
    {
      reading_size += n;
      while (n-- > 0)
        crc = crctable[(crc ^ (*p++)) & 0xFF] ^ (crc >> CHAR_BIT)
      return crc;
    }

Original: http://www.goice.co.jp/member/mo/formats/lzh.html
LHA: http://gnuwin32.sourceforge.net/packages/lha.htm