The HEMTT Book

What is HEMTT?

HEMTT is used to build your Arma 3 mod into PBOs for development and release. It is a replacement for tools like Addon Builder and pboProject.

HEMTT supports the majority of the features found in Bohemia's tools, but prioritizes support for the workflows of best practices and modern modding.

HEMTT is slightly opinionated, most configurations are supported, but some niche features are not and will not be supported. This is to keep the codebase small and maintainable, as well as promote best practices.

HEMTT works best when used in a Git based project, but the HEMTT executable should not be added to your version control.

It is also recommended to use VSCode for your Arma 3 modding, as it has great extensions that will support you in your modding journey.

Additionally the HEMTT VSCode extension is available.

Warning

The VSCode Plugin is in development, and available as an alpha

Using HEMTT in VSCode's integrated terminal is the recommended way to use HEMTT, the output can have helpful information that may be missed otherwise.

Installation

Download

The latest HEMTT release can be downloaded from the GitHub releases page.

Builds are available for Windows and Linux.

Windows

HEMTT can be installed using Winget.

winget install hemtt

To update HEMTT with winget use:

winget upgrade hemtt

Linux & MacOS

HEMTT can be installed using an installer script.

curl -sSf https://hemtt.dev/install.sh | bash

The script can be ran again to update HEMTT.

Manual Installation (Global)

HEMTT can be installed globally on your system, and used from anywhere.

The HEMTT executable can be placed in any directory on your system, and added to your PATH environment variable.

HEMTT can then be ran from any terminal with hemtt.

Manual Installation (Project Local)

The HEMTT executable can be placed in the root of your project, and used from there.

Warning

It is strongly recommended not to add it to your version control system.

HEMTT can then be ran from a terminal in the root of your project with .\hemtt.exe on Windows, or ./hemtt on Linux.

Note

Whenever possible use the global winget installation. That way HEMTT stays up-to-date.

Compile from Source

HEMTT can be compiled from source using Rust.

HEMTT usually requires the latest stable version of Rust, older versions may work but are not supported.

You can use the cargo install --path bin command to install HEMTT while in the root of the repository.

Configuration

Every HEMTT project requires a .hemtt/project.toml file. This file contains all the information HEMTT needs to build your mod.

Previous versions of HEMTT supported a hemtt.json or hemtt.toml file, but these are no longer supported.

Minimum Configuration

The minimum configuration only requires a name and prefix to be set.

.hemtt/project.toml

name = "Advanced Banana Environment"
prefix = "abe"

You can read more about these options on the Minimum Configuration page.

Version

HEMTT uses a custom version format based on standards in the Arma 3 community. You can read more about it on the Version page.

Project

You can additionally configure optional settings for your project.

Main Prefix

The mainprefix option allows you to set the root prefix for your project, used before the prefix option. This is currently only used by hemtt launch.

.hemtt/project.toml

mainprefix = "z"

It should match the $PBOPREFIX$ file in each addon folder.

addons/main/$PBOPREFIX$

z\abe\addons\main

Files

You can add a list of files to be copied to the build directory. This is useful for including files that are not part of addons, such as a README.md, LICENSE, logos, or extensions. To include a folder, you must use a glob pattern that matches all files in that folder.

.hemtt/project.toml

[files]
include = [
    "mod.cpp",        # These files are copied to the build directory by default
    "meta.cpp",       # They do not need to be added to your list
    "LICENSE",
    "logo_ca.paa",
    "logo_co.paa",
    "python_code/**", # Copy the folder "python_code" including all its files
]
exclude = [
    "*.psd",          # By default this list is empty
    "addons/main/README.md",
]

include

By default, those 5 files are included in the build directory if they exist in the root of your project. You do not need to add them to your list. Additional files or glob paths can be added to the list.

.hemtt/project.toml

[files]
include = [
    "mod.cpp",      # These files are copied to the build directory by default
    "meta.cpp",     # They do not need to be added to your list
    "LICENSE",
    "logo_ca.paa",
    "logo_co.paa",
]

exclude

By default, no files are excluded from PBOs. You can add files or glob paths to the list.

.hemtt/project.toml

[files]
exclude = [
    "*.psd",        # By default this list is empty
    "addons/main/README.md",
]

properties

You can add a list of properties to be added to every PBO.

.hemtt/project.toml

[properties]
author = "ABE Team"
url = "https://github.com/ABE-Mod/ABE"

Minimum Configuration

The minimum configuration only requires a name and prefix to be set.

.hemtt/project.toml

name = "Advanced Banana Environment"
prefix = "abe"

name

The name of your mod, currently unused.

prefix

The prefix of your mod.

It should be used in the $PBOPREFIX$ file in each addon folder in the following format.

z\{prefix}\addons\main

addons/main/$PBOPREFIX$

z\abe\addons\main

Version Configuration

HEMTT uses the project version as part of the signing authority. It is also included as an property in built PBOs.

No [version] configuration is required if you use macros in your script_version.hpp file.

.hemtt/project.toml

[version]
path = "addons/main/script_version.hpp" # Default

major = 1 # Overrides path when set
minor = 0
patch = 4
build = 3 # Optional

