Skip to content

POSTAL 2

Platform ID com.runningwithscissors.postal2

DigitalHeaven.Mods.Postal2 Prototype replaces the Dude with a DigitalHeaven avatar in POSTAL 2 (build 5100, an early hybrid Unreal Engine 2). It is an UnrealScript mod with a small native library behind it. Nothing of the game’s is replaced.

GamePOSTAL 2, build 5100 (System\Postal2.exe, InstalledVersion=5100)
Mod hookWorkshop P2GameMod (a mutator)
PlatformStatus
Windows🟡 Prototype
Linux (Proton only)❔
macOS (Proton only)❔
Share the Pain (build 1409)❌
Runtime modExporter
RolePrimaryFallback
AvatarsAny workspace avatar, picked in the gameAvatars exported ahead of time
Package builtIn the game process, on pickBy the DigitalHeaven.Platforms.Postal2 CLI, with UCC or --writer csharp
Read throughDHRuntimeDHManifest.txt
Needs UCCNoYes, unless --writer csharp

The mod asks the runtime first, then the manifest. Everything after that (state, picking, limbs, the settings page, saves) is the same code for both. Without DHRuntime.u the mod is the exporter mod.

Picking an avatar builds its package in the game process, off the game thread.

  1. The picker lists the workspace’s avatars. A pick sends build to DHRuntime.
  2. Reference skeletons are read from the player’s own Characters.ukx and weapon .ukx files: the Dude and 19 limb skeletons.
  3. Load workspace: the avatar’s pallet is read.
  4. Fit: the avatar is fitted onto the reference skeletons.
  5. Textures are converted.
  6. C# writer: a DHRt<hash> package is written in memory (<hash> is 16 hex of its SHA-256).
  7. A KERNEL32 file hook answers the engine’s open of ..\System\DHRt<hash>.u from memory.
  8. The engine loads the package like any other, and the mod swaps the body.
picker -> reference skeletons -> load workspace -> fit -> textures -> C# writer
(pick) 50 ms 700 ms 1.4-1.6 s 270 ms 210 ms
|
engine loads package <- KERNEL32 file hook serves bytes from memory <+
(DHRt<hash>.u, 14.3 MB, never written to disk)

Measured for mltn Taidum (build 5100, suburbs-3):

StageTime
Reference skeletons50 ms
Load workspace700 ms
Fit1.4 to 1.6 s
Textures270 ms
C# writer210 ms
Total2.6 to 2.9 s

The package is 14.3 MB. Nothing is written to disk and nothing is cached. A changed avatar is a new content-hashed package under the same choice. Builds run one at a time on their own thread. A frame hitch was not measured.

Four files go into the game’s System folder. Nothing of the game’s is replaced, and no RWS data ships: the reference skeletons are read from the player’s own install.

FileHolds
DigitalHeaven.uThe mod (script package)
DHRuntime.uDHRuntimeBridge, the one native function
DHRuntime.dllUE2 native package; forwards to DHRtCore.dll and installs the file hook
DHRtCore.dllNativeAOT win-x86 library: workspace, fit, textures, package writer

native\install-runtime.bat and uninstall-runtime.bat copy exactly these and refuse while the game runs.

Peak working set while buildingabout +360 MB (1692 MB with the build, 1332 MB without)
Resident after the build1537 MB

The game is a 32-bit process, so the peak matters. Reduction work is in progress.

  1. Start POSTAL 2.
  2. New Game > Workshop… > Mods > DigitalHeaven, then Start.

Plain New Game strips mutators and does not load the mod.

FeatureStatus
Body swap (with mirrors)🟡 Prototype
First-person hands on 19 limb skeletons🟡 Prototype
First-person kick🟡 Prototype
Settings page and DigitalHeaven.ini🟡 Prototype
Spring bones❌
Overlay❌

Replaces the Dude’s mesh and skins, in third person and in mirrors. The fit keeps the Dude’s bone names, hierarchy and animations, and by default writes the avatar’s own proportions into the skeleton. A late swap restarts the Dude’s idle so the avatar does not stay in the bind pose.

