Task 355: .LGA File Format
Task 355: .LGA File Format
LHA File Format Specifications
The .LHA (also known as .LZH) file format is an archive format originating from the LHA compression utility created by Haruyasu Yoshizaki in 1988. It supports various compression methods based on LZSS and Huffman coding, and is commonly used for archiving files, particularly in older systems and Amiga communities. The format consists of a sequence of records, each representing a compressed file or directory entry, terminated by a zero-byte header length. Headers vary by level (0, 1, 2, 3), with differences in field placement and timestamp formats (DOS for levels 0-2, Unix for level 3). Compression methods are identified by 5-byte strings like "-lh5-". Extended headers (for levels 1+) allow additional metadata such as long filenames, directories, permissions, and large file support.
1. List of Properties Intrinsic to the File Format
Based on the specifications, the following are the key properties (fields) intrinsic to the .LHA file format. These are derived from the header structures and are common across entries in the archive. Properties can vary slightly by header level, but this list covers the core ones:
- Header Length: Unsigned 1-byte integer indicating the size of the header (excluding itself in some calculations, depending on level).
- Header Checksum: Unsigned 1-byte integer, sum of header bytes (mod 256) for verification.
- Compression Method: 5-byte ASCII string (e.g., "-lh5-") specifying the compression algorithm (e.g., lh0 for no compression, lh1-lh7 for varying window sizes).
- Compressed File Size: Unsigned 4-byte little-endian integer, size of the compressed data.
- Uncompressed File Size: Unsigned 4-byte little-endian integer, size of the original uncompressed data.
- File Timestamp: 4-byte value, either DOS format (levels 0-2: packed date/time) or Unix timestamp (level 3).
- File Attributes: Unsigned 1-byte integer, representing file permissions or attributes (platform-specific).
- LHA Level: Unsigned 1-byte integer (0, 1, 2, or 3), determining header structure.
- Filename: Variable-length string (ASCII), preceded by length byte in level 0/1; stored in extended headers for level 2/3.
- Uncompressed CRC: Unsigned 2-byte (CRC16) or 4-byte (CRC32 in some extensions) checksum of the uncompressed data.
- OS Type: Unsigned 1-byte integer (e.g., 'M' for MS-DOS, 'U' for Unix), indicating the originating operating system (present in levels 1+).
- Extended Header Size: Unsigned 2-byte little-endian integer, size of extended headers (levels 1+).
- Extended Headers: Variable chain of sub-headers (each with u2 size, u1 type, data). Common types include:
- 0x00: Common/header CRC.
- 0x01: Filename.
- 0x02: Directory path.
- 0x03: Comment.
- 0x40: Unix permissions.
- 0x41: Unix UID/GID.
- 0x42: 64-bit file size (for large files).
- 0x50: Unix timestamp.
- Others: Platform-specific (e.g., 0x3f for OS type).
These properties define the structure and metadata for each archived entry, enabling compression, multi-file support, and platform compatibility.
2. Two Direct Download Links for .LHA Files
- http://aminet.net/package/util/arc/WHDArchiveExtractor.lha (An Amiga WHDLoad archive extractor tool in .LHA format).
- http://aminet.net/package/dev/mui/mui38dev-storm.lha (MUI developer kit for Amiga in .LHA format).
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .LHA File Dump
This is an HTML snippet with embedded JavaScript that can be embedded in a Ghost blog post. It creates a drop zone for .LHA files, parses the file using DataView (assuming level 0 for simplicity; extend for other levels), and dumps the properties to the screen in a element.
4. Python Class for .LHA Handling
This Python class opens a .LHA file, decodes/reads the properties (supporting level 0; extend for others), prints them to console, and can write a simple level 0 archive.
import struct
import datetime
class LhaHandler:
def __init__(self, filename):
self.entries = []
with open(filename, 'rb') as f:
data = f.read()
offset = 0
while offset < len(data):
header_len = data[offset]
if header_len == 0:
break
offset += 1
checksum = data[offset]
offset += 1
method = data[offset:offset+5].decode('ascii')
offset += 5
comp_size, = struct.unpack('<I', data[offset:offset+4])
offset += 4
uncomp_size, = struct.unpack('<I', data[offset:offset+4])
offset += 4
timestamp_raw, = struct.unpack('<I', data[offset:offset+4])
# Parse DOS timestamp
time_part = timestamp_raw & 0xFFFF
date_part = (timestamp_raw >> 16) & 0xFFFF
year = 1980 + (date_part >> 9)
month = (date_part >> 5) & 0xF
day = date_part & 0x1F
hour = time_part >> 11
minute = (time_part >> 5) & 0x3F
sec = (time_part & 0x1F) * 2
timestamp = datetime.datetime(year, month, day, hour, minute, sec)
offset += 4
attr = data[offset]
offset += 1
level = data[offset]
offset += 1
if level == 0:
name_len = data[offset]
offset += 1
name = data[offset:offset+name_len].decode('ascii')
offset += name_len
crc, = struct.unpack('<H', data[offset:offset+2])
offset += 2
# Skip data
offset += comp_size
self.entries.append({
'Header Length': header_len,
'Header Checksum': checksum,
'Compression Method': method,
'Compressed Size': comp_size,
'Uncompressed Size': uncomp_size,
'File Timestamp': timestamp,
'Attributes': attr,
'Level': level,
'Filename': name,
'Uncompressed CRC': crc,
'OS Type': 'N/A',
'Extended Header Size': 'N/A',
'Extended Headers': 'N/A'
})
else:
raise ValueError("Only level 0 supported")
def print_properties(self):
for entry in self.entries:
print("Entry Properties:")
for key, value in entry.items():
print(f"{key}: {value}")
print("---")
def write(self, output_filename, entries_data):
# Simple write for level 0, assuming uncompressed (-lh0-), single entry
with open(output_filename, 'wb') as f:
for ed in entries_data:
name = ed['filename']
content = ed['content'] # bytes
uncomp_size = len(content)
comp_size = uncomp_size # No compression
method = '-lh0-'
attr = 0x20 # Default
level = 0
crc = 0 # Dummy, calculate properly if needed
timestamp_raw = 0 # Dummy
name_len = len(name)
header_size_inner = 20 + 1 + name_len # Method to end
header_len = header_size_inner + 1 # Including checksum
checksum = 0 # Dummy, calculate sum
header = struct.pack('<B', header_len) + struct.pack('<B', checksum) + method.encode('ascii') + \
struct.pack('<II', comp_size, uncomp_size) + struct.pack('<I', timestamp_raw) + \
struct.pack('<BB', attr, level) + struct.pack('<B', name_len) + name.encode('ascii') + \
struct.pack('<H', crc)
f.write(header)
f.write(content)
f.write(b'\x00') # End marker
# Example usage:
# handler = LhaHandler('example.lha')
# handler.print_properties()
# handler.write('new.lha', [{'filename': 'test.txt', 'content': b'Hello'}])
5. Java Class for .LHA Handling
This Java class opens a .LHA file, decodes/reads the properties (level 0 support), prints to console, and can write a simple level 0 archive.
import java.io.*;
import java.nio.*;
import java.nio.file.*;
import java.util.*;
public class LhaHandler {
private List<Map<String, Object>> entries = new ArrayList<>();
public LhaHandler(String filename) throws IOException {
byte[] data = Files.readAllBytes(Paths.get(filename));
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
int offset = 0;
while (offset < data.length) {
int headerLen = bb.get(offset) & 0xFF;
if (headerLen == 0) break;
offset++;
int checksum = bb.get(offset) & 0xFF;
offset++;
String method = new String(data, offset, 5, "ASCII");
offset += 5;
long compSize = bb.getInt(offset) & 0xFFFFFFFFL;
offset += 4;
long uncompSize = bb.getInt(offset) & 0xFFFFFFFFL;
offset += 4;
int timestampRaw = bb.getInt(offset);
// Parse DOS timestamp (simplified)
int datePart = timestampRaw >>> 16;
int timePart = timestampRaw & 0xFFFF;
int year = 1980 + (datePart >> 9);
int month = (datePart >> 5) & 0xF;
int day = datePart & 0x1F;
int hour = timePart >> 11;
int minute = (timePart >> 5) & 0x3F;
int sec = (timePart & 0x1F) * 2;
String timestamp = year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + sec;
offset += 4;
int attr = bb.get(offset) & 0xFF;
offset++;
int level = bb.get(offset) & 0xFF;
offset++;
if (level == 0) {
int nameLen = bb.get(offset) & 0xFF;
offset++;
String name = new String(data, offset, nameLen, "ASCII");
offset += nameLen;
int crc = bb.getShort(offset) & 0xFFFF;
offset += 2;
Map<String, Object> entry = new HashMap<>();
entry.put("Header Length", headerLen);
entry.put("Header Checksum", checksum);
entry.put("Compression Method", method);
entry.put("Compressed Size", compSize);
entry.put("Uncompressed Size", uncompSize);
entry.put("File Timestamp", timestamp);
entry.put("Attributes", attr);
entry.put("Level", level);
entry.put("Filename", name);
entry.put("Uncompressed CRC", crc);
entry.put("OS Type", "N/A");
entry.put("Extended Header Size", "N/A");
entry.put("Extended Headers", "N/A");
entries.add(entry);
offset += (int) compSize;
} else {
throw new IllegalArgumentException("Only level 0 supported");
}
}
}
public void printProperties() {
for (Map<String, Object> entry : entries) {
System.out.println("Entry Properties:");
entry.forEach((k, v) -> System.out.println(k + ": " + v));
System.out.println("---");
}
}
public void write(String outputFilename, List<Map<String, Object>> entriesData) throws IOException {
// Simple level 0 write, uncompressed
try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
for (Map<String, Object> ed : entriesData) {
String name = (String) ed.get("filename");
byte[] content = (byte[]) ed.get("content");
int uncompSize = content.length;
int compSize = uncompSize;
String method = "-lh0-";
int attr = 0x20;
int level = 0;
int crc = 0; // Dummy
int timestampRaw = 0; // Dummy
int nameLen = name.length();
int headerSizeInner = 20 + 1 + nameLen;
int headerLen = headerSizeInner + 1;
int checksum = 0; // Dummy
ByteBuffer bb = ByteBuffer.allocate(headerLen + 1).order(ByteOrder.LITTLE_ENDIAN);
bb.put((byte) headerLen);
bb.put((byte) checksum);
bb.put(method.getBytes("ASCII"));
bb.putInt(compSize);
bb.putInt(uncompSize);
bb.putInt(timestampRaw);
bb.put((byte) attr);
bb.put((byte) level);
bb.put((byte) nameLen);
bb.put(name.getBytes("ASCII"));
bb.putShort((short) crc);
fos.write(bb.array());
fos.write(content);
}
fos.write(0); // End
}
}
// Example usage:
// LhaHandler handler = new LhaHandler("example.lha");
// handler.printProperties();
// List<Map<String, Object>> data = new ArrayList<>();
// Map<String, Object> entry = new HashMap<>();
// entry.put("filename", "test.txt");
// entry.put("content", "Hello".getBytes());
// data.add(entry);
// handler.write("new.lha", data);
}
6. JavaScript Class for .LHA Handling
This JavaScript class opens a .LHA file (using Node.js fs), decodes/reads properties (level 0 support), prints to console, and can write a simple level 0 archive.
const fs = require('fs');
class LhaHandler {
constructor(filename) {
this.entries = [];
const data = fs.readFileSync(filename);
const view = new DataView(data.buffer);
let offset = 0;
while (offset < data.length) {
const headerLen = view.getUint8(offset);
if (headerLen === 0) break;
offset++;
const checksum = view.getUint8(offset);
offset++;
let method = '';
for (let i = 0; i < 5; i++) { method += String.fromCharCode(view.getUint8(offset + i)); }
offset += 5;
const compSize = view.getUint32(offset, true);
offset += 4;
const uncompSize = view.getUint32(offset, true);
offset += 4;
const timestampRaw = view.getUint32(offset, true);
const datePart = timestampRaw >>> 16;
const timePart = timestampRaw & 0xFFFF;
const year = 1980 + (datePart >> 9);
const month = (datePart >> 5) & 0xF;
const day = datePart & 0x1F;
const hour = timePart >> 11;
const minute = (timePart >> 5) & 0x3F;
const sec = (timePart & 0x1F) * 2;
const timestamp = `${year}-${month}-${day} ${hour}:${minute}:${sec}`;
offset += 4;
const attr = view.getUint8(offset);
offset++;
const level = view.getUint8(offset);
offset++;
if (level === 0) {
const nameLen = view.getUint8(offset);
offset++;
let name = '';
for (let i = 0; i < nameLen; i++) { name += String.fromCharCode(view.getUint8(offset + i)); }
offset += nameLen;
const crc = view.getUint16(offset, true);
offset += 2;
this.entries.push({
'Header Length': headerLen,
'Header Checksum': checksum,
'Compression Method': method,
'Compressed Size': compSize,
'Uncompressed Size': uncompSize,
'File Timestamp': timestamp,
'Attributes': attr,
'Level': level,
'Filename': name,
'Uncompressed CRC': crc,
'OS Type': 'N/A',
'Extended Header Size': 'N/A',
'Extended Headers': 'N/A'
});
offset += compSize;
} else {
throw new Error('Only level 0 supported');
}
}
}
printProperties() {
this.entries.forEach((entry) => {
console.log('Entry Properties:');
Object.entries(entry).forEach(([key, value]) => console.log(`${key}: ${value}`));
console.log('---');
});
}
write(outputFilename, entriesData) {
let buffer = Buffer.alloc(0);
entriesData.forEach((ed) => {
const name = ed.filename;
const content = Buffer.from(ed.content);
const uncompSize = content.length;
const compSize = uncompSize;
const method = '-lh0-';
const attr = 0x20;
const level = 0;
const crc = 0; // Dummy
const timestampRaw = 0; // Dummy
const nameLen = name.length;
const headerSizeInner = 20 + 1 + nameLen;
const headerLen = headerSizeInner + 1;
const checksum = 0; // Dummy
const header = Buffer.alloc(headerLen + 1);
let off = 0;
header[off++] = headerLen;
header[off++] = checksum;
Buffer.from(method, 'ascii').copy(header, off); off += 5;
header.writeUInt32LE(compSize, off); off += 4;
header.writeUInt32LE(uncompSize, off); off += 4;
header.writeUInt32LE(timestampRaw, off); off += 4;
header[off++] = attr;
header[off++] = level;
header[off++] = nameLen;
Buffer.from(name, 'ascii').copy(header, off); off += nameLen;
header.writeUInt16LE(crc, off); off += 2;
buffer = Buffer.concat([buffer, header, content]);
});
buffer = Buffer.concat([buffer, Buffer.from([0])]);
fs.writeFileSync(outputFilename, buffer);
}
}
// Example usage:
// const handler = new LhaHandler('example.lha');
// handler.printProperties();
// handler.write('new.lha', [{ filename: 'test.txt', content: 'Hello' }]);
7. C "Class" for .LHA Handling
Since C does not have classes, this is a struct with associated functions for opening, decoding/reading, printing properties to console, and writing a simple level 0 archive.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
uint8_t header_len;
uint8_t checksum;
char method[6];
uint32_t comp_size;
uint32_t uncomp_size;
uint32_t timestamp_raw;
uint8_t attr;
uint8_t level;
char* filename;
uint16_t crc;
// N/A for others in level 0
} LhaEntry;
typedef struct {
LhaEntry* entries;
int count;
} LhaHandler;
LhaHandler* lha_open(const char* filename) {
FILE* f = fopen(filename, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t* data = malloc(size);
fread(data, 1, size, f);
fclose(f);
LhaHandler* handler = malloc(sizeof(LhaHandler));
handler->entries = NULL;
handler->count = 0;
int offset = 0;
while (offset < size) {
uint8_t header_len = data[offset];
if (header_len == 0) break;
offset++;
uint8_t checksum = data[offset];
offset++;
char method[6];
memcpy(method, &data[offset], 5);
method[5] = '\0';
offset += 5;
uint32_t comp_size = *(uint32_t*)(&data[offset]);
offset += 4;
uint32_t uncomp_size = *(uint32_t*)(&data[offset]);
offset += 4;
uint32_t timestamp_raw = *(uint32_t*)(&data[offset]);
offset += 4;
uint8_t attr = data[offset];
offset++;
uint8_t level = data[offset];
offset++;
if (level == 0) {
uint8_t name_len = data[offset];
offset++;
char* name = malloc(name_len + 1);
memcpy(name, &data[offset], name_len);
name[name_len] = '\0';
offset += name_len;
uint16_t crc = *(uint16_t*)(&data[offset]);
offset += 2;
handler->entries = realloc(handler->entries, sizeof(LhaEntry) * (handler->count + 1));
LhaEntry* entry = &handler->entries[handler->count++];
entry->header_len = header_len;
entry->checksum = checksum;
strcpy(entry->method, method);
entry->comp_size = comp_size;
entry->uncomp_size = uncomp_size;
entry->timestamp_raw = timestamp_raw;
entry->attr = attr;
entry->level = level;
entry->filename = name;
entry->crc = crc;
offset += comp_size;
} else {
free(data);
// Cleanup entries
for (int i = 0; i < handler->count; i++) free(handler->entries[i].filename);
free(handler->entries);
free(handler);
return NULL; // Only level 0
}
}
free(data);
return handler;
}
void lha_print_properties(LhaHandler* handler) {
for (int i = 0; i < handler->count; i++) {
LhaEntry* e = &handler->entries[i];
printf("Entry Properties:\n");
printf("Header Length: %u\n", e->header_len);
printf("Header Checksum: %u\n", e->checksum);
printf("Compression Method: %s\n", e->method);
printf("Compressed Size: %u\n", e->comp_size);
printf("Uncompressed Size: %u\n", e->uncomp_size);
printf("File Timestamp (raw): %u\n", e->timestamp_raw);
printf("Attributes: %u\n", e->attr);
printf("Level: %u\n", e->level);
printf("Filename: %s\n", e->filename);
printf("Uncompressed CRC: %u\n", e->crc);
printf("OS Type: N/A\n");
printf("Extended Header Size: N/A\n");
printf("Extended Headers: N/A\n");
printf("---\n");
}
}
void lha_write(const char* output_filename, const char* filename_to_add, const uint8_t* content, size_t content_size) {
// Simple level 0 write, single entry, uncompressed
FILE* f = fopen(output_filename, "wb");
if (!f) return;
const char* name = filename_to_add;
uint32_t uncomp_size = (uint32_t)content_size;
uint32_t comp_size = uncomp_size;
char method[6] = "-lh0-";
uint8_t attr = 0x20;
uint8_t level = 0;
uint16_t crc = 0; // Dummy
uint32_t timestamp_raw = 0; // Dummy
uint8_t name_len = strlen(name);
uint8_t header_size_inner = 20 + 1 + name_len;
uint8_t header_len = header_size_inner + 1;
uint8_t checksum = 0; // Dummy
fwrite(&header_len, 1, 1, f);
fwrite(&checksum, 1, 1, f);
fwrite(method, 5, 1, f);
fwrite(&comp_size, 4, 1, f);
fwrite(&uncomp_size, 4, 1, f);
fwrite(×tamp_raw, 4, 1, f);
fwrite(&attr, 1, 1, f);
fwrite(&level, 1, 1, f);
fwrite(&name_len, 1, 1, f);
fwrite(name, name_len, 1, f);
fwrite(&crc, 2, 1, f);
fwrite(content, content_size, 1, f);
uint8_t end = 0;
fwrite(&end, 1, 1, f);
fclose(f);
}
void lha_close(LhaHandler* handler) {
for (int i = 0; i < handler->count; i++) free(handler->entries[i].filename);
free(handler->entries);
free(handler);
}
// Example usage:
// LhaHandler* handler = lha_open("example.lha");
// lha_print_properties(handler);
// lha_close(handler);
// uint8_t content[] = "Hello";
// lha_write("new.lha", "test.txt", (uint8_t*)content, 5);