git_hash = 0 # Default: 8

Macros

By default, HEMTT will look for addons/main/script_version.hpp and use the version components defined there. No [version] configuration is required. A major, minor, and patch version are required, and a build version is optional.

/addons/main/script_version.hpp

#define MAJOR 1
#define MINOR 0
#define PATCH 4 // `#define PATCHLVL` can also be used
#define BUILD 3 // Optional

If your macros are in another file, you can set the path with the version.path key.

.hemtt/project.toml

[version]
path = "addons/common/script_version.hpp"

Defined in Configuration

If you do not want to use a version file, you can set the version components directly in the configuration. If a version is defined in the configuration, the macros will not be used, even if a path is set.

.hemtt/project.toml

[version]
major = 1
minor = 0
patch = 4
build = 3 # Optional

Git Hash

By default, HEMTT will include the first 8 characters of the current git hash in the version. Since the git hash is enabled by default, without configuration HEMTT will require a git repository with at least one commit to be present. The git hash can be disabled by setting version.git_hash = 0, or configured to a different length.

.hemtt/project.toml

[version]
git_hash = 0 # Disabled
git_hash = 4 # 4 characters

Lint Configuration

HEMTT runs lints against your config and SQF files to check for errors, common mistakes, and improvements. Some lints can be disabled, configured, or changed in severity.

Lints can be kept in the project.toml file under the lints section, or in a separate .hemtt/lints.toml file. When kept in lints.toml, the lints. prefix is not required.

See the Analysis section for Config and SQF lints.

Configuration

Note

Some lints are not able to be disabled or reduced in severity. These are usually critical lints that are required for your project to work correctly.

There are 3 ways to configure a lint:

Disabling

A lint can be completely disabled by setting it to false. Only lints that are not critical can be disabled.

[lints.sqf]
command_case = false

Severity

A lint can have its severity changed. The severity can be Error, Warning, or Help. Only lints that are not critical errors can have their severity changed.

[lints.sqf]
command_case = "Warning"

Options

Some lints provide configuration options. Check the documentation for the lint to see what options are available.

[lints.sqf.command_case]
severity = "Error"
options.ignore = [
    "AGLtoASL",
    "ASLtoAGL",
]

Addon Configuration

In addition to .hemtt/project.toml, HEMTT also supports an optional addon.toml in each addon folder.

/addons/banana/addon.toml

[binarize]
enabled = false # Default: true
exclude = [
    "data/*.p3d",
    "data/anim/chop.rtm",
]

[rapify]
enabled = false # Default: true
exclude = [
    "missions/**/description.ext",
]

[files]
exclude = [
    ".vscode/**/*", # Exclude all files in the .vscode folder
    "data/*.psd",
]

[properties]
iso = "14001"

binarize

HEMTT's binarization of addons can be disabled for the addon by setting binarize.enabled to false, or disabled for specific files by adding glob patterns to binarize.exclude.

/addons/banana/addon.toml

[binarize]
enabled = false # Default: true
exclude = [
    "data/*.p3d",
    "data/anim/chop.rtm",
]

rapify

HEMTT's preprocessing & rapifying of addon configs can be disabled for the addon by setting rapify.enabled to false, or disabled for specific files by adding glob patterns to rapify.exclude.

When it is required to disable preprocessing & rapifying of config.cpp, it is recommended to create a separate addon to house any optional config, with the minimum amount of code required to make it work. Disabling preprocessing & rapifying will allow you to ship invalid config, which could cause issues for your players. It will also cause slower load times when the config is valid.

/addons/banana/addon.toml

[rapify]
enabled = false # Default: true
exclude = [
    "missions/**/description.ext",
]

files

files.exclude is an array of glob patterns that will be excluded and not packed into the PBO. It is important to note that this matches against files, not folders. To exclude a folder, you must use a glob pattern that matches all files in that folder.

/addons/banana/addon.toml

[files]
exclude = [
    ".vscode/**/*", # Exclude all files in the .vscode folder
    "data/*.psd",
]

properties

Much like the properties key in .hemtt/project.toml, the properties key in addon.toml allows you to add custom properties to the PBO.

/addons/banana/addon.toml

[properties]
iso = "14001"

P Drive

By default, HEMTT does not require a P Drive (sometimes called a Work Drive), and many mods do not require one.

HEMTT supports a P Drive for mods that do require one, but only to access \a3\.

Includes

Whenever possible, an .\include\ folder should be used in place of a P Drive. Any files placed in the .\include\ folder can be used from models or scripts as if they were in a P Drive. No $PBOPREFIX$ is required, but the full path must be created in the .\include\ folder.

The most common use case is for CBA's script_macros_common.hpp, you can see an example of this in ACE's GitHub Repo.

Default Behaviour, Ignored

By default, HEMTT will allow references to a P Drive, but will not fail the build if it does not exist. Even if a P Drive exists, it will not be used by HEMTT unless explicitly required by the project.

Disallowing P Drive

If a P Drive is explicitly disallowed by the project, it can specify as such.

.hemtt/project.toml

[hemtt.build]
pdrive = "disallow"

[hemtt.check]
pdrive = "ignore"

