Task 300: .ICE File Format

Task 300: .ICE File Format

File Format Specifications for the .ICE File Format

The .ICE file format is a compressed archive format associated with the ICEOWS software and also used on Amiga systems for storing compressed archive data in the LZH/Lharc format. It is essentially a variant of the LHA (LZH) file format, which was created by Haruyasu Yoshizaki in 1988. The LHA format is a stream of records, each containing a header and compressed data. The parsing is sequential, in little-endian byte order. The overall structure is a sequence of records until the end of the stream.

Structure Summary

Record:

  • Header Length (u1)
  • File Header (size = header_len - 1)
  • File Data (size = compressed_size)

File Header:

  • Header Checksum (u1)
  • Compression Method (5-byte ASCII string, e.g., "-lh5-")
  • Compressed Size (u4)
  • Uncompressed Size (u4)
  • DOS Time (u4, timestamp in DOS format)
  • Reserved1 (u1)
  • Level (u1)
  • Filename Length (u1)
  • Filename (ASCII string, size = filename_len)
  • CRC16 (u2)
  • OS ID (u1, if level == 0)
  • Extended Header Size (u2, if level == 2)
  • Additional fields depending on level (e.g., for level 2, OS and extended header).
  1. List of all the properties of this file format intrinsic to its file system:
  • Header Length
  • Header Checksum
  • Compression Method
  • Compressed Size
  • Uncompressed Size
  • File Timestamp (DOS datetime)
  • Attribute
  • LHA Level
  • Filename Length
  • Filename
  • CRC16
  • OS ID (conditional)
  • Extended Header Size (conditional)
  • File Data (compressed content)
  1. Two direct download links for files of format .ICE:

(Note: These are LHA files, which use the same format as .ICE on Amiga systems.)

  1. Ghost blog embedded HTML JavaScript for drag n drop .ICE file to dump properties:

.ICE File Properties Dumper

Drag and drop .ICE file here
  1. Python class to open, decode, read, write, and print properties:
import struct
import os

class IceFile:
    def __init__(self, filepath=None):
        self.filepath = filepath
        self.records = []

    def open(self, filepath):
        self.filepath = filepath
        self.decode()

    def decode(self):
        with open(self.filepath, 'rb') as f:
            data = f.read()
            offset = 0
            while offset < len(data):
                (header_len,) = struct.unpack('<B', data[offset:offset+1])
                offset += 1
                if header_len == 0:
                    break
                (checksum,) = struct.unpack('<B', data[offset:offset+1])
                offset += 1
                method = data[offset:offset+5].decode('ascii')
                offset += 5
                (compressed_size,) = struct.unpack('<I', data[offset:offset+4])
                offset += 4
                (uncompressed_size,) = struct.unpack('<I', data[offset:offset+4])
                offset += 4
                (dos_time,) = struct.unpack('<I', data[offset:offset+4])
                offset += 4
                (attr,) = struct.unpack('<B', data[offset:offset+1])
                offset += 1
                (level,) = struct.unpack('<B', data[offset:offset+1])
                offset += 1
                (filename_len,) = struct.unpack('<B', data[offset:offset+1])
                offset += 1
                filename = data[offset:offset+filename_len].decode('ascii')
                offset += filename_len
                (crc16,) = struct.unpack('<H', data[offset:offset+2])
                offset += 2
                os_id = 0
                if level == 0:
                    (os_id,) = struct.unpack('<B', data[offset:offset+1])
                    offset += 1
                file_data = data[offset:offset+compressed_size]
                offset += compressed_size

                self.records.append({
                    'Header Length': header_len,
                    'Header Checksum': checksum,
                    'Compression Method': method,
                    'Compressed Size': compressed_size,
                    'Uncompressed Size': uncompressed_size,
                    'File Timestamp': dos_time,
                    'Attribute': attr,
                    'LHA Level': level,
                    'Filename Length': filename_len,
                    'Filename': filename,
                    'CRC16': crc16,
                    'OS ID': os_id,
                    'File Data': file_data  # For write
                })

    def print_properties(self):
        for idx, record in enumerate(self.records):
            print(f"Record {idx}:")
            for key, value in record.items():
                if key != 'File Data':
                    print(f"  {key}: {value}")

    def write(self, filepath):
        with open(filepath, 'wb') as f:
            for record in self.records:
                f.write(struct.pack('<B', record['Header Length']))
                f.write(struct.pack('<B', record['Header Checksum']))
                f.write(record['Compression Method'].encode('ascii'))
                f.write(struct.pack('<I', record['Compressed Size']))
                f.write(struct.pack('<I', record['Uncompressed Size']))
                f.write(struct.pack('<I', record['File Timestamp']))
                f.write(struct.pack('<B', record['Attribute']))
                f.write(struct.pack('<B', record['LHA Level']))
                f.write(struct.pack('<B', record['Filename Length']))
                f.write(record['Filename'].encode('ascii'))
                f.write(struct.pack('<H', record['CRC16']))
                if record['LHA Level'] == 0:
                    f.write(struct.pack('<B', record['OS ID']))
                f.write(record['File Data'])
            f.write(b'\x00')  # End of stream