The avatar’s hands are drawn over the held weapon on 19 limb skeletons, one per weapon family. The kick draws the avatar’s foot. Each avatar’s package carries its own limb meshes.

Reach it with Escape > Options > DigitalHeaven, or mutate dhmenu. The Options list on the main menu is not reachable (no mod runs there).

SettingValuesStatus
AvatarEvery workspace avatar, or Dude (default)✅ via .ini, ❔ via the page
Replace Bodyon, off✅
Replace First Person Handson, off✅
Replace First Person Kickon, off✅
Level of DetailFull Detail, Automatic🟡 the runtime writer emits one LOD

Choices are written to System\DigitalHeaven.ini on the first change:

[DigitalHeaven.DHSettings]
Avatar=DHAvatar_MltnTaidum
bReplaceBody=True
bReplaceHands=True
bReplaceKick=True
bFullDetail=True

An empty Avatar takes the manifest’s active avatar. Restore Defaults clears the choices. Taking the avatar off restores the Dude captured before the first swap in the level.

Type each with mutate:

CommandDoes
dhstatusLogs which avatar is worn
dhreloadReloads the manifest and avatar
dharmsLogs arm bone data
dhheadLogs head bone data
dhbonesLogs the bone table and whether the game applies the avatar’s bone lengths
dhhandsLogs one frame of hand placement against the held weapon
dhglowdhglow <0 to 255> sets the hands’ ambient light
dhmenuOpens the settings page
dhnoclipToggles DigitalHeaven noclip (the key V when it was free, and a button on the Tools page)

Noclip flies the player through everything with the look direction: jump climbs, crouch descends, and the walk key (Shift by default) flies faster. The flight is Core’s NoclipFlight, the same step the engine and the other game mods run, with its default speed (12 m/s, 2.5 times that with the walk key). The runtime library runs one step per frame through the native bridge (about a microsecond a call), and the mod reads the held movement keys through the player’s own binds (the game’s input axes read zero outside the player’s own tick, so a key held when noclip starts counts at once), converts meters to Unreal units (88 per meter, the avatar scale), and moves the pawn by the answered velocity with physics and collision off. The numbers cross as whole millionths, since UnrealScript prints a float with two decimals and would round a frame’s time to zero. Noclip needs the runtime.

Switching it off puts the collision back at once, where the player is, inside geometry or not, as Source’s noclip does. Noclip never reaches a save: it ends for the save and comes back after.

The V key is bound the way the menu keys are: only when free, never over another binding, logged, and removed when the Menu Key setting is off or the runtime is uninstalled.

lods, fit and proportions resolve in this order, each layer overriding the one before:

  1. Built-in default
  2. dh-config.jsonc, platforms.postal2
  3. The avatar’s override, platforms/postal2/<avatar>.jsonc (or under the ID com.runningwithscissors.postal2)
  4. User force (CLI flag, or the settings page for Level of Detail)
KeyValuesDefaultEffect
lodson, offoffoff keeps the full mesh at every distance
fitheight, hipsheightheight scales to the Dude’s total height with the hip cap
proportionsown, referenceownown writes the avatar’s own bone lengths into the skeleton
KeyRuntime modExporter
lods🟡 single LOD from the C# writer✅ with UCC (LOD 1 to 3 are one bone per vertex)
fit✅✅
proportions✅✅
ItemState
Finger fit🟡 approximate on some weapons (chainsaw and dusters worst)
Tail🟡 rigid: the Dude has no tail bones
Patterned fur🟡 flattened to a tint
LODs from the C# writer🟡 one LOD (collapse data is zero; pops would be the first suspect)
Share the Pain❌ needs the 1409 script and native builds
Workshop delivery of the DLLs❌ files are installed by hand
Linux and macOS❔ Proton only, untested
Settings page avatar picker in game❔ the same path was driven through the .ini
Saves with a runtime avatar❔
Limbs with runtime avatars❔