Decompiling Killing Floor Packages with UT Package Tool: Errors and Fixes
Decompile to learn, to fix abandoned mods, or with the author's permission. Credit the original author in anything you publish.
This is an adaptation of kazachidla's Steam guide on decompiling scripted Unreal Engine 2.5 code for Killing Floor. It uses UT Package Tool 2.0 beta 5 (UTPT), which recovers code from packages whose source text was stripped. UTPT needs much more manual work than UE Explorer, but the author found it more reliable with braces and block boundaries. The second half of the page is the list of compile errors UTPT leaves behind and how to fix each one.
Tools
- UT Package Tool 2.0 beta 5: the decompiler.
- A hex editor (the guide uses HexEdit): to make UTPT accept a KF package.
- UnCodeX (optional): browses and searches the KF script source, which comes with the Killing Floor SDK. You will need it to look up correct type names.
Step 1. Export defaultproperties with ucc
UTPT decompiles default values with errors, so take them from the game's own exporter first. Create a .bat in System:
ucc batchexport Mut.u class uc ..\Mut\Classes
pause
Replace Mut with the package name. Run it and copy the resulting .uc files to a separate folder, for example Default. You only need their defaultproperties blocks.
Step 2. Decompile in UTPT
- Open the original
.uin a hex editor and change the first byte fromC2toC1. UTPT does not know Killing Floor; after this it treats the file as an Unreal Tournament 2004 package. Save. - Open the patched file in UTPT. In Export Tree, right-click any class and choose Full Collapse.
- In File → Options, enable Add line offsets to decompiled code. You will need the offsets to place labels.
- For every class: right-click → Decompile, then Save in the right pane. Save into
<Package>\Classesin the game root, e.g.Mut\Classes. - Open each saved
.ucand replace itsdefaultpropertieswith the block from theDefaultfolder. - Clean up the class declaration. UTPT adds specifiers the compiler rejects:
class AntiBlocker extends Mutator
Localized
HideCategories(Movement,Collision,Lighting,LightColor,Karma,Force);
becomes
class AntiBlocker extends Mutator;
Then compile with ucc make and work through the errors below.
Compile errors UTPT leaves behind
Unrecognized type 'FPerksListType'
UTPT declares a variable before the struct it uses:
var array<FPerksListType> CachePerks;
struct FPerksListType
{
var Class<KFpubVet> PerkClass;
var byte CurrentLevel;
};
Move every struct to the top of the file, right after the class declaration, before any variable that uses it.
Missing '>' in 'class limitor'
local array<Class<KFpubVet>> ca;
The UE2 parser reads >> as the shift operator. Add a space:
local array<Class<KFpubVet> > ca;
Unrecognized type 'ServerResponseLine'
UTPT drops the outer class from types declared in other classes:
function GetServerDetails (out ServerResponseLine ServerState)
Qualify it:
function GetServerDetails (out GameInfo.ServerResponseLine ServerState)
Search the type in UnCodeX to find where it is declared. Common cases: PlayerRecord → xUtil.PlayerRecord, ServerFavorite → ExtendedConsole.ServerFavorite, AllowWeaponInTrader → Static.AllowWeaponInTrader. Missing Static. on static calls is something you will fix often.
'Extends' not allowed here: state overrides version in parent class
auto state PendingMatch extends PendingMatch
The state name got duplicated. Drop the extends part:
auto state PendingMatch
Specified type modifiers not allowed here
function bool ButtonClicked (export editinlineuse GUIComponent Sender)
local export editinlineuse GUIList List;
Remove export editinlineuse from parameters and local variables:
function bool ButtonClicked (GUIComponent Sender)
local GUIList List;
Type mismatch in 'new'
C = new (None,Class'KFpubLC');
Correct syntax:
C = new (None) Class'KFpubLC';
Label 'JL0021' not found in this block of code
UTPT turns every loop into goto but almost never places the labels, and when it does they are usually wrong. Use the line offsets you enabled in step 2: the label name is the target offset, so goto JL0021 jumps to the line whose offset ends in 0x0021.
i = PC.Player.LocalInteractions.Length - 1; // 0x00000019 : 0x0000
JL0021: if ( i >= 0 ) // 0x00000032 : 0x0021
{
if ( PC.Player.LocalInteractions[i].Class == Default.Class )
return;
--i;
goto JL0021; // 0x00000066 : 0x0065
}
The label must be in the same function as the goto. Better still, rewrite it as the loop it was (while (i >= 0)).
Missing 'Reliable' or 'Unreliable'
un?reliable if ( (Role == 4) && bNetOwner )
UTPT could not tell which one it was. reliable guarantees delivery; unreliable may be dropped on a bad connection. Most replication conditions in mods are reliable. If the stock parent class has the same block, copy its keyword.
'If' is not allowed here (numbers instead of enum names)
if ( Level.NetMode == 3 )
UTPT replaces enum names with their numbers. The compiler sometimes accepts them, but not everywhere. Put the names back: Level.NetMode == NM_Client. Tables for the most common enums:
| Value | NetMode | Role | ClientState (weapon) | ClientGrenadeState |
|---|---|---|---|---|
| 0 | NM_Standalone | ROLE_None | WS_None | GN_None |
| 1 | NM_DedicatedServer | ROLE_DumbProxy | WS_Hidden | GN_TempDown |
| 2 | NM_ListenServer | ROLE_SimulatedProxy | WS_BringUp | GN_BringUp |
| 3 | NM_Client | ROLE_AutonomousProxy | WS_PutDown | |
| 4 | ROLE_Authority | WS_ReadyToFire |
| Value | Physics | Value | Physics |
|---|---|---|---|
| 0 | PHYS_None | 9 | PHYS_Spider |
| 1 | PHYS_Walking | 10 | PHYS_Trailer |
| 2 | PHYS_Falling | 11 | PHYS_Ladder |
| 3 | PHYS_Swimming | 12 | PHYS_RootMotion |
| 4 | PHYS_Flying | 13 | PHYS_Karma |
| 5 | PHYS_Rotating | 14 | PHYS_KarmaRagDoll |
| 6 | PHYS_Projectile | 15 | PHYS_Hovering |
| 7 | PHYS_Interpolating | 16 | PHYS_CinMotion |
| 8 | PHYS_MovingBrush |
| Value | Sound slot (PlayOwnedSound) | DrawType |
|---|---|---|
| 0 | SLOT_None | DT_None |
| 1 | SLOT_Misc | DT_Sprite |
| 2 | SLOT_Pain | DT_Mesh |
| 3 | SLOT_Interact | DT_Brush |
| 4 | SLOT_Ambient | DT_RopeSprite |
| 5 | SLOT_Talk | DT_VerticalSprite |
| 6 | SLOT_Interface | DT_Terraform |
| 7 | DT_SpriteAnimOnce | |
| 8 | DT_StaticMesh | |
| 9 | DT_DrawType | |
| 10 | DT_Particle | |
| 11 | DT_AntiPortal | |
| 12 | DT_FluidSurface |
Most of these are declared in Actor.uc; check the source when in doubt.
'If' is not allowed here (missing state label)
The same error has a second cause. UTPT does not place labels in state code:
auto state RepSetup
{
if ( Level.NetMode == NM_Client )
{
stop
}
State code needs a Begin: label, and stop needs a semicolon:
auto state RepSetup
{
Begin:
if ( Level.NetMode == NM_Client )
stop;
Check every state for missing labels. If you skip this, the package may even compile without warnings, but the state code will not run as intended. The guide notes zeds that "act dumb" for this reason, because zed AI relies heavily on state code. Most mod states are rewrites of stock ones, so compare with the stock class.
goto inside state code
goto to a numeric label does not work inside state code, even with labels added. Rewrite the jumps as while and for. Decompiled (does not compile):
SendIndex = 0;
if ( SendIndex < ShopInventory.Length )
{
ClientReceiveWeapon(SendIndex, ShopInventory[SendIndex].PC, ShopInventory[SendIndex].CatNum);
Sleep(0.1);
++SendIndex;
goto JL007C;
}
Rewritten:
for( SendIndex=0; SendIndex<ShopInventory.Length; ++SendIndex )
{
ClientReceiveWeapon(SendIndex, ShopInventory[SendIndex].PC, ShopInventory[SendIndex].CatNum);
Sleep(0.1f);
}
The guide's full example rebuilds ServerPerks' ClientPerkRepLink.RepSetup this way. Whenever the decompiled code resembles a stock or well-known class, use the original as the reference.
How long it takes
A single weapon, zed or skin can be done quickly. A whole server package can take several days to a week of careful work. The stock source covers nearly every variable and function you will need to look up.
Animations are a separate problem. If an author packed meshes into a large .ukx with the mesh data stripped, it cannot be unpacked, and the guide's author knew no fix. You can still ship the whole package, or try to delete unused content in the KF SDK, but the SDK refuses to delete a mesh that a class in the same package references.