Modding

Mod paks

Project

A pak is a file of assets cooked by Unreal Engine 5.8. The mod's init.lua loads them by path. Assets are made in the stock UE 5.8 from the Epic Games Launcher, in an empty project; no game source and no game files are needed. The example project is Evospace/EvospacePakExample: a gear mesh, a texture and a pulsing material that replace the look of copper plates.

folder
EvospacePakExample/
    Evospace.uproject
    Config/
    Content/
        ModContent/
            PakExample/
                SM_PakExample.uasset
                M_PakExample.uasset
                T_PakExample.uasset
    Mod/
        PakExample/
            info.json
            init.lua
    pack.sh
    pack.bat
  • The project is named Evospace, so /Game/ in it is /Game/ in the game.
  • Config/ holds the game's renderer and shader format settings. Materials keep their shader code inside the asset; the project's Share Material Shader Code stays off.

Content

  • Assets go under /Game/ModContent/<ModName>/. Paths outside it are not packed.
  • Only engine classes: static and skeletal meshes, textures, materials, sounds, Niagara. Blueprints of game classes are not supported; logic goes in init.lua.
  • A reference to a game asset needs a placeholder at the same path in the project. The placeholder is not packed; the game resolves the path to its own asset.

Loading

The game mounts every .pak in Paks/<Platform>/ of the mod folder at startup, before any Lua runs.

folder
PakExample/
    info.json
    init.lua
    Paks/
        Mac/
            PakExample.pak
        Windows/
            PakExample.pak
        Linux/
            PakExample.pak
init.lua lua
local PakExample = {}

function PakExample.init()
    local mesh = StaticMesh.load("/Game/ModContent/PakExample/SM_PakExample.SM_PakExample")
    if mesh == nil then
        print_err("PakExample: SM_PakExample not found, Paks/<Platform>/PakExample.pak is not mounted")
        return
    end
    StaticItem.get("CopperPlate").mesh = mesh
    print_info("PakExample: CopperPlate is drawn with SM_PakExample from the pak")
end

db:mod(PakExample)

An asset path is the package path, a dot, and the asset name. A loader logs the path and returns nil when the asset is not found.

FunctionLoads
StaticMesh.load Static mesh.
Material.load Material or material instance.
Texture.load 2D texture.
SoundClass.load Sound class.
AnimSequence.load Animation sequence.

Build

Mac, Linux bash
./pack.sh PakExample Mac
Windows cmd
pack.bat PakExample Windows

pack.sh and pack.bat take the mod name and the platform, which defaults to the machine's own. They cook Content/ModContent/<ModName> and write Mod/<ModName>/Paks/<Platform>/<ModName>.pak. UE_ROOT sets the engine folder; the defaults are the launcher's /Users/Shared/Epic Games/UE_5.8 and C:\Program Files\Epic Games\UE_5.8. The first cook compiles the engine's global shaders and takes a while.

Each platform has its own pak, cooked by an engine that has the platform. A platform without a pak loads the mod without its assets.

PlatformCooked on
Mac A Mac.
Windows Windows.
Linux Windows or Linux with the Linux target platform of the engine installed. Also used by the Steam Deck.

Engine version

The game mounts a pak only when every package in it was cooked by UE 5.8 with package versions. pack.sh and pack.bat stop on another engine. Packaging from the editor's menu cooks without package versions, and such a pak is refused too. A refused pak is named in the log:

log
Mod PakExample: pak .../PakExample.pak not mounted, M_PakExample.uasset is cooked by another engine: package version 522/1017, the game reads 522/1018; cook it with UE 5.8

Install

The mod folder goes to Content/Mods of the game, or to the Workshop from the main menu, as in Publishing. Paks are mounted only by the game, not by the Unreal editor. A mounted pak and a loaded asset show in the log:

log
Mod PakExample: pak .../Paks/Mac/PakExample.pak mounted with order 100
Lua: PakExample: CopperPlate is drawn with SM_PakExample from the pak