When disallowed by the project, HEMTT will fail to build the project if any references to a P Drive are found.

Requiring P Drive

If a P Drive is required by the project, it must specify as such. If the flag is not set, HEMTT will not allow the P Drive to be used.

.hemtt/project.toml

[hemtt.build]
pdrive = "require"

[hemtt.check]
pdrive = "ignore"

When required by the project, HEMTT will fail to build the project if all required files can not be resolved. HEMTT will only enable use of P:\a3\.

HEMTT will look for a P Drive in the following order:

P Drive (Mounted)

HEMTT will use P:\ as expected when it exists.

P Drive (Unmounted)

HEMTT will use the path configured in Arma 3 Tools as the P Drive, even if it is customized from the default and unmounted.

Arma 3 Installation

If no P Drive, mounted or unmounted, is found, HEMTT will attempt to extract the required files from your Arma 3 Installation.

Custom Commands

Danger

You are reading about an advanced and niche feature. This is specific for projects using Intercept and injecting custom SQF commands. This is not a feature that is commonly used.

Custom commands are defined using yaml inside the .hemtt/commands directory. The file name should be the name of the file, but this is not enforced. The command name will be taken from the defined name in the yaml file.

For reference, you can find all of Arma 3's commands in the arma3-wiki repo on the dist branch, under the acemod organization.

name: bananize
description: Bananize the player, forcing them to only throw bananas.
syntax:
- call: !Unary player
  ret:
    - Boolean
    - The success of the bananization
  params:
  - name: unit
    description: The unit to bananize
    type: Object
argument_loc: Global
effect_loc: Global
examples:
- <sqf>bananize player</sqf>

Commands

Development

Release

Options

--just

The build and dev commands can be used to build a single addon. It can be used multiple times to build multiple addons.

hemtt build --just myAddon

Danger

It is advised to only use this on very large projects that take a long time to build. It is advised to only use this after running the command once without --just to ensure all addons are built. Anytime you run any git commands that can modify files, you should run without --just to ensure all addons are up to date. Before reporting any unexpected behavior, try running without --just first.

Global Options

-t, --threads

Number of threads to use, defaults to the number of CPUs.

hemtt ... -t 4

-v

Verbosity level, can be specified multiple times.

hemtt ... -v # Debug
hemtt ... -vv # Trace

When running inside a CI platform like GitHub Actions, the output will always be set to trace.

Note

The full log can also be found at .hemttout/latest.log after each build

hemtt new

Create a new mod

Usage: hemtt new [OPTIONS] <name>

Arguments:
  <name>  Name of the new mod

Options:
    -h, --help
        Print help information (use `-h` for a summary)

hemtt new is used to create a new mod. It will create a new folder with the name you provide, and create some starting files.

It will ask for:

  • The full name of your mod
  • The author of your mod
  • The prefix of your mod
  • The main prefix of your mod
  • A license for your mod

hemtt dev

Build your mod for local development and testing.

Usage: hemtt dev [OPTIONS]

Options:
    -b, --binarize
        Use BI's binarize on supported files
        
    --no-rap
        Do not rapify files

    -o, --optional <optional>
        Include an optional addon folder

    -O, --all-optionals
        Include all optional addon folders

    --just <just>
        Only build the specified addon

    -t, --threads <threads>
        Number of threads, defaults to # of CPUs

    -v...
        Verbosity level

    -h, --help
        Print help information (use `-h` for a summary)

hemtt dev is designed to help your development workflows. It will build your mod into .hemttout/dev, with links back to the original addon folders. This allows you to use file-patching with optional mods for easy development.

Configuration

.hemtt/project.toml

[hemtt.dev]
exclude = ["addons/unused"]

exclude

A list of addons to exclude from the development build. Includes from excluded addons can be used, but they will not be built or linked.

Options

-b, --binarize

By default, hemtt dev will not binarize any files, but rather pack them as-is. Binarization is often not needed for development, but can be enabled with the -b --binarize flag.

hemtt dev -b

-o, --optional

Include an optional addon folder. This can be used multiple times to include multiple optional addons.

hemtt dev -o caramel -o split

-O, --all-optionals

Include all optional addon folders.

hemtt dev -O

hemtt launch

Launch Arma 3 with your mod and dependencies.

Usage: hemtt launch [OPTIONS] [config]... [-- <passthrough>...]

Arguments:
  [config]...
        Launches with the specified `hemtt.launch.` configurations

  [passthrough]...
        Passthrough additional arguments to Arma 3

Options:
    -e, --executable <executable>
        Arma 3 executable to launch

    

    -i, --instances <instances>
          Launches multiple instances of the game

          [default: 1]

    -Q, --quick
        Skips the build step, launching the last built version

    -b, --binarize
        Use BI's binarize on supported files
        
    --no-rap
        Do not rapify files

    --no-filepatching
        Do not enable filePatching

    -o, --optional <optional>
        Include an optional addon folder

    -O, --all-optionals
        Include all optional addon folders

    -t, --threads <threads>
        Number of threads, defaults to # of CPUs

    -v...
        Verbosity level
        

    -h, --help
        Print help information (use `-h` for a summary)

