Task 279: .HA File Format
Task 279: .HA File Format
File Format Specifications for .HA
The .HA file format is a compressed archive format developed by Harri Hirvola in 1993 for MS-DOS and later ported to Unix/Linux. It uses little-endian byte order for all multi-byte values and primarily employs the HSC compression algorithm, though it supports multiple compression types. The format consists of a main archive header followed by per-file headers and the corresponding compressed file data.
- List of All Properties Intrinsic to Its File System
The .HA format functions as a simple archive "file system," supporting multiple files with metadata for organization, integrity, and retrieval. The intrinsic properties are:
- Archive Magic Number: 2 bytes (ASCII "HA") – Identifies the file as a .HA archive.
- Number of Files: 2 bytes (uint16, little-endian) – Total count of files stored in the archive.
- Per-File Properties (repeated for each file):
- Version and Compression Type: 1 byte – Upper 4 bits: version number; Lower 4 bits: compression type (0 = CPY/copy/uncompressed, 1 = ASC, 2 = HSC/default compression, 14/0xE = DIR/directory, 15/0xF = Special).
- Compressed Length: 4 bytes (uint32, little-endian) – Size of the compressed data block for the file.
- Uncompressed Length: 4 bytes (uint32, little-endian) – Original size of the file before compression.
- CRC-32 Checksum: 4 bytes (uint32, little-endian) – Integrity checksum for the uncompressed file data.
- Timestamp: 4 bytes (uint32, little-endian) – Unix timestamp (seconds since epoch) for the file's last modification time.
- Pathname: Null-terminated ASCII string – Full path to the file (may be empty).
- Filename: Null-terminated ASCII string – Base name of the file.
- Machine-Specific Information (MSI) Length: 1 byte (uint8) – Length of the following MSI data.
- Machine-Specific Information (MSI): Variable length (as per length byte) – First byte is the information type; remaining bytes are type-specific data (e.g., permissions, attributes).
These properties enable basic file system-like features: hierarchical storage via paths, timestamping for versioning, integrity via CRC, and platform-specific extensions via MSI.
- Two Direct Download Links for Files of Format .HA
After extensive searching across web archives, GitHub repositories, and software preservation sites (including Archive.org, Textfiles.com, and SAC.sk), no publicly available direct download links for sample .HA archive files were found. The format is extremely obscure and rarely used today, with most resources focusing on the HA archiver tool itself rather than example archives. To obtain .HA files, download the HA archiver and create samples:
- HA 0.999 beta (source and executable in ZIP): https://www.sac.sk/Download.php?dir=&file=ha0999.zip
- HA 0.98 (DOS binary in ZIP): https://cd.textfiles.com/swextrav4/swextrav4-3/arch/ha098.zip
- Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .HA Property Dump
Embed the following complete HTML code in a Ghost blog post (use the HTML card). It creates a drag-and-drop zone that reads the .HA file as a binary buffer, parses the headers, and dumps all properties to a <pre>
element on screen. It supports reading only (no decompression, as HSC is proprietary/complex); assumes little-endian and null-terminated strings.
Drag and drop a .HA file here to view its properties.
- Python Class for .HA Handling
This class reads a .HA file, parses and prints all properties to console. It supports writing a simple archive using CPY (no compression, CRC calculated via binascii). Decompression is not implemented for HSC/ASC (proprietary); for CPY, data is copied as-is.
import struct
import binascii
import os
from datetime import datetime
class HAArchive:
COMP_TYPES = {0: 'CPY', 1: 'ASC', 2: 'HSC', 14: 'DIR', 15: 'Special'}
def __init__(self, filename=None):
self.filename = filename
self.magic = None
self.num_files = None
self.files = [] # List of dicts with properties
def read(self):
with open(self.filename, 'rb') as f:
data = f.read()
offset = 0
# Main header
self.magic = struct.unpack_from('<2s', data, offset)[0].decode('ascii')
offset += 2
if self.magic != 'HA':
raise ValueError('Invalid .HA magic')
self.num_files = struct.unpack_from('<H', data, offset)[0]
offset += 2
print(f'Magic: {self.magic}')
print(f'Number of Files: {self.num_files}\n')
for i in range(self.num_files):
file_info = {}
ver_comp = struct.unpack_from('<B', data, offset)[0]
file_info['version'] = (ver_comp >> 4) & 0xF
file_info['comp_type'] = ver_comp & 0xF
offset += 1
print(f'File {i+1}:')
print(f' Version: {file_info["version"]}')
print(f' Compression Type: {self.COMP_TYPES.get(file_info["comp_type"], file_info["comp_type"])} ({file_info["comp_type"]})')
file_info['comp_len'] = struct.unpack_from('<I', data, offset)[0]
offset += 4
print(f' Compressed Length: {file_info["comp_len"]}')
file_info['uncomp_len'] = struct.unpack_from('<I', data, offset)[0]
offset += 4
print(f' Uncompressed Length: {file_info["uncomp_len"]}')
file_info['crc32'] = struct.unpack_from('<I', data, offset)[0]
offset += 4
print(f' CRC-32: 0x{file_info["crc32"]:08X}')
file_info['timestamp'] = struct.unpack_from('<I', data, offset)[0]
offset += 4
dt = datetime.fromtimestamp(file_info['timestamp'])
print(f' Timestamp: {dt} (Unix: {file_info["timestamp"]})')
# Null-terminated strings
path_end = data.find(b'\x00', offset)
file_info['path'] = data[offset:path_end].decode('ascii')
offset = path_end + 1
print(f' Pathname: {file_info["path"] or "(empty)"}')
name_end = data.find(b'\x00', offset)
file_info['filename'] = data[offset:name_end].decode('ascii')
offset = name_end + 1
print(f' Filename: {file_info["filename"] or "(empty)"}')
msi_len = struct.unpack_from('<B', data, offset)[0]
offset += 1
file_info['msi_len'] = msi_len
print(f' MSI Length: {msi_len}')
if msi_len > 0:
msi_type = struct.unpack_from('<B', data, offset)[0]
offset += 1
file_info['msi_type'] = msi_type
file_info['msi_data'] = data[offset:offset + msi_len - 1].decode('ascii', errors='ignore')
offset += msi_len - 1
print(f' MSI Type: {msi_type}')
print(f' MSI Data: {file_info["msi_data"] or "(none)"}')
print()
self.files.append(file_info)
def write(self, output_filename, files_list):
# Simple writer: CPY only, no MSI
with open(output_filename, 'wb') as f:
f.write(b'HA')
f.write(struct.pack('<H', len(files_list)))
for file_path in files_list:
with open(file_path, 'rb') as src:
content = src.read()
uncomp_len = len(content)
comp_len = uncomp_len # CPY
crc32 = binascii.crc32(content) & 0xFFFFFFFF
timestamp = int(os.path.getmtime(file_path))
dirname, basename = os.path.split(file_path)
# Version 0, CPY (0)
f.write(struct.pack('<B', 0x00))
f.write(struct.pack('<II', comp_len, uncomp_len))
f.write(struct.pack('<I', crc32))
f.write(struct.pack('<I', timestamp))
f.write(dirname.encode('ascii') + b'\x00')
f.write(basename.encode('ascii') + b'\x00')
f.write(struct.pack('<B', 0)) # MSI len 0
f.write(content) # Data
print(f'Wrote simple CPY .HA archive to {output_filename}')
# Example usage:
# archive = HAArchive('example.ha')
# archive.read()
# archive.write('new.ha', ['file1.txt', 'file2.txt'])
- Java Class for .HA Handling
This class reads a .HA file using RandomAccessFile
, parses and prints properties to console (System.out). Write supports simple CPY (no compression, CRC via custom impl). Decompression stubbed for non-CPY.
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.*;
import java.util.zip.CRC32;
public class HAArchive {
private String filename;
private String magic;
private int numFiles;
private List<Map<String, Object>> files = new ArrayList<>();
private static final Map<Integer, String> COMP_TYPES = Map.of(
0, "CPY", 1, "ASC", 2, "HSC", 14, "DIR", 15, "Special"
);
public HAArchive(String filename) {
this.filename = filename;
}
public void read() throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(filename, "r")) {
byte[] header = new byte[4];
raf.read(header, 0, 2);
magic = new String(header, 0, 2, "ASCII");
if (!"HA".equals(magic)) {
throw new IOException("Invalid .HA magic");
}
raf.read(header, 2, 2);
numFiles = ByteBuffer.wrap(header, 2, 2).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF;
System.out.println("Magic: " + magic);
System.out.println("Number of Files: " + numFiles + "\n");
for (int i = 0; i < numFiles; i++) {
Map<String, Object> fileInfo = new HashMap<>();
int verComp = raf.readUnsignedByte();
int version = (verComp >> 4) & 0xF;
int compType = verComp & 0xF;
fileInfo.put("version", version);
fileInfo.put("comp_type", compType);
System.out.println("File " + (i + 1) + ":");
System.out.println(" Version: " + version);
System.out.println(" Compression Type: " + COMP_TYPES.getOrDefault(compType, String.valueOf(compType)) + " (" + compType + ")");
fileInfo.put("comp_len", raf.readInt()); // LE uint32
System.out.println(" Compressed Length: " + fileInfo.get("comp_len"));
fileInfo.put("uncomp_len", raf.readInt());
System.out.println(" Uncompressed Length: " + fileInfo.get("uncomp_len"));
fileInfo.put("crc32", raf.readInt() & 0xFFFFFFFFL);
System.out.println(" CRC-32: 0x" + String.format("%08X", fileInfo.get("crc32")));
fileInfo.put("timestamp", raf.readInt() & 0xFFFFFFFFL);
long ts = (Long) fileInfo.get("timestamp");
System.out.println(" Timestamp: " + new Date(ts * 1000) + " (Unix: " + ts + ")");
// Null-terminated strings
StringBuilder path = new StringBuilder();
int ch;
while ((ch = raf.read()) != 0 && ch != -1) {
path.append((char) ch);
}
fileInfo.put("path", path.toString());
System.out.println(" Pathname: " + (path.length() == 0 ? "(empty)" : path));
StringBuilder fname = new StringBuilder();
while ((ch = raf.read()) != 0 && ch != -1) {
fname.append((char) ch);
}
fileInfo.put("filename", fname.toString());
System.out.println(" Filename: " + (fname.length() == 0 ? "(empty)" : fname));
int msiLen = raf.readUnsignedByte();
fileInfo.put("msi_len", msiLen);
System.out.println(" MSI Length: " + msiLen);
if (msiLen > 0) {
int msiType = raf.readUnsignedByte();
fileInfo.put("msi_type", msiType);
byte[] msiDataBytes = new byte[msiLen - 1];
raf.read(msiDataBytes);
String msiData = new String(msiDataBytes, "ASCII");
fileInfo.put("msi_data", msiData);
System.out.println(" MSI Type: " + msiType);
System.out.println(" MSI Data: " + (msiData.isEmpty() ? "(none)" : msiData));
}
System.out.println();
files.add(fileInfo);
}
}
}
public void write(String outputFilename, List<String> inputFiles) throws IOException {
CRC32 crc = new CRC32();
try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
fos.write("HA".getBytes("ASCII"));
fos.write(ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort((short) inputFiles.size()).array());
for (String filePath : inputFiles) {
byte[] content = Files.readAllBytes(Path.of(filePath));
int uncompLen = content.length;
int compLen = uncompLen; // CPY
crc.reset();
crc.update(content);
long crcVal = crc.getValue();
BasicFileAttributes attrs = Files.readAttributes(Path.of(filePath), BasicFileAttributes.class);
FileTime ft = attrs.lastModifiedTime();
long timestamp = ft.toInstant().getEpochSecond();
String dirName = Path.of(filePath).getParent().toString();
String baseName = Path.of(filePath).getFileName().toString();
// Version 0, CPY (0)
fos.write(0x00);
fos.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(compLen).array());
fos.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(uncompLen).array());
fos.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt((int) crcVal).array());
fos.write(ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt((int) timestamp).array());
fos.write((dirName + "\0").getBytes("ASCII"));
fos.write((baseName + "\0").getBytes("ASCII"));
fos.write(0); // MSI len 0
fos.write(content);
}
}
System.out.println("Wrote simple CPY .HA archive to " + outputFilename);
}
// Example usage:
// HAArchive archive = new HAArchive("example.ha");
// archive.read();
// archive.write("new.ha", Arrays.asList("file1.txt", "file2.txt"));
}
- JavaScript Class for .HA Handling (Node.js)
This Node.js class uses fs
to read/write, parses headers, and prints to console. Write supports CPY only (CRC via custom poly). Run with node script.js
.
const fs = require('fs');
class HAArchive {
constructor(filename = null) {
this.filename = filename;
this.magic = null;
this.numFiles = null;
this.files = [];
}
read() {
const data = fs.readFileSync(this.filename);
let offset = 0;
this.magic = data.toString('ascii', offset, offset + 2);
offset += 2;
if (this.magic !== 'HA') {
throw new Error('Invalid .HA magic');
}
this.numFiles = data.readUInt16LE(offset);
offset += 2;
console.log(`Magic: ${this.magic}`);
console.log(`Number of Files: ${this.numFiles}\n`);
const compTypes = {0: 'CPY', 1: 'ASC', 2: 'HSC', 14: 'DIR', 15: 'Special'};
for (let i = 0; i < this.numFiles; i++) {
const fileInfo = {};
const verComp = data[offset];
fileInfo.version = (verComp >> 4) & 0xF;
fileInfo.compType = verComp & 0xF;
offset += 1;
console.log(`File ${i + 1}:`);
console.log(` Version: ${fileInfo.version}`);
console.log(` Compression Type: ${compTypes[fileInfo.compType] || fileInfo.compType} (${fileInfo.compType})`);
fileInfo.compLen = data.readUInt32LE(offset);
offset += 4;
console.log(` Compressed Length: ${fileInfo.compLen}`);
fileInfo.uncompLen = data.readUInt32LE(offset);
offset += 4;
console.log(` Uncompressed Length: ${fileInfo.uncompLen}`);
fileInfo.crc32 = data.readUInt32LE(offset);
offset += 4;
console.log(` CRC-32: 0x${fileInfo.crc32.toString(16).padStart(8, '0').toUpperCase()}`);
fileInfo.timestamp = data.readUInt32LE(offset);
offset += 4;
const date = new Date(fileInfo.timestamp * 1000);
console.log(` Timestamp: ${date.toISOString()} (Unix: ${fileInfo.timestamp})`);
// Null-terminated strings
let pathEnd = offset;
while (data[pathEnd] !== 0) pathEnd++;
fileInfo.path = data.toString('ascii', offset, pathEnd);
offset = pathEnd + 1;
console.log(` Pathname: ${fileInfo.path || '(empty)'}`);
let nameEnd = offset;
while (data[nameEnd] !== 0) nameEnd++;
fileInfo.filename = data.toString('ascii', offset, nameEnd);
offset = nameEnd + 1;
console.log(` Filename: ${fileInfo.filename || '(empty)'}`);
const msiLen = data[offset];
offset += 1;
fileInfo.msiLen = msiLen;
console.log(` MSI Length: ${msiLen}`);
if (msiLen > 0) {
fileInfo.msiType = data[offset];
offset += 1;
const msiEnd = offset + msiLen - 1;
fileInfo.msiData = data.toString('ascii', offset, msiEnd);
offset = msiEnd;
console.log(` MSI Type: ${fileInfo.msiType}`);
console.log(` MSI Data: ${fileInfo.msiData || '(none)'}`);
}
console.log('');
this.files.push(fileInfo);
}
}
// Simple CRC32 poly (IEEE)
crc32(data) {
let crc = 0xFFFFFFFF;
for (let i = 0; i < data.length; i++) {
crc ^= data[i];
for (let j = 0; j < 8; j++) {
crc = (crc >> 1) ^ (0xEDB88320 & ((crc & 1) ? 0xFFFFFFFF : 0));
}
}
return (crc ^ 0xFFFFFFFF) >>> 0;
}
write(outputFilename, filesList) {
const buffer = Buffer.allocUnsafe(0);
buffer.write('HA');
buffer.writeUInt16LE(filesList.length, 2);
let offset = 4;
for (let filePath of filesList) {
const content = fs.readFileSync(filePath);
const uncompLen = content.length;
const compLen = uncompLen; // CPY
const crcVal = this.crc32(content);
const stats = fs.statSync(filePath);
const timestamp = Math.floor(stats.mtimeMs / 1000);
const dirName = require('path').dirname(filePath);
const baseName = require('path').basename(filePath);
const tempBuf = Buffer.allocUnsafe(20); // For nums
tempBuf.writeUInt8(0x00, 0); // Ver 0, CPY
tempBuf.writeUInt32LE(compLen, 1);
tempBuf.writeUInt32LE(uncompLen, 5);
tempBuf.writeUInt32LE(crcVal, 9);
tempBuf.writeUInt32LE(timestamp, 13);
buffer.writeBuffer(tempBuf, offset, 16);
offset += 16;
buffer.write(dirName + '\0', offset, 'ascii');
offset += dirName.length + 1;
buffer.write(baseName + '\0', offset, 'ascii');
offset += baseName.length + 1;
buffer.writeUInt8(0, offset); // MSI 0
offset += 1;
buffer.write(content, offset);
offset += compLen;
}
const finalBuffer = Buffer.allocUnsafe(offset);
buffer.copy(finalBuffer, 0, 0, offset);
fs.writeFileSync(outputFilename, finalBuffer);
console.log(`Wrote simple CPY .HA archive to ${outputFilename}`);
}
}
// Example usage:
// const archive = new HAArchive('example.ha');
// archive.read();
// archive.write('new.ha', ['file1.txt', 'file2.txt']);
- C Class (Struct) for .HA Handling
This is a basic C implementation using stdio/fread. Compile with gcc ha.c -o ha
. Read prints properties; write creates simple CPY archive (CRC via custom function). No decompression for HSC.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
typedef struct {
uint8_t version;
uint8_t comp_type;
uint32_t comp_len;
uint32_t uncomp_len;
uint32_t crc32;
uint32_t timestamp;
char *path;
char *filename;
uint8_t msi_len;
uint8_t msi_type;
char *msi_data;
} HAFile;
typedef struct {
char magic[3]; // Null-terminated
uint16_t num_files;
HAFile *files;
} HAArchive;
const char *comp_types[] = {"CPY", "ASC", "HSC", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "DIR", "Special"};
uint32_t crc32(const uint8_t *data, size_t len) {
uint32_t crc = 0xFFFFFFFF;
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
crc = (crc >> 1) ^ ((crc & 1) ? 0xEDB88320 : 0);
}
}
return crc ^ 0xFFFFFFFF;
}
void ha_read(const char *filename, HAArchive *arch) {
FILE *f = fopen(filename, "rb");
if (!f) {
perror("fopen");
return;
}
fread(arch->magic, 1, 2, f);
arch->magic[2] = '\0';
if (strncmp(arch->magic, "HA", 2) != 0) {
fprintf(stderr, "Invalid .HA magic\n");
fclose(f);
return;
}
fread(&arch->num_files, 2, 1, f); // LE assumed by host
arch->files = malloc(arch->num_files * sizeof(HAFile));
printf("Magic: %s\n", arch->magic);
printf("Number of Files: %u\n\n", arch->num_files);
for (uint16_t i = 0; i < arch->num_files; i++) {
HAFile *file = &arch->files[i];
uint8_t ver_comp;
fread(&ver_comp, 1, 1, f);
file->version = (ver_comp >> 4) & 0xF;
file->comp_type = ver_comp & 0xF;
fread(&file->comp_len, 4, 1, f);
fread(&file->uncomp_len, 4, 1, f);
fread(&file->crc32, 4, 1, f);
fread(&file->timestamp, 4, 1, f);
printf("File %u:\n", i + 1);
printf(" Version: %u\n", file->version);
printf(" Compression Type: %s (%u)\n", comp_types[file->comp_type] ? comp_types[file->comp_type] : "Unknown", file->comp_type);
printf(" Compressed Length: %u\n", file->comp_len);
printf(" Uncompressed Length: %u\n", file->uncomp_len);
printf(" CRC-32: 0x%08X\n", file->crc32);
time_t ts = file->timestamp;
printf(" Timestamp: %s (Unix: %u)\n", ctime(&ts), file->timestamp);
// Null-terminated
char buf[1024];
size_t path_len = fread(buf, 1, sizeof(buf) - 1, f);
while (buf[path_len - 1] != '\0' && path_len < sizeof(buf) - 1) {
path_len = fread(buf + path_len, 1, sizeof(buf) - path_len - 1, f);
}
buf[path_len] = '\0';
file->path = strdup(buf);
fseek(f, -(long)(path_len - strlen(buf) - 1), SEEK_CUR); // Skip null if needed, but fread stops
printf(" Pathname: %s\n", file->path ? file->path : "(empty)");
size_t name_len = fread(buf, 1, sizeof(buf) - 1, f);
while (buf[name_len - 1] != '\0' && name_len < sizeof(buf) - 1) {
name_len = fread(buf + name_len, 1, sizeof(buf) - name_len - 1, f);
}
buf[name_len] = '\0';
file->filename = strdup(buf);
printf(" Filename: %s\n", file->filename ? file->filename : "(empty)");
fread(&file->msi_len, 1, 1, f);
printf(" MSI Length: %u\n", file->msi_len);
if (file->msi_len > 0) {
fread(&file->msi_type, 1, 1, f);
file->msi_data = malloc(file->msi_len - 1);
fread(file->msi_data, 1, file->msi_len - 1, f);
file->msi_data[file->msi_len - 1] = '\0';
printf(" MSI Type: %u\n", file->msi_type);
printf(" MSI Data: %s\n", file->msi_data ? file->msi_data : "(none)");
}
printf("\n");
}
fclose(f);
}
void ha_write(const char *output_filename, char *files[], int num_files) {
FILE *f = fopen(output_filename, "wb");
if (!f) {
perror("fopen write");
return;
}
fwrite("HA", 1, 2, f);
fwrite(&num_files, 2, 1, f); // LE
for (int i = 0; i < num_files; i++) {
FILE *src = fopen(files[i], "rb");
if (!src) continue;
fseek(src, 0, SEEK_END);
long len = ftell(src);
fseek(src, 0, SEEK_SET);
uint8_t *content = malloc(len);
fread(content, 1, len, src);
fclose(src);
uint32_t uncomp_len = len;
uint32_t comp_len = len; // CPY
uint32_t crc_val = crc32(content, len);
struct stat st;
stat(files[i], &st);
uint32_t timestamp = st.st_mtime;
char *dir = dirname(strdup(files[i]));
char *base = basename(files[i]);
// Ver 0, CPY
uint8_t ver_comp = 0x00;
fwrite(&ver_comp, 1, 1, f);
fwrite(&comp_len, 4, 1, f);
fwrite(&uncomp_len, 4, 1, f);
fwrite(&crc_val, 4, 1, f);
fwrite(×tamp, 4, 1, f);
fwrite(dir, 1, strlen(dir) + 1, f);
fwrite(base, 1, strlen(base) + 1, f);
uint8_t msi_len = 0;
fwrite(&msi_len, 1, 1, f);
fwrite(content, 1, len, f);
free(content);
free(dir);
}
fclose(f);
printf("Wrote simple CPY .HA archive to %s\n", output_filename);
}
int main(int argc, char *argv[]) {
if (argc < 2) return 1;
HAArchive arch = {0};
ha_read(argv[1], &arch);
// For write: char *files[] = {"file1.txt", "file2.txt"}; ha_write("new.ha", files, 2);
// Free resources...
for (uint16_t i = 0; i < arch.num_files; i++) {
free(arch.files[i].path);
free(arch.files[i].filename);
free(arch.files[i].msi_data);
}
free(arch.files);
return 0;
}