The .uo Format

A .uo file is one extracted object rendered as pseudo-UnrealScript. It's the file you edit, and the only input pack-mod reads.

Anatomy

// ue3-tools  pkg=Interface.upk  p_ver=9411  export=#312
// path: Texture2D UI_Menu.Backgrounds.MainMenu
// net_index: 1044

Texture2D MainMenu {
    SizeX = 1024   // int
    SizeY = 512   // int
    Format = EPixelFormat::PF_DXT5   // EPixelFormat
    LODGroup = TextureGroup::TEXTUREGROUP_UI   // TextureGroup
    TextureFileCacheName = 'CharTextures'   // name
    UnpackMin = [0.0, 0.0, 0.0, 0.0]   // array<float>

    @native(Texture2D) {
        mips = [
            { size = 1024x512, source = tfc('chartextures'), bytes = 524288, flags = 0x00000001 }
            { size = 512x256, source = inline, bytes = 131072, flags = 0x00000000 }
        ]
        tfc_guid = 0x1a2b3c4d_5e6f7081_92a3b4c5_d6e7f809
    }

    @sidecar = "MainMenu.dds"
}

Header comments

The three-to-four comment lines above the body are structured metadata, not decoration. The packer parses them to find the object again.

Line Meaning
pkg=<stem>.upk Which package this object came from. Used to group .uo files during packing. Required.
p_ver=<n> Package version at extraction time
export=#<n> 1-based export index — the fallback lookup if the path doesn't match
// path: <Class> <Pkg.Group.Object> The UE full name — the primary lookup key
// net_index: <n> Preserved and re-emitted; present only if p_ver ≥ 684

Don't edit the header

Changing pkg= or // path: changes which object gets overwritten. Delete a .uo you don't want packed rather than renaming it.

Body

<ClassName> <ObjectName> {
    <field> = <value>   // <type hint>
    ...
}

The // type suffix on each line is a hint for humans and is ignored on parse.

Values

Form Example
Integer 42, -7, 0x1f
Float 1.0, 3.14159, -0.5
Bool true, false
Null / empty object None
Byte 0x1f
String "text with \"escapes\" and \u{263a}"
Name 'SomeName'
Enum EnumName::ValueName
Object reference &ObjectName, &Outer.ObjectName, &extern:Package::ObjectName
Array [1, 2, 3] or one element per line inside []
Struct { Field = value, Other = value }
Opaque bytes @bytes(deadbeefcafe)

Arrays of up to eight scalars are written inline; anything longer or containing structs is written one element per line. Both forms parse identically.

Object references

References print as the shortest unambiguous label. If two exports in the package share a leaf name, the outer chain is prefixed. Imports are prefixed extern: with their top-level package.

On the way back in, a reference is resolved by exact dotted path first, then by unique leaf name. If it resolves to nothing, the packer reserves a new export slot and records it in .newexports.

Native block

@native(<Variant>) {
    ...
}

The variant is Texture2D, SwfMovie, SoundNodeWave, Raw, Empty, or NativeProps. Its contents are informational — the packer skips the whole block and reuses the original native tail bytes, unless a sidecar replaces them.

Raw blocks print a byte count and the first 64 bytes as a hex preview.

Native property fields

When a class has no registered serializer but its native tail could be decoded from CPF_Native properties, the fields are emitted inside the object body instead, under a marker comment:

Foo Bar {
    SomeTaggedProp = 1   // int

    // native-serialized (CPF_Native)
    Bounds.SphereRadius = 512.0   // float
    Bounds.Origin = { X = 0.0, Y = 0.0, Z = 0.0 }   // struct
}

The parser treats that comment as a mode switch — everything after it is routed to native fields, not tagged properties. Editing them is possible but re-encoding is only applied if it round-trips byte-identically; otherwise the original tail is kept and a note is printed.

Sidecars

@sidecar = "MainMenu.dds"

One line per emitted file, at the end of the body. A property whose payload was moved into a sidecar is marked in place:

RawData = @sidecar  // externalized by SwfMovie

Replace the sidecar file on disk and repack — the packer re-injects it into the right place. See Asset Payloads.

Declaration files

Meta-objects extract as declarations rather than property dumps:

// ue3-tools  pkg=Engine.upk  p_ver=9411  export=#4102
// path: ScriptStruct Engine.Actor:PointRegion
// struct_flags=0x00000020

struct immutable PointRegion {
    var ZoneInfo Zone;
    var int iLeaf;
    var byte bForceRefresh;
}

Classes render with consts, enums, nested structs, variables, function stubs, states and a defaultproperties block sourced from the class default object.

Declarations are reference material

pack-mod detects a file whose first token is class, struct, enum, const, var, function, state, immutable or immutablewhencooked and skips it. You can't currently modify a class definition by editing one.

Editing rules

  • Types are fixed. An edit must match the original value's type. Changing an int to a string is a hard error, not a coercion.
  • None removes. Setting a property to None drops it from the output.
  • New properties need the schema. A property not present in the original can be added, but only with --game-root so its type can be looked up on the class.
  • Missing struct fields are created. A struct field that was omitted from the original because it held its default value is created from the schema when you set it.