hemtt launch is used to build and launch a dev version of your mod. It will run the hemtt dev command internally after a few checks, options are passed to the dev command.

You can chain multiple configurations together, and they will be overlayed from left to right. Any arrays will be concatenated, and any duplicate keys will be overridden. With the below configuration, hemtt launch default vn ace would launch with all three configurations. Note that default must be specified when specifying additional configurations, default is only implied when no configurations are specified.

Configuration

hemtt launch requires the mainprefix option to be set.

Launch configurations can be stored in either .hemtt/project.toml under hemtt.launch, or in a separate file under .hemtt/launch.toml. The latter is useful for keeping your main configuration file clean. When using launch.toml, the hemtt.launch key is not required.

.hemtt/project.toml

mainprefix = "z"

# Launched with `hemtt launch`
[hemtt.launch.default]
workshop = [
    "450814997", # CBA_A3's Workshop ID
]
presets = [
    "main", # .html presets from .hemtt/presets/
]
dlc = [
    "Western Sahara",
]
optionals = [
    "caramel",
]
mission = "test.VR" # Mission to launch directly into the editor with
parameters = [
    "-skipIntro",           # These parameters are passed to the Arma 3 executable
    "-noSplash",            # They do not need to be added to your list
    "-showScriptErrors",    # You can add additional parameters here
    "-debug",
    "-filePatching",
]
executable = "arma3" # Default: "arma3_x64"
file_patching = false # Default: true
binarize = true # Default: false
rapify = false # Default: true

# Launched with `hemtt launch vn`
[hemtt.launch.vn]
extends = "default"
dlc = [
    "S.O.G. Prairie Fire",
]

# Launched with `hemtt launch ace`
[hemtt.launch.ace]
extends = "default"
workshop = [
    "463939057", # ACE3's Workshop ID
]

.hemtt/launch.toml

[default]
workshop = [
    "450814997", # CBA_A3's Workshop ID
]

[vn]
extends = "default"
dlc = [
    "S.O.G. Prairie Fire",
]

extends

The name of another configuration to extend. This will merge all arrays with the base configuration, and override any duplicate keys.

workshop

A list of workshop IDs to launch with your mod. These are not subscribed to, and will need to be manually subscribed to in Steam.

presets

A list of .html presets to launch with your mod. Exported from the Arma 3 Launcher, and kept in .hemtt/presets/.

dlc

A list of DLCs to launch with your mod. The fullname or short-code can be used.

Currently supported DLCs:

Full NameShort Code
Contactcontact
Global Mobilizationgm
S.O.G. Prairie Firevn
CSLA Iron Curtaincsla
Western Saharaws
Spearhead 1944spe
Reaction Forcesrf

optionals

A list of optional addon folders to launch with your mod.

mission

The mission to launch directly into the editor with. This can be specified as either the name of a folder in .hemtt/missions/ (e.g., test.VR would launch .hemtt/missions/test.VR/mission.sqm) or the relative (to the project root) path to a mission.sqm file or a folder containing it.

parameters

A list of Startup Parameters to pass to the Arma 3 executable.

executable

The name of the Arma 3 executable to launch. This is usually arma3 or arma3_x64. Do not include the .exe extension, it will be added automatically on Windows. Only paths relative to the Arma 3 directory are supported.

file_patching

Whether to launch Arma 3 with -filePatching. Equivalent to --no-filepatching or -F.

binarize

Whether to use BI's binarize on supported files. Equivalent to --binarize.

rapify

Provides the ability to disable rapify for the launch command. Equivalent to --no-rap.

Options

-i, --instances <instances>

Launches multiple instances of the game. If unspecified, it will default to 1.

hemtt launch -i 2

-Q, --quick

Skips the build step, launching the last built version. Will throw an error if no build has been made, or no symlink exists.

hemtt launch -Q

-e, --executable <executable>

The Arma 3 executable to launch. Overrides the executable option in the configuration file.

hemtt launch -e arma3profiling_x64 # Relative to the Arma 3 directory
hemtt launch -e "C:\Program Files\Steam\steamapps\common\Arma 3\arma3_x64.exe" # Absolute path

--no-filepatching

Do not launch Arma 3 with -filePatching.

Passthrough Options

Any options after -- will be passed to the Arma 3 executable. This is useful for passing additional Startup Parameters.

hemtt launch -- -world=empty -window

hemtt build

Build your project

Usage: hemtt build [OPTIONS]

Options:
    --no-bin
        Do not binarize files

    --no-rap
        Do not rapify files

    --just <just>
        Only build the specified addon

    -t, --threads <threads>
        Number of threads, defaults to # of CPUs

    -v...
        Verbosity level

    -h, --help
        Print help information (use `-h` for a summary)

hemtt build will build your mod into .hemttout/build. It will binarize all applicable files, and will not create folder links like hemtt dev.

It is intended to be used for testing your mod locally before release.

Configuration

.hemtt/project.toml

[hemtt.build]
optional_mod_folders = false # Default: true

optional_mod_folders

By default, hemtt build will create separate mods for each optional mod folder.

Options

--no-bin

