THM CLI
thm patch-bump changes or disables the bump declaration in an existing texture descriptor. Use it when moving an
imported texture or correcting a missing bump reference. Generate the texture files separately with
dds make-bump. thm bump-alignment checks whether each descriptor’s bump belongs to
its diffuse, which a texture pack installed in part breaks without any file going missing.
Repoint a bump declaration
Run from a gamedata root containing the descriptor and its replacement bump texture:
xrf-cli thm patch-bump --path ./textures/wpn/wpn_pm/wpn_pm.thm --to "wpn\wpn_pm\wpn_pm_bump" `
--dry-run
xrf-cli thm patch-bump --path ./textures/wpn/wpn_pm/wpn_pm.thm --to "wpn\wpn_pm\wpn_pm_bump"
Example report result — preview a bump-name change:
{
"isDryRun": true,
"originalSize": 138,
"patchedSize": 154,
"previousMode": 1,
"previousName": ""
}
Exit code: 0. This dry run changes no file. The report shows a 16-byte increase for the requested name.
The first command validates and reports the proposed change; the second rewrites the descriptor in place. The stored
name is relative to textures, uses backslashes, and omits the extension.
Use --dest to write a separate descriptor. --to changes the name while preserving the existing mode, including
use_parallax; it does not enable a descriptor whose mode is disabled.
Disable a missing bump
If the surface should have no bump map, clear the declaration:
xrf-cli thm patch-bump --path ./textures/tile/tile_walls_red_01.thm --off
--off sets the mode to none and clears the name, matching the form written by STextureParams. Give exactly one of
--to or --off: a command with neither or both is rejected as a usage error, exit 2.
How the engine resolves it
CTextureDescrMngr::LoadTHM reads the descriptor beside the texture. An active bump declaration uses the stored name;
the engine does not discover a map merely because a neighboring file ends in _bump.dds.
If that name resolves to nothing, the renderer still selects the bump shader path and substitutes ed\ed_dummy_bump,
logging ! Fallback to default bump map. The surface appears flat while retaining the bump rendering path. A copied
descriptor can cause this when it still points into the source project’s texture layout. In renderer_r4, this lookup
happens through CTexture::Preload.
Preservation and verification
The patcher rebuilds only the bump chunk and copies other chunks byte for byte. Before writing, it requires a rewrite of the existing declaration to reproduce the source exactly. After writing, it reads the requested declaration back. A failed read-back check triggers restoration of an in-place source or removal of a separate destination. This does not make an interrupted or failed filesystem write transactional; use a separate destination when the original must remain available.
From the directory containing the assembled gamedata tree, verify the resulting references:
xrf-cli gamedata verify --path ./gamedata --checks textures --report ./texture-report.json
Resolve remaining bump findings before checking the surface in game. Descriptor validation establishes the reference; it does not establish the visual quality of the map.
Check that bumps belong to their diffuses
A bump drawn for one picture and laid under another still resolves, so gamedata verify passes it, but the relief no
longer lines up with what the surface shows: cracks and rivets light where the picture has none. This happens when a
texture pack replaces a diffuse and not its _bump, or the reverse. Compare every set an installation declares:
xrf-cli thm bump-alignment --path ./stalker-anomaly --report ./alignment.json
Example output — stock Anomaly:
Comparing the bumps texture descriptors declare with their diffuses
7132 descriptors, 2758 declaring a bump; 2661 sets compared, 2 missing a diffuse, 95 missing a bump, 0 unread
2241 aligned, 173 doubtful, 223 mismatched (98 with their own bump), 24 featureless; 1098 borrow another's bump
Exit code: 0. It is a report rather than a check: the verdict is a measure’s judgement, not a rule the engine
enforces.
Each set is compared by its edges: where the diffuse’s colour changes against where the bump’s relief changes, texel for
texel, at up to 512 texels a side. The same comparison with the bump shifted along the tile gives what any two textures
share by chance, and the verdict judges how far the set rises above it: aligned, doubtful, mismatched, or
featureless when either texture carries nothing to compare. A set whose bump is its diffuse’s own <texture>_bump and
reads mismatched is the one to look at, and the console names each. A descriptor can also borrow another texture’s
bump on purpose, a dirtier variant reusing the clean brick’s relief; isBorrowed marks those in the report, and they
often follow their diffuse only loosely.
Layer a pack over the game it modifies to check the result as the game sees it, highest priority first:
xrf-cli thm bump-alignment --path ./texture-pack ./stalker-anomaly --source auto --report ./alignment.json
--prefix textures\wpn limits the check to the descriptors of one folder; their bumps are still found wherever they
are.
Command reference
xrf-cli thm bump-alignment
Report whether each texture’s bump belongs to its diffuse, by how closely the bump’s slopes follow the diffuse’s edges
xrf-cli thm bump-alignment [OPTIONS] --path <path>...
| Option | Required | Default | Description |
|---|---|---|---|
-p, --path <path>... | yes | Root holding the textures and their descriptors. Repeat to layer roots, highest priority first | |
--source <source> | containing-installation | How to read the path: auto treats it as an installation only when it declares one, directory ignores any declaration, volumes mounts every archive volume beneath it, installation requires one, containing-installation searches parent directories for one. Possible values: auto, directory, volumes, installation, containing-installation. | |
--prefix <prefix> | Limit to the descriptors of one logical subtree, such as textures\wpn | ||
-s, --silent | Turn off logging | ||
-v, --verbose | Turn on verbose logging | ||
--json | Write the run’s JSON report to stdout, moving human output to stderr | ||
--report <PATH> | Write the run’s JSON report to a file |
xrf-cli thm patch-bump
Repoint the bump texture reference of a THM file
xrf-cli thm patch-bump [OPTIONS] --path <path> <--to <to>|--off>
| Option | Required | Default | Description |
|---|---|---|---|
-p, --path <path> | yes | Path to thm file | |
-d, --dest <dest> | Path to resulting thm file, defaults to in place rewrite of the source file | ||
--to <to> | Bump texture reference to write, engine style without extension, for example ‘wpn\wpn_pm\wpn_pm_bump’ | ||
--off | Declare no bump at all, clearing the mode and the name; use for a bump that does not exist and is not going to | ||
--dry-run | Validate the change and report the result without writing any file | ||
-s, --silent | Turn off logging | ||
-v, --verbose | Turn on verbose logging | ||
--json | Write the run’s JSON report to stdout, moving human output to stderr | ||
--report <PATH> | Write the run’s JSON report to a file |