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).
- 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)
- Two direct download links for files of format .ICE:
(Note: These are LHA files, which use the same format as .ICE on Amiga systems.)
- Ghost blog embedded HTML JavaScript for drag n drop .ICE file to dump properties:
.ICE File Properties Dumper
Drag and drop .ICE file here
- 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')
- 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");
}
}
- JavaScript class to open, decode, read, write, and print properties:
class IceFile {
constructor() {
this.records = [];
}
open(file) {
const reader = new FileReader