# Example usage
if __name__ == '__main__':
    ice = IceFile()
    ice.open('sample.ice')
    ice.print_properties()
    ice.write('output.ice')
  1. Java class to open, decode, read, write, and print properties:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class IceFile {
    private String filepath;
    private ArrayList<Map<String, Object>> records = new ArrayList<>();

    public void open(String filepath) throws IOException {
        this.filepath = filepath;
        decode();
    }

    private void decode() throws IOException {
        FileInputStream fis = new FileInputStream(filepath);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ( (len = fis.read(buffer)) > -1 ) {
            baos.write(buffer, 0, len);
        }
        fis.close();
        byte[] data = baos.toByteArray();
        ByteBuffer bb = ByteBuffer.wrap(data);
        bb.order(ByteOrder.LITTLE_ENDIAN);
        int offset = 0;
        while (offset < data.length) {
            byte headerLen = bb.get(offset);
            offset += 1;
            if (headerLen == 0) break;
            byte checksum = bb.get(offset);
            offset += 1;
            String method = new String(data, offset, 5, "ASCII");
            offset += 5;
            int compressedSize = bb.getInt(offset);
            offset += 4;
            int uncompressedSize = bb.getInt(offset);
            offset += 4;
            int dosTime = bb.getInt(offset);
            offset += 4;
            byte attr = bb.get(offset);
            offset += 1;
            byte level = bb.get(offset);
            offset += 1;
            byte filenameLen = bb.get(offset);
            offset += 1;
            String filename = new String(data, offset, filenameLen, "ASCII");
            offset += filenameLen;
            short crc16 = bb.getShort(offset);
            offset += 2;
            byte osId = 0;
            if (level == 0) {
                osId = bb.get(offset);
                offset += 1;
            }
            byte[] fileData = new byte[compressedSize];
            System.arraycopy(data, offset, fileData, 0, compressedSize);
            offset += compressedSize;

            Map<String, Object> record = new HashMap<>();
            record.put("Header Length", headerLen);
            record.put("Header Checksum", checksum);
            record.put("Compression Method", method);
            record.put("Compressed Size", compressedSize);
            record.put("Uncompressed Size", uncompressedSize);
            record.put("File Timestamp", dosTime);
            record.put("Attribute", attr);
            record.put("LHA Level", level);
            record.put("Filename Length", filenameLen);
            record.put("Filename", filename);
            record.put("CRC16", crc16);
            record.put("OS ID", osId);
            record.put("File Data", fileData);
            records.add(record);
        }
    }

    public void printProperties() {
        for (int i = 0; i < records.size(); i++) {
            System.out.println("Record " + i + ":");
            Map<String, Object> record = records.get(i);
            for (Map.Entry<String, Object> entry : record.entrySet()) {
                if (!entry.getKey().equals("File Data")) {
                    System.out.println("  " + entry.getKey() + ": " + entry.getValue());
                }
            }
        }
    }

    public void write(String filepath) throws IOException {
        FileOutputStream fos = new FileOutputStream(filepath);
        for (Map<String, Object> record : records) {
            fos.write((byte) record.get("Header Length"));
            fos.write((byte) record.get("Header Checksum"));
            fos.write(((String) record.get("Compression Method")).getBytes("ASCII"));
            ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
            bb.putInt((int) record.get("Compressed Size"));
            fos.write(bb.array());
            bb.clear();
            bb.putInt((int) record.get("Uncompressed Size"));
            fos.write(bb.array());
            bb.clear();
            bb.putInt((int) record.get("File Timestamp"));
            fos.write(bb.array());
            fos.write((byte) record.get("Attribute"));
            fos.write((byte) record.get("LHA Level"));
            fos.write((byte) record.get("Filename Length"));
            fos.write(((String) record.get("Filename")).getBytes("ASCII"));
            bb.clear();
            bb.putShort((short) record.get("CRC16"));
            fos.write(bb.array(), 0, 2);
            if ((byte) record.get("LHA Level") == 0) {
                fos.write((byte) record.get("OS ID"));
            }
            fos.write((byte[]) record.get("File Data"));
        }
        fos.write(0); // End of stream
        fos.close();
    }

    public static void main(String[] args) throws IOException {
        IceFile ice = new IceFile();
        ice.open("sample.ice");
        ice.printProperties();
        ice.write("output.ice");
    }
}
  1. JavaScript class to open, decode, read, write, and print properties:
class IceFile {
  constructor() {
    this.records = [];
  }

  open(file) {
    const reader = new FileReader