Do not binarize any files. They will be copied directly into the PBO. config.cpp, *.rvmat, *.ext will still be rapified.

This can be configured per addon in addon.toml.

--no-rap

Do not rapify any files. They will be copied directly into the PBO.

This can be configured per addon in addon.toml.

hemtt release

Build a release version your project

Usage: hemtt release [OPTIONS]

Options:
    --no-sign
        Do not sign the PBOs

    --no-archive
        Do not create a zip archive of the release

    --no-bin
        Do not binarize files

    --no-rap
        Do not rapify files

    -t, --threads <threads>
        Number of threads, defaults to # of CPUs

    -v...
        Verbosity level

    -h, --help
        Print help information (use `-h` for a summary)

hemtt release will build your mod into .hemttout/release. It will create bisign files for all addons, and a bikey for validation.

It is intended to be used for releasing your mod.

It will create two zip archives in the releases folder: - {name}-latest.zip - {name}-{version}.zip

Configuration

hemtt release is built the same way as hemtt build, and will use its configuration.

[hemtt.release]
sign = false # Default: true
archive = false # Default: true

sign

If sign is set to false, a bikey will not be created, and the PBOs will not be signed.

Danger

All public releases of your mods should be signed. This will be a requirement of many communities, and is an important security feature.

archive

If archive is set to false, a zip archive will not be created. The output will be in .hemttout/release.

Options

--no-sign

Do not sign the PBOs or create a bikey.

--no-archive

Do not create a zip archive of the release. The output will be in .hemttout/release.

hemtt script

Run a Rhai script on the project

Usage: hemtt script [OPTIONS] <script>

Options:
    -v...
        Verbosity level

    -h, --help
        Print help information (use `-h` for a summary)

hemtt script is used to run a Rhai script on the project, this is useful for automating tasks in a platform agnostic way, or requiring external dependencies.

Learn more about Scripts.

Rhai

Rhai is a simple scripting language that is embedded in HEMTT for Hooks and Scripts. It has a syntax similar to Javascript, and uses types similar to Rust.

A few examples of Rhai are provided below, but this is not a complete reference. The full Language Reference can be found in the Rhai Book.

Variables

Variables are defined using let and const, variables are dynamically typed. Variables defined with const cannot be changed.

let x; // ()
let x = 1; // (i32)
let x = 1.1; // (f32)
const x = "hello"; // (string)

Shadowing

Variables are shadowed in the current scope when redefined.

let x = 1;
print(x); // 1

{
    let x = 2;
    print(x); // 2
}

print(x); // 1

Logic

If

if foo() {
    print("foo is true");
} else if x == 2 {
    print("x is 2");
} else {
    print("foo is false and x is not 2");
}

If statements can be used as expressions.

let x = if foo() {
    1
} else {
    2
};

Libraries

HEMTT adds some additional functionality to Rhai through libraries.

Logging

Rhai

Rhai has two built in functions for logging, print and debug.

print(string)

Prints a string to the console.

print("Hello World!");
 INFO [post_release/test.rhai] Hello World!

debug(any)

Prints a representation of the value to the console if the --debug flag is passed to HEMTT.

debug(HEMTT.version().to_string());
debug(HEMTT.project().version.major());
DEBUG [post_release/test.rhai] "1.13.2"
DEBUG [post_release/test.rhai] 1

HEMTT

HEMTT provides additional logging functions.

info(string)

Prints a string to the console. Same functionality as print.

info("Hello World!");
 INFO [post_release/test.rhai] Hello World!

warn(string)

Prints a string to the console with a warning prefix.

warn("Hello World!");
 WARN [post_release/test.rhai] Hello World!

error(string)

Prints a string to the console with an error prefix.

error("Hello World!");
ERROR [post_release/test.rhai] Hello World!

fatal(string)

Prints string to the console with an error prefix, HEMTT will mark the build as failed and exit.

fatal("Hello World!");
ERROR [post_release/test.rhai] Hello World!
error: Hook signaled failure: post_release/test.rhai

HEMTT

The HEMTT constant gives access to information and the ability to modify the build process.

version()

Returns the version of HEMTT.

HEMTT.version().to_string(); // "1.13.2"
HEMTT.version().major(); // 1
HEMTT.version().minor(); // 13
HEMTT.version().patch(); // 2
HEMTT.version().build(); // ""

project()

Returns the project information.

See more about the Project library.

HEMTT.project().version().to_string(); // "1.3.0.1052"

mode()

Returns the current mode of HEMTT, one of:

  • dev
  • launch
  • build
  • release
HEMTT.mode(); // "release"

is_dev()

Returns true if the current mode is dev.

HEMTT.is_dev(); // false

is_launch()

Returns true if the current mode is launch.

HEMTT.is_launch(); // false

is_build()

Returns true if the current mode is build.

HEMTT.is_build(); // false

is_release()

Returns true if the current mode is release.

HEMTT.is_release(); // true

Project

version()

Returns the project version.

.build() will return 0 if the build number is not set.

