# HPK Archiver for Haemimont Engine game files
[![Crates.io][crates-badge]][crates-url]
![Rust version][rust-version]
[![Actions build status][actions-badge]][actions-url]
[![Downloads][downloads-badge]][downloads-url]
[crates-badge]: https://img.shields.io/crates/v/hpk.svg
[crates-url]: https://crates.io/crates/hpk
[rust-version]: https://img.shields.io/badge/rust-1.70%2B-blue.svg
[actions-badge]: https://github.com/nickelc/hpk/workflows/ci/badge.svg
[actions-url]: https://github.com/nickelc/hpk/actions
[downloads-badge]: https://img.shields.io/github/downloads/nickelc/hpk/total?color=red
[downloads-url]: https://github.com/nickelc/hpk/releases
[lua-url]: https://www.lua.org
[unluac-url]: https://sourceforge.net/projects/unluac
[luadec-url]: https://github.com/viruscamp/luadec
## Supported games
* Tropico 3-5
* The First Templar
* Omerta: City of Gangsters
* Grand Ages: Rome
* Victor Vran
* Surviving Mars
## Overview
1. [Compatibility Notes](#compatibility-notes)
1. [Game Scripting](#game-scripting)
2. [Building](#building)
3. [Installation](#installation)
4. [Usage](#usage)
1. [`hpk help`](#hpk-help)
2. [`hpk list`](#hpk-list)
3. [`hpk create`](#hpk-create)
4. [`hpk extract`](#hpk-extract)
5. [`hpk debug-print`](#hpk-debug-print)
5. [HPK File Format](#hpk-file-format)
## Compatibility Notes
Surviving Mars hangs when hpk files in the `DLC` folder contain compressed files.
Use the `--dont-compress-files` option when creating files.
### Game Scripting
Haeminont uses [Lua][lua-url] for scripting their games.
The scripts contain only the compiled Lua bytecode and have to be decompiled
with [unluac][unluac-url] or [luadec][luadec-url] to get the source code.
With Victor Vran they switched from Lua `5.1` to `5.3` but the bytecode
headers are missing two bytes to make it possible to decompile the scripts.
| | Victor Vran's Lua Bytecode Header |
|--------|---------------------------------------------------------|
| Broken | `1B4C 7561 5300 1993 0D0A 1A0A 0404 ____ 0878 5600 ...` |
| Valid | `1B4C 7561 5300 1993 0D0A 1A0A 0404 0404 0878 5600 ...` |
| | Surviving Mars' Lua Bytecode Header |
|--------|---------------------------------------------------------|
| Broken | `1B4C 7561 5300 1993 0D0A 1A0A 0404 ____ 0878 5600 ...` |
| Valid | `1B4C 7561 5300 1993 0D0A 1A0A 0404 0408 0878 5600 ...` |
Use `hpk extract --fix-lua-files ...` to fix the scripts on extraction.
Use `hpk create --cripple-lua-files ...` to change the bytecode header of the compiled lua scripts on repacking.
The game can also load plain Lua files instead of a compiled version if you run into any problems.
## Building
hpk is written in Rust, so you'll need to grab a
[Rust installation](https://www.rust-lang.org/) in order to compile it.
Building is easy:
```
$ git clone https://github.com/nickelc/hpk
$ cd hpk
$ cargo build --release
$ ./target/release/hpk --version
hpk 0.3.0
```
## Installation
### Cargo
```bash
$ cargo install hpk
```
### Windows
Use the GitHub [Releases](https://github.com/nickelc/hpk/releases) tab to obtain the binary.
## Usage
### hpk help
```
$ hpk help
HPK archiver for Haemimont Engine game files (Tropico 3-5, Omerta, Victor Vran, Surviving Mars etc.)
Usage: hpk
Commands:
create Create a new hpk archive
extract Extract files from a hpk archive
list List the content of a hpk archive
debug-print Print debug information of a hpk archive
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
https://github.com/nickelc/hpk
```
### hpk list
```bash
$ hpk list files/omerta/Packs/TextureLists.hpk
entities.lst
fallback.lst
fx.lst
misc.lst
sky.lst
terrains.lst
water.lst
```
### hpk create
```text
$ hpk create -h
Create a new hpk archive
Usage: hpk create [OPTIONS]
Arguments:
input directory
hpk output file
Options:
--compress
Compress the whole hpk file
--chunk-size
Default chunk size: 32768
--cripple-lua-files
Cripple bytecode header for Victor Vran or Surviving Mars
--with-filedates
Stores the last modification times in a _filedates file
--filedate-fmt
Specifies the format of the stored filedates.
default: 'Windows file time' used by Tropico 3 and Grand Ages: Rome
short: 'Windows file time / 2000' used by Tropico 4 and Omerta
--dont-compress-files
No files are compressed. Overrides `--extensions`
--extensions ...
Specifies the file extensions to be compressed. default: [lst,lua,xml,tga,dds,xtex,bin,csv]
--lz4
Sets LZ4 as encoder
-h, --help
Print help (see more with '--help')
```
### hpk extract
```text
$ hg extract -h
Extract files from a hpk archive
Usage: hpk extract [OPTIONS] [paths]...
Arguments:
hpk archive
destination folder
[paths]... An optional list of archive members to be processed, separated by spaces.
Options:
--ignore-filedates Skip processing of a _filedates file and just extract it
--fix-lua-files Fix the bytecode header of Victor Vran's or Surviving Mars' Lua files
--force Force extraction if destination folder is not empty
-v Verbosely list files processed
-h, --help Print help
```
### hpk debug-print
```bash
$ hpk debug-print files/omerta/Packs/TextureLists.hpk
reading file: files/omerta/Packs/TextureLists.hpk
header:
data_offset: 0x24
fragments_residual_offset: 0x0
fragments_residual_count: 0
fragments_per_file: 1
fragments_filesystem_offset: 0x1459
fragments_filesystem_length: 64
filesystem entries: 8
filesystem fragments:
0x13D1 len: 136
0x24 len: 3876
0xF48 len: 278
0x105E len: 82
0x10B0 len: 134
0x1136 len: 140
0x11C2 len: 304
0x12F2 len: 223
dir: index=1 depth=0 ""
fragment: 0x13D1 len: 136
file: index=2 depth=1 "entities.lst"
fragment: 0x24 len: 3876
compressed: ZLIB inflated_length=30917 chunk_size=32768 chunks=1
chunks: 0x10 len: 3860
file: index=3 depth=1 "fallback.lst"
fragment: 0xF48 len: 278
compressed: ZLIB inflated_length=2956 chunk_size=32768 chunks=1
chunks: 0x10 len: 262
file: index=4 depth=1 "fx.lst"
fragment: 0x105E len: 82
compressed: ZLIB inflated_length=290 chunk_size=32768 chunks=1
chunks: 0x10 len: 66
file: index=5 depth=1 "misc.lst"
fragment: 0x10B0 len: 134
compressed: ZLIB inflated_length=344 chunk_size=32768 chunks=1
chunks: 0x10 len: 118
file: index=6 depth=1 "sky.lst"
fragment: 0x1136 len: 140
compressed: ZLIB inflated_length=536 chunk_size=32768 chunks=1
chunks: 0x10 len: 124
file: index=7 depth=1 "terrains.lst"
fragment: 0x11C2 len: 304
compressed: ZLIB inflated_length=2268 chunk_size=32768 chunks=1
chunks: 0x10 len: 288
file: index=8 depth=1 "water.lst"
fragment: 0x12F2 len: 223
compressed: ZLIB inflated_length=1978 chunk_size=32768 chunks=1
chunks: 0x10 len: 207
```
## HPK File Format
### Header
| Offset | Size | Value |
|--------|------|-----------------------------------------------|
| 0 | 4 | Magic number; `0x4C555042` (`"BPUL"`) |
| 4 | 4 | Data offset; `0x24` (`36`) |
| 8 | 4 | Number of fragments per file; `1`, `8` |
| 12 | 4 | Unknown; `0xFFFFFFFF` |
| 16 | 4 | Offset of the residual fragments in bytes |
| 20 | 4 | Number of residual fragments |
| 24 | 4 | Unknown; `0x1` |
| 28 | 4 | Offset of the filesystem fragments in bytes |
| 32 | 4 | Size of the filesystem fragments in bytes |
### Fragment (Filesystem & Residual)
* Offsets of fragments are relative from the start of the file.
* The first filesystem fragment is the root directory.
| Offset | Size | Value |
|--------|------|-------------------------------------------|
| 0 | 4 | Offset of a fragment in bytes |
| 4 | 4 | Size of a fragment in bytes |
### Filesystem Entry: Directory
| Offset | Size | Value |
|--------|------|-------------------------------------------|
| 0 | 4 | Fragment Index; Index starts with `1` |
| 4 | 4 | Entry Type; File=`0x0` Dir=`0x1` |
| 8 | 2 | Size of the following name in bytes |
| 10 | ? | Name data |
### Fragmented File (zlib/lz4/zstd compressed)
Offsets of compressed chunks are relative from the start of a fragment.
Victor Vran (Steam version) and Surviving Mars use LZ4 as compression.
ZSTD is used by Surviving Mars's mod editor.
The challenge hpks of Tropico 5 are compressed like a fragmented file.
| Offset | Size | Value |
|--------|------|-----------------------------------------------|
| 0 | 4 | Magic number, `0x42494C5A` (`"ZLIB"`)
`0x20345A4C` (`"LZ4 "`)
`0x4454535A` (`"ZSTD"`) |
| 4 | 4 | Size of the inflated data in bytes |
| 8 | 4 | Inflated chunk size, `0x0800` (`32768`) |
| 12 | 4 | Chunk offset in bytes, `0x10` for one chunk |
| * | 4 | Additional chunk offsets (optional) |
### `_filedates` File
HPK files can contain a `_filedates` file with the last modification times of the files and directories.
Each line consists of a path and a file time, separated by equals signs.
```
path/to/file=value
path/to/folder=value
```
Path is the basic path to the file but for Grand Ages: Rome the actual path is prefixed by
the basename of the original HPK file (Shaders.hpk `"Shaders/Shaders/VertexAnim.fx"`).
Value is either the Windows file time (`"default"`) or the Windows file time divided by 2000 (`"short"`).
| Format | Games |
|---------|-----------------------------|
| default | Grand Ages: Rome, Tropico 3 |
| short | Tropico 4, Omerta |