Terminal Reality POD archive utility for Windows.
KPod opens, browses, previews, extracts and builds the POD archives used by
Monster Truck Madness 1 & 2, CART Precision Racing, Hellbender, Terminal
Velocity, Fury3, Nocturne, 4x4 EVO 1 & 2 and their relatives. It reads and writes both the classic POD1
format and the Community Patch 3 POD1-64 extension with 64-byte entry
names. It also reads and explicitly authors POD2; EPD remains read-only.
It started as a Windows-native port of
JPod, the Java 17 original, and matches
it byte for byte: the same archives, the same .inf and .lst reports, the same
format names.
Browsing CRAZY98.POD while previwing a BIN model
| Format | Games | Support |
|---|---|---|
POD1 |
MTM 1 & 2, CPR, Hellbender, Terminal Velocity, Fury3 | browse, preview, extract, save |
POD1-64 (Extended POD1) |
Community Patch 3 content | browse, preview, extract, save |
POD2 |
Nocturne, 4x4 Evo 1 & 2 | browse, preview, extract, explicit save/conversion, CRC, timestamp and audit history |
EPD |
Fly! | browse, preview, extract |
- Open
.podand.epdarchives and browse them in a file-viewer style list with Name / Size / Description columns. - Folders and subfolders are shown with icons and start collapsed, so root files and top-level folders are easy to scan.
- Click a column header to sort by name, size or description; click again to reverse it, and a third time to return to archive order.
- The filter box narrows the list as you type, and it overrides collapse so a match is never hidden. The Advanced button opens a search dialog that jumps the list to a result.
- The 80-character archive comment is editable below the toolbar.
- The 10 most recently opened files are remembered in
%APPDATA%\KPod\config.json. - Preferences are read the first time something needs them rather than at launch, so starting up costs no disk read at all.
- Extract All writes every entry to a folder, recreating the archive's subfolder structure.
- Extract Sel. extracts the selection, with an option to preserve or flatten the folder structure. Selecting a folder heading selects everything under it.
- New Archive starts an empty archive.
- Open Response List File loads the existing
.lstsyntax or a PODTool-style.rspwithpodFilename:,volumeName:,//comments and relative paths. - Add Files / Add Folder appends files or recursively imports a directory.
- Folder editing creates folders, renames file or folder trees, and moves selections by command or internal drag and drop while preserving case and order.
- Remove deletes the selection.
- Replace with File swaps one entry's data, keeping its archive name.
- Disable / Enable in Game hides a track or truck from the game the way
CommPatch 26 does, by renaming it to an extension the engine does not recognise:
.trkbecomes.trx,.sitbecomes.six, and.si2becomes.siy. Nothing is deleted and nothing is re-encoded, so enabling puts the addon back exactly as it was. The item only appears for a selection that has something to rename. Changing a track pod's status can trigger the multiplayer Different Version message, which the status bar says after each rename. - Validate Archive checks limits, field capacity, unsafe and duplicate paths, payload ranges, RAW palette records, checksums and the expected output layout.
- Save / Save As saves a named archive or writes a copy. POD1 is the default; POD2 is an explicit target and the actual output format is shown before writing.
- Save .inf exports the fixed-column report: file name, total size, entry count, comment, and a padded name / size / offset table.
- Save .lst exports a plain entry-name list, ready to feed back into the response-list loader.
Double-click any entry, or use Preview from the right-click menu:
| Extension | Preview |
|---|---|
.raw, .clr |
8-bit paletted image with a live palette selector and Save as BMP. Art textures (64x64) are drawn at 4x with no smoothing; unknown sizes open a dimensions dialog. |
.act |
16x16 colour swatch grid; hovering a swatch reports its index and hex value. |
.wav |
Audio player with play, pause/resume, stop, seek bar and formatted time display. |
.mod |
Standard tracker-module playback with the same transport and seeking, once the optional MOD playback add-on is installed. |
.png |
Alpha-preserving PNG preview over a transparency checkerboard. |
.tga |
Uncompressed or RLE 24/32-bit true-colour Targa preview, including alpha and image-origin flags. |
.bin |
Native OpenGL 3.3 model viewer with textures, normal maps, material effects, lighting/wireframe/grid controls, diagnostics and clickable texture thumbnails. An animated BIN opens on frame 1 with a banner naming the frame and where it resolved from; Play or the A key steps through the frames. |
.bmp, .jpg, ... |
Standard images through GDI+. |
.txt, .def, .nav, .lvl, .sit, .si2, .six, .siy, .trk, .trx, .txv, .lst, .ini, .cfg, .tex, .tnl, .ttx, .trn, .ndx and other text formats |
Scrollable monospaced text. |
| anything else | Hex dump of the first 4 096 bytes. |
For .raw and .clr entries the palette is resolved in this order:
- the palette recorded in the entry's own POD directory field (see below)
- the same base name with an
.actextension, in the same archive directory METALCR2.ACTanywhere in the archiveVGA.ACTanywhere in the archive- the bundled MTM1
metalcr2.act
The archive's own METALCR2.ACT outranks the bundled copy because METALCR2 is not
one palette: CPR ships a different one from MTM1 and MTM2, so the copy the pod
carries is the one known to match its art. A VGA.ACT in the pod says the archive
belongs to one of the flight games, and since nothing at this point tells Terminal
Velocity from Fury3 or Hellbender, that copy is the only one that can be trusted.
An .act whose name matches nothing is never guessed at. The preview selector
offers those last, along with the bundled CPR, Hellbender and Terminal
Velocity/Fury3 palettes and greyscale, and none of them is ever chosen
automatically. The last manual choice is remembered when another RAW offers the
same option.
The BIN viewer follows the same rules. Its selector supplies the fallback for
model textures that have no same-name .act of their own, so it is ranked from
the point of view of a texture that has none: another texture's palette is a
guess, and METALCR2 is not.
Step 1 is the one that matters for MTM1, Terminal Velocity, Fury3 and Hellbender.
Their packer stored the palette each RAW was authored against in the spare bytes
of the entry's name field, and it is not derivable from the file name: on those
games' main archives a name-based guess picks the wrong palette for 8 221 of 8 224
RAW entries. MTM2, CPR and community archives carry no such record and fall
straight through to the name-based rules. docs/POD_FORMAT.md
specifies it in full.
When a RAW payload is not one of the recognized sizes, the preview dialog offers the exact width and height pairs that match the byte count, a swap button, and every palette in the archive. The three common screen sizes are recognized and opened directly:
64000bytes:320 x 200256000bytes:640 x 400307200bytes:640 x 480
BIN previews use a native OpenGL 3.3 context. Drag with the left mouse button to orbit, use the mouse wheel to zoom, the left/right arrow keys to strafe, and Reset view to refit the model. The toolbar controls textures, wireframe, grid, smoothing, lighting direction and background colour. Classic transparency and Extended BIN alpha, blend, additive, two-sided, depth-write, tint, emissive, specular, normal-strength and TEXSOLID material states are applied by the GPU.
Textures are found in ART, MODELS, DATA, TEXTURES, then the archive root
and title fallback, with PNG preferred over TGA and RAW. _N.PNG and _N.TGA
normal maps use the games' DirectX/green-down convention. A same-name ACT remains
authoritative for a RAW texture; unresolved RAW textures share the selectable
metadata/archive/bundled fallback palette. The bottom strip reports missing or
non-standard assets, and clicking a resolved thumbnail opens it in the normal
image preview. The surface picks its pixel format by enumerating what the device
actually publishes rather than by handing ChoosePixelFormat an ideal descriptor,
which on a software renderer can score a plain GDI format as the closest match and
fail several calls later with no explanation. Systems without an OpenGL 3.3-capable
display driver show a recoverable explanation instead of closing KPod, naming the
renderer and OpenGL version the driver reports.
On a machine whose display driver publishes no usable OpenGL (ARM VMs, etc), putting a software
implementation's opengl32.dll, Mesa's llvmpipe build for example, next to KPod.exe
replaces the system renderer for this process. KPod loads it by explicit full path
before its first OpenGL call, so any companion the renderer needs resolves from that
same folder. There is nothing to install, no registry entry, and no effect on any other
program. Nothing ships with KPod and nothing is required: with no such file present the
system renderer is used, and the BIN preview's diagnostics line names whichever one is
in play. It costs frame rate in exchange for working at all, which is why it is opt-in.
Copy the whole renderer package, not just opengl32.dll. Mesa's WGL build is a loader
that needs its companions, libgallium_wgl.dll among them, in the same folder, and it
has to be the 32-bit build because KPod is a 32-bit program. A file that is present but
cannot be loaded is reported with the reason rather than ignored.
Pixel formats are enumerated through the loaded opengl32.dll's own wgl exports when
a drop-in renderer is in use, and through GDI otherwise. GDI's ChoosePixelFormat
family answers for the system display driver, so it reports nothing usable about a
replacement renderer's formats; presentation goes back through whichever of the two the
format came from.
Adds the open archive to the game's pod.ini mount list. KPod treats 99 as the
recommended limit, warns when the list is already that long, and still mounts the
POD rather than blocking. It searches the archive's folder and its parent, and
refuses to mount the same POD twice.
The .NET Framework build turns on multicore JIT at launch. The first run records
which methods starting up actually compiles, into
%LOCALAPPDATA%\KPod\startup.profile; later runs replay that list on a background
thread so the JIT works ahead of the code rather than being asked for one method at a
time. It is best effort, so a machine that will not let the profile be written simply
starts the way it did before. Deleting the file costs one slower start and nothing
else.
This is the Framework build's substitute for the ReadyToRun precompilation the .NET build gets at publish time. NGen would do more, but it needs an installer, and KPod is meant to be copied rather than installed.
Opening an archive reads its directory, not its contents. A POD directory is a few kilobytes even when the archive is hundreds of megabytes, so entry payloads stay on disk until a preview, an extract or a save actually asks for one. The practical effect is that archive size barely affects how long an archive takes to open, or how much memory holding it open costs.
Measured on the .NET 10 build, opening these archives and then browsing, sorting and
filtering all 4341 entries of Hellbender's GAME.POD:
| Archive | Size | Entries | Open | Held after open |
|---|---|---|---|---|
UI.pod (Community Patch 2) |
192 MB | 462 | 2.2 ms | 130 KB |
GAME.POD (MTM1) |
51 MB | 2789 | 1.0 ms | 709 KB |
GAME.POD (Hellbender) |
41 MB | 4341 | 1.3 ms | 1.1 MB |
FURYSE.POD (Fury3) |
31 MB | 1330 | 0.5 ms | 346 KB |
On Hellbender's GAME.POD, expanding every folder takes 2.5 ms, sorting the whole
list by description 3.0 ms, four filter keystrokes 5.2 ms, and selecting a 3420-entry
folder and mapping the selection back to rows 2.6 ms.
Saving streams too. The writer plans the directory from names and lengths, then
copies each payload through a single 64 KB buffer into a temporary file beside the
target and swaps it in, so peak memory for a save is the directory plus that buffer
regardless of archive size. Rewriting the 192 MB UI.pod in place takes 120 ms, and
because the new archive is complete on disk before it replaces the old one, a failed
save cannot damage the original.
POD1-64 is the Community Patch 3 long-name extension. It is not a 64-bit archive format and it is not EPD: the 64 refers only to the widened directory name field.
| Property | Classic POD1 | POD1-64 |
|---|---|---|
| Header | 84 bytes | 84 bytes |
| Directory name field | 32 bytes | 64 bytes |
| Longest name | 31 bytes | 63 bytes |
| Directory record | 40 bytes | 72 bytes |
Directory entry i starts at |
84 + i * 40 |
84 + i * 72 |
POD1 has no magic value, so KPod detects the layout by validating it. The classic
40-byte directory is tried first and accepted only when every record decodes to a
plausible non-empty path whose byte range lies inside the file; the 72-byte layout
is tried only if the classic table fails. That ordering keeps an ordinary archive
from being reported as extended. The title bar shows Extended POD1 when the
wider directory was used.
For a new archive, KPod emits classic POD1 whenever every complete directory field fits and promotes to POD1-64 only when a path or embedded RAW palette record needs the wider field. An opened POD1-64 archive stays extended. The complete record, including both strings and their terminators, must fit; otherwise saving is rejected instead of truncating metadata. The actual output format is confirmed before writing.
The upstream engine notes describe production extended archives as carrying a version-tagged header, but the contract that would define the tag has never been published. KPod, JPod, JSPod and JSTruckViewer all implement the untagged 84-byte-header interpretation and validate it structurally.
Windows, and nothing else. KPod runs on .NET Framework 4.8, which is part of Windows 10 (from the April 2018 update) and Windows 11, so there is nothing to install. It also runs on Windows 7 SP1 and 8.1 if 4.8 is present.
A second build targeting .NET 10 is published for anyone who prefers it. That one needs the .NET 10 Desktop Runtime. The two are identical in behaviour; pick whichever suits the machine.
Prebuilt binaries are on the
Releases page. Download
KPod.exe and run it. No installer, no dependencies, one file. The .NET
Framework 4.8 build is the one to take unless you specifically want the .NET 10
build, which is published alongside it.
- Open..., or drag a
.podonto the exe, or pass a path on the command line. - Double-click a folder row to expand it, or use Expand + to open everything.
- Double-click a file to preview it.
- Select entries and press Extract Sel., or Extract All for the lot.
Note
The executable is unsigned, so Windows SmartScreen will warn on first run.
Choose More info then Run anyway. If you copied the file from another
machine you may also need Unblock-File .\KPod.exe in PowerShell.
Actions worth naming carry a label, the rest are icon-only with a tooltip, and the ones that come in families sit behind a split button whose face runs the most common of them. Everything here is also in the menus, so no action is reachable only through a dropdown.
| Control | Face runs | Arrow opens |
|---|---|---|
| New | New archive | New Archive, Open Response List File |
| Open | Open a POD or EPD archive | Open POD, Open Response List File, recent files |
| Save | Save over the open archive | |
| Save As | Write the entry list to a new archive | |
| Add | Add Files | Add Files, Add Folder, Create Folder |
| Extract | Extract Selected | Extract Selected, Extract All |
| Remove | Drop the selected entries from the list | |
| Expand All / Collapse All | Open or close every folder | |
| Search | Open the search dialog | |
| About | Version and credits, at the right edge |
The icons are glyphs from Segoe MDL2 Assets, the icon font Windows draws its own command bars with, rendered at the monitor's DPI so they stay sharp when the window moves between displays. That font arrived with Windows 10; on Windows 7 SP1 and 8.1 the buttons fall back to plain text labels.
- File: Open POD, New Archive, Open Response List File, Open Recent, Add Files, Remove Selected, Save As, Extract All, Extract Selected, Save .inf Report, Save .lst List, Exit
- Tools: Mount in pod.ini, Search
- Help: About
Size is what the archive would occupy if saved now, which is why it changes as
entries are added and removed, and why it grows by 32 bytes per entry the moment a
name forces the POD1-64 directory. Files and Selected are counts, unsaved
appears once the list differs from what is on disk, and the square at the right
edge turns red while an operation is running.
Building needs the .NET 10 SDK, which compiles both targets. Running the result needs only what the table above says.
dotnet restore KPod.slnx
dotnet build KPod.slnx # builds net48 and net10.0 together
dotnet test KPod.slnx -f net10.0All three work on macOS and Linux as well as Windows. KPod.Windows sets
EnableWindowsTargeting, so the WinForms project compiles anywhere even though it
only runs on Windows. The test project also targets net48, but running that leg
needs a Windows machine or Mono; -f net10.0 is the portable way to run the suite.
To run the app on Windows:
dotnet run --project KPod.Windows -f net48Release builds:
# .NET Framework 4.8, the main download
dotnet publish KPod.Windows -c Release -f net48 -p:DebugType=none
# .NET 10, the alternative
dotnet publish KPod.Windows -c Release -f net10.0-windows -r win-x86 \
--self-contained false -p:PublishSingleFile=true -p:DebugType=noneBoth produce a single executable. The Framework build compiles KPod.Core
straight into the exe and uses Costura.Fody 6.2.0 to embed the managed Silk.NET
dependencies, since .NET Framework has no single-file publisher.
Nothing about MOD playback is inside the executable. See MOD playback add-on below.
The .NET 10 publish turns on ReadyToRun, which compiles the app's IL to native
code ahead of time so starting it does not have to JIT its own code first. It applies
automatically whenever a runtime identifier is given, which the command above does,
and it costs about 1.7 MB of executable size. Pass -p:PublishReadyToRun=false to
trade the startup back for the smaller file.
Do not add -p:PublishReadyToRun=true to the net48 command. ReadyToRun is a .NET
Core feature that does nothing for .NET Framework, and asking for it makes the SDK
pick a runtime identifier from whatever machine is doing the build, so publishing
from a Mac quietly produces an ARM64 binary in bin/Release/net48/osx-arm64/ that no
Windows PC will run. The project fails the build with an explanation if you try. The
.NET Framework build has no ReadyToRun equivalent short of NGen, which needs an
installer rather than an xcopy.
.wav playback is built in. Tracker modules need libopenmpt, which is not part of
KPod.exe: it is a separate download, the official libopenmpt 0.8.9 release for 32-bit
Windows. The repository ships it in mod-playback/, which is the
folder to zip.
To install it, copy five DLLs into the folder that holds KPod.exe:
libopenmpt.dll
openmpt-mpg123.dll
openmpt-ogg.dll
openmpt-vorbis.dll
openmpt-zlib.dll
That is the entire installation. Nothing is written to the registry, nothing is
copied under %LOCALAPPDATA%, and no network connection is used at any point.
Deleting the five files uninstalls it.
Opening a .mod entry is what triggers the load. KPod checks that all five files are
beside the executable, then loads libopenmpt.dll by explicit full path with
LOAD_WITH_ALTERED_SEARCH_PATH, which is what lets the four companions resolve out of
that same folder and nowhere else. If a file is absent, or Windows refuses to load
one, the audio player opens with its transport disabled and says MOD playback is
not installed, naming the first missing DLL. Everything else in KPod, WAV playback
included, is unaffected. Only a successful load is cached, so dropping the DLLs in and
reopening the entry works without restarting KPod.
The add-on carries its own licenses. When they are present beside the executable, About → Third-party notices appends them to the notices built into the exe, so the dialog describes what is actually installed.
Keeping libopenmpt out of the executable is what took the net48 download from about 9 MB to about 1.8 MB.
KPod is x86, on both target frameworks. One 32-bit binary runs on 32-bit Windows, on x64 under WOW64, and on ARM64 under emulation, so a single download covers every Windows PC. Being 32-bit is also what lets the MOD playback add-on be a single set of DLLs rather than three.
PlatformTarget is set in KPod.Windows.csproj, so the net48 command above needs no
extra flags. The .NET 10 build additionally needs the matching runtime identifier,
because that is what lets it be a single file and be precompiled; -r win-x86 is the
only supported value and any other one fails the build.
Almost all of the code is shared, with one #if for WinForms startup and one for
a folder-dialog property. Where .NET Framework lacks something, KPod.Core/Compat
supplies it rather than the calling code branching: Index, Range, records
support, path helpers, Latin-1 lookup, JSON, and Java-compatible trimming. The
Windows project uses Silk.NET's OpenGL bindings for BIN previews; Costura embeds
those dependencies into net48 and the .NET single-file publisher handles the
modern builds.
| Project | Target | Purpose |
|---|---|---|
KPod.Core |
net48, net10.0 |
Archive reading and writing, image and palette decoding, reports, manifests, pod.ini, preferences, and the folder-browser model. No Windows API, so it runs anywhere |
KPod.Windows |
net48, net10.0-windows |
WinForms UI, plus the Windows-only pieces under Platform/ (MCI audio) |
KPod.Tests |
net48, net10.0 |
Portable xUnit tests for KPod.Core |
KPod.Windows.Tests |
net48, net10.0-windows |
Windows-only decoding, PCM, seeking, lifecycle and audio-UI tests, plus the add-on probe. Builds copy mod-playback/ into the test output so the decoder tests have a libopenmpt to load |
KPod.Core must never reference System.Drawing or System.Windows.Forms. The
RAW decoder returns int[] pixels and only KPod.Windows turns them into a
Bitmap; that one rule is what keeps the test suite portable.
KPod.Windows/Assets/build-icon.sh rebuilds KPod.ico from the two SVGs using
stock macOS tools. The full artwork is used at 48px and above; the small entries
use the K-only variant, because the slabs turn to mush below 32px.
docs/POD_FORMAT.md specifies the whole POD family: classic
POD1, the POD1-64 long-name extension, POD2 and EPD. It covers the palette record
the early Terminal Reality packer stored in the spare bytes of each RAW entry's
name field, which is documented nowhere else, and it marks every claim as verified
against shipped archives, documented upstream, implemented but unconfirmed, or
unresolved. docs/pod-format.json carries the same
structures as machine-readable data.
- JPod - the Java 17 original
- KPodman - the POD mounting utility this shares its build with
- MTM2 Engine Content Limits
- EPD Format Reference
- Pod 2 Format Reference
Developed by Juan Pablo Utreras for the Monster Truck Madness Guild.
Based on the original WinPod by MDMRE.
Licensed under the Apache License 2.0.
Monster Truck Madness, Terminal Velocity, Fury3, Hellbender, CPR, Nocturne and 4x4 Evo are trademarks of their respective owners. This is an unofficial, community-made utility with no affiliation to Microsoft or Terminal Reality.