HEMTT.project().version().to_string(); // "1.3.0.1052"
HEMTT.project().version().to_string_short(); // "1.3.0"
HEMTT.project().version().major(); // 1
HEMTT.project().version().minor(); // 3
HEMTT.project().version().patch(); // 0
HEMTT.project().version().build(); // 1052

name()

Returns the project name.

HEMTT.project().name(); // "Advanced Banana Environment"

prefix()

Returns the project prefix.

HEMTT.project().prefix(); // "abe"

mainprefix()

Returns the project main prefix, empty if not set.

HEMTT.project().mainprefix(); // "z"

File System

HEMTT has two types of file systems, which one is used depends on the context.

Scripts always use the real file system, as they run outside of the build process.

Hooks use the virtual file system during the pre_build, post_build, and pre_release phases.

HEMTT_VFS - Virtual File System

pre_build, post_build, and pre_release phases have access to the virtual file system. This means that the files are not actually written to disk. Files can be created, deleted, read from, written to, and these changes will appear only in the build output.

This is useful for modifying files with find-and-replace, or adding files to the build output, without the need for cleaning up after the build.

When using the virtual file system, the HEMTT_VFS constant is available. It is used as the root path.

Warning

During the pre_release phase, only files outside of addons should be changed. PBOs are already built, and changing files inside of addons will have no effect.

.hemtt/project.toml

[version]
major = 1
minor = 0
patch = 3

.hemtt/hooks/pre_build/set_version.rhai

// Get the path to the script_version.hpp file
let version = HEMTT_VFS
        .join("addons")
        .join("main")
        .join("script_version.hpp");
// Create (or overwrite) the file
let out = version.create_file();
out.write("#define MAJOR " + HEMTT.project().version().major() + "\n");
out.write("#define MINOR " + HEMTT.project().version().minor() + "\n");
out.write("#define PATCH " + HEMTT.project().version().patch() + "\n");
if HEMTT.project().version().build() != "" {
    out.write("#define BUILD " + HEMTT.project().version().build() + "\n");
}
print("Set version to " + HEMTT.project().version().to_string());

HEMTT_RFS - Real File System

All phases and scripts have access to the real file system. This means that the files are actually written to disk.

Danger

Be careful when modifying files while using the real file system, as you can destructively modify the project files. It is recommended to use the virtual file system whenever possible, and commit the changes to the project files prior to testing hooks.

When using the real file system, two additional constants are available. HEMTT_RFS is the root of the project, and HEMTT_OUT is the root of the build output.

.hemtt/hooks/pre_release/set_version.rhai

// Read the current contents of the docs/version.txt
// file from the project source
let version = HEMTT_RFS.join("docs").join("version.txt").open_file().read();
// Replace the placeholder version with the actual version
version.replace("0.0.0", HEMTT.project().version().to_string());
// Write the new contents to the build output
// create_file will overwrite the file if it exists
HEMTT_OUT.join("docs").join("version.txt").create_file().write(version);

Functions

All the functions below are available on both the virtual and real file systems.

join(string)

Joins the path with the given string.

HEMTT_VFS.join("addons"); // Points to ./addons in the project folder
HEMTT_VFS.join("addons").join("main"); // Points to ./addons/main in the project folder

exists()

Returns true if the path exists.

HEMTT_VFS.join("addons").exists(); // true
HEMTT_VFS.join(".hemtt").join("project.toml").exists(); // true

is_dir()

Returns true if the path is a directory.

HEMTT_VFS.join("addons").is_dir(); // true
HEMTT_VFS.join(".hemtt").join("project.toml").is_dir(); // false

is_file()

Returns true if the path is a file.

HEMTT_VFS.join("addons").is_file(); // false
HEMTT_VFS.join(".hemtt").join("project.toml").is_file(); // true

parent()

Returns the parent directory of the path.
Will panic if the path is root while using the real file system.
Will return the root path while using the virtual file system, if already at the root.

HEMTT_VFS.join("addons").parent(); // Points to ./
HEMTT_VFS.join(".hemtt").join("project.toml").parent(); // Points to ./.hemtt

file_name()

Returns the file name of the path.

HEMTT_VFS.join("addons").file_name(); // addons
HEMTT_VFS.join(".hemtt").join("project.toml").file_name(); // project.toml

copy(path)

Copies the file or directory to the given path.

HEMTT_VFS.join("docs").copy(HEMTT_OUT.join("docs")); // Copies the docs folder to the build output

move(path)

Moves the file or directory to the given path.

HEMTT_VFS.join("docs").move(HEMTT_OUT.join("docs")); // Moves the docs folder to the build output

list(path)

Lists the contents of the directory. If the path is a file, returns an empty array.

HEMTT_VFS.join("docs").list(); // Returns an array of paths of files and directories in the docs folder

open_file()

Opens the file for reading.

HEMTT_VFS.join("docs").join("readme.md").open_file(); // Returns a File object

create_file()

Creates the file for writing. Overwrites the file if it exists.

HEMTT_VFS.join("docs").join("readme.md").create_file(); // Returns a File object

remove_file()

Removes the file.

HEMTT_VFS.join("docs").join("readme.md").remove_file(); // Removes the file

read()

Reads the contents of the file.

HEMTT_VFS.join("docs").join("readme.md").open_file().read(); // Returns a string containing the contents of the file

write(string)

Writes the string to the file. Can be called multiple times to append to the file.

HEMTT_VFS.join("docs").join("readme.md").create_file().write("Hello World!"); // Writes "Hello World!" to the file

create_dir()

Creates the directory.

HEMTT_VFS.join("docs").create_dir(); // Creates the docs folder

create_dir_all()

Creates the directory and all parent directories.

HEMTT_VFS.join("docs").join("images").create_dir_all(); // Creates the images folder and the docs folder if they don't exist

remove_dir()

Removes the directory.

HEMTT_VFS.join("docs").remove_dir(); // Removes the docs folder

remove_dir_all()

Removes the directory and all its contents.

HEMTT_VFS.join("docs").remove_dir_all(); // Removes the docs folder and all its contents

Time

date(string)

Returns the current date in the given format.

date("[year]-[month]-[day] [hour]:[minute]:[second]"); // {{ time_1 }}
date("[year repr:last_two][month][day]"); // {{ time_2 }}

You can find the full list of format specifiers here.

Hooks

HEMTT supports hooks at various points in the build process. The hooks are written using Rhai. Rhai has an extension for VSCode that provides syntax highlighting.

Some example Rhai scripts can be found on the Rhai Playground. Additional commands can be requested as a GitHub Discussion.

Hooks are stored in the .hemtt/hooks/{phase} folders. The {phase} is the name of the phase that the hook is run in. The hooks are run in alphabetical order.

Example

.hemtt
└── hooks
    ├── pre_build
    │   ├── 01_example.rhai
    │   └── 02_example.rhai
    └── post_build
        ├── 01_example.rhai
        └── 02_example.rhai

Phases

There are 4 phases of the build process that can be hooked into:

HookFile System
pre_buildVirtual
post_buildVirtual
pre_releaseReal
post_releaseReal

pre_build

The pre_build hook is run before any preprocessing, binarization, or packing PBOs. This is the place to modify files that will be packed into the PBOs.

post_build

The post_build hook is run after all preprocessing, binarization, and packing PBOs. It is run before any release tasks.

pre_release

The pre_release hook is run before any release tasks. It is only run during the hemtt release command.

post_release

The post_release hook is run after all release tasks, and archives have been created. It is only run during the hemtt release command.

Hook Examples

Renaming the release zip

We want to modify the release zip to a different name format, we need to use the real file system during the post_release phase. This means that the hook will only run during the hemtt release command.

.hemtt/hooks/post_release/rename_zip.rhai

let releases = HEMTT_RFS.join("releases");
let src = releases.join(HEMTT.project().prefix() + "-latest.zip"); // "prefix-latest.zip"
let dst = releases.join("@" + HEMTT.project().prefix() + ".zip"); // "@prefix.zip"
if src.is_file() { // support --no-archive
    print("Moving zip to " + dst);
    if !src.move(dst) {
        fatal("Failed to move " + src + " to " + dst);
    }
}

Setting the version in a file

We want to set the version of the project in the mod.cpp file included in our builds, we need to use the virtual file system during the pre_build phase. This means that the hook will run during the hemtt build and hemtt release commands.

Since we are using the virtual file system, the file on disk will not be modified.

.hemtt/hooks/pre_build/set_version.rhai

let modcpp = HEMTT_VFS.join("mod.cpp").open_file().read(); // Read the contents of mod.cpp
modcpp.replace("0.0.0", HEMTT.project().version().to_string_short()); // Replace the placeholder version with the actual version
HEMTT_VFS.join("mod.cpp").create_file().write(modcpp); // Write the new contents over the old contents

Scripts

Scripts can be called from the command line using hemtt script <script>.

The files are located in the .hemtt/scripts folder, and are written in Rhai.

They have access to all the same libraries as hooks, but only use the real file system, since they run outside of the build process.

Scripts are useful for automating tasks that are not part of the build process, such as creating a new addon, or updating the version number.

Examples

Bumping the minor version

.hemtt/scripts/bump_minor.rhai

// Read the current contents of script_version.hpp
let script_version = HEMTT_RFS.join("addons")
    .join("main")
    .join("script_version.hpp")
    .open_file()
    .read();

// Replace the current version with the new version
let prefix = "#define MINOR ";
let current = HEMTT.project().version().minor();
let next = current + 1;

script_version.replace(prefix + current.to_string(), prefix + next.to_string());

// Write the modified contents to script_version.hpp
HEMTT_RFS.join("addons")
    .join("main")
    .join("script_version.hpp")
    .create_file()
    .write(script_version);

hemtt utils inspect

Provides information about supported files. Supported: pbo, bikey, bisign

Usage: hemtt utils inspect [OPTIONS] <file>

Arguments:
  <file>
        File to inspect

Options:
  -v...
        Verbosity level

  -h, --help
        Print help (see a summary with '-h')

This will inspect a file and provide information about it.

Currently supported files are:

  • .pbo
  • .bikey
  • .bisign

hemtt utils pbo inspect

Inspect a PBO

Usage: hemtt utils pbo inspect [OPTIONS] <pbo>

Arguments:
  <pbo>
        PBO to inspect

Options:
  -v...
        Verbosity level

  -h, --help
        Print help (see a summary with '-h')

Provides information about a PBO.

This is the same as hemtt utils inspect but will assume the file is a PBO.

In some cases the output might be cut off in the terminal. Adjust the terminal.integrated.scrollback setting in VS Code if necessary.

Example

Check abe_main.pbo located in the build folder

hemtt.exe utils pbo inspect .hemttout\build\addons\abe_main.pbo

hemtt utils pbo extract

Extract a file from a PBO

Usage: hemtt utils pbo extract [OPTIONS] <pbo> <file> [output]

Arguments:
  <pbo>     PBO file to extract from
  <file>    File to extract
  [output]  Where to save the extracted file

Options:
  -v...                    Verbosity level
  -h, --help               Print help

Extracts a file from a PBO.

If no output is specified, it will be written to stdout.

hemtt utils pbo unpack

Unpack a PBO

Usage: hemtt utils pbo unpack [OPTIONS] <pbo> <output>

Arguments:
  <pbo>     PBO file to unpack
  <output>  Directory to unpack to

Options:
  -v...                    Verbosity level
  -h, --help               Print help

Unpacks a PBO to a directory.

A $PBOPREFIX$ file will be created in the output directory containing the prefix of the PBO.

All other properties from the PBO will be saved into properties.txt

hemtt utils sqf case

Danger

This command requires manual review. It can have lots of false positives so you are strongly encouraged to check each modified file to ensure it is correct.

Fix capitalization in SQF commands

Usage: hemtt utils sqf [OPTIONS] <path>

Arguments:
  <path>
          Path to the SQF file or a folder to recursively fix

Options:
    -t, --threads
        Number of threads, defaults to # of CPUs

    -h, --help
        Print help information (use `-h` for a summary)

This will recursively correct all capitalization mistakes in SQF commands.

Example

private _positionASL = GetPosasl Player;
// becomes
private _positionASL = getPosASL player;

False Positives

This command does not full parse your SQF files.

It will not change words in strings in comments, but it may change words that will break your program

// script_macros.hpp
#define FALSE 0
#define TRUE 1

// fnc_someFunction.sqf
if (getNumber (configFile >> "someClass" >= TRUE)) then {...};
// becomes
if (getNumber (configFile >> "someClass" >= true)) then {...};
private _value = player getVariable [QGVAR(showHud), false];
// becomes
private _value = player getVariable [QGVAR(showHUD), false];

hemtt utils verify

Check a .bisign file against a public key and PBO

Usage: hemtt utils verify [OPTIONS] <pbo> <bikey>

Arguments:
  <pbo>
          PBO to verify

  <bikey>
          BIKey to verify against

Options:

    -v...
        Verbosity level

    -h, --help
        Print help information (use `-h` for a summary)

This will verify a signed PBO against a public key. This is useful for verifying that a PBO is signed correctly.

It will check:

  • The authority matches
  • The PBO is correctly sorted
  • The hashes match
  • A prefix property is present

Analysis

HEMTT will analyze your project for some common issues.

Preprocessor
Config
SQF

Preprocessor

HEMTT will provide warnings for common issues in your config, in both the preprocessing and rapifying stages.

Warning Suppression

Currently, HEMTT only allows the suppression of certain preprocessor warnings. To suppress a warning, use the following structure:

#pragma hemtt suppress { warning code } { scope = line }

The warning code can be one of the following:

CodeDescription
pw3_padded_argPadded argument in a macro call

The scope can be one of the following, if not specified, the scope will be line.

ScopeDescription
lineSuppresses the warning for the next line
fileSuppresses the warning for the remainder of the current file, not including includes
configSuppresses the warning for the remainder of the current config, including includes

Preprocessor Flags

HEMTT provides a few preprocessor flags to control the behavior of the preprocessor.

FlagDescription
pw3_ignore_formatIgnores padded arguments in ARR_N, WARNING_N, TRACE_N, FORMAT_N, etc. macros
pe23_ignore_has_includeAssume any #if __has_include is false

The scope of these flags is the same as the warning suppression scope.

Preprocessor Warnings

[PW1] Redefine Macro

This warning is emitted when a macro is defined more than once.

#define FOO 1
#define FOO 2

It may also appear when a macro is defined in a file that is included more than once.

// foo.hpp
#define FOO 1

// bar.hpp
#include "foo.hpp"
#include "foo.hpp"

[PW2] Invalid Config Case

This warning is emitted when config.cpp is not all lowercase, e.g. Config.cpp.

[PW3] Padded Argument

This warning is emitted when an argument to a macro is padded with spaces.

#define Introduction(var1, var2) var1, meet var2
HELLO(Jim, Bob)

This would produce Jim, meet Bob instead of Jim, meet Bob. (Note the extra space before Bob).

By default, all macros are checked, but a flag can be set to ignore ARR_N, WARNING_N, TRACE_N, FORMAT_N, etc. macros.

#pragma hemtt flag pw3_ignore_format { scope = line }

This file will be generated, do not edit it manually

This file will be generated, do not edit it manually