Packaging Flow
Overview
ActionManager::BuildData(Platform platform, bool embedded) is the main function responsible for packaging game assets and compiling executables for various target platforms.
Location: ActionManager.cpp:152
Function Parameters
| Parameter | Type | Description |
|---|---|---|
platform |
Platform |
Target platform (Windows, Linux, Android, GameCube, Wii, N3DS) |
embedded |
bool |
Whether to embed assets into the executable |
Flow Diagram
BuildData()
|
v
[1] Gather Engine State & Paths
|
v
[2] Create Packaged Directory
|
v
[3] Cook & Save Assets (Engine + Project)
|
v
[4] Generate Asset Registry
|
v
[5] Generate Embedded Files (Assets + Scripts)
|
v
[6] Copy Scripts (or embed them)
|
v
[7] Copy Project Files (.octp, Config.ini)
|
v
[8] Handle Platform-Specific Tasks (shaders)
|
v
[8b] Static Content / Content Pak (if enabled)
|
v
[8c] Romfs copy (3DS)
|
v
[9] Compile Executable (if needed)
|
v
[10] Copy Executable to Packaged Directory
Detailed Step-by-Step Flow
Step 1: Initialize Paths and State
std::string polyphaseDirectory = SYS_GetPolyphasePath();
const EngineState* engineState = GetEngineState();
bool standalone = engineState->mStandalone;
const std::string& projectDir = engineState->mProjectDirectory;
const std::string& projectName = engineState->mProjectName;
Path Resolution (SYS_GetPolyphasePath):
- First checks current directory for Polyphase/imgui.ini
- Falls back to executable directory if Standalone/Standalone.rc not found
- Returns empty string on consoles (Android, 3DS, Dolphin)
Step 2: Create Packaged Directory Structure
Directory Structure Created:
{ProjectDir}/Packaged/
└── {Platform}/ <- e.g., Windows/, Linux/, N3DS/
├── Engine/
└── {ProjectName}/
- Existing platform directory is deleted and recreated fresh
Step 3: Cook Assets
The saveDir lambda recursively processes asset directories:
- Creates corresponding directory in packaged folder
- Loads each asset (if not already loaded)
- Saves platform-specific
.octfile - Optionally adds to embedded assets list (if
embedded && !useRomfs) - Unloads asset if it wasn't originally loaded
Packaged Asset Paths:
- Engine assets: {PackagedDir}/Engine/{path}/
- Project assets: {PackagedDir}/{ProjectName}/{path}/
Step 4: Generate Asset Registry
Creates {PackagedDir}/{ProjectName}/AssetRegistry.txt containing:
{AssetType},{AssetPath}
{AssetType},{AssetPath}
...
Step 5: Generate Embedded Files
Creates in {ProjectDir}/Generated/:
- EmbeddedAssets.h / EmbeddedAssets.cpp
- EmbeddedScripts.h / EmbeddedScripts.cpp
If standalone, copies Generated folder to Standalone/
Step 6: Handle Scripts
| Mode | Behavior |
|---|---|
embedded && !useRomfs |
Gathers scripts for embedding |
| Otherwise | Copies script folders to packaged directory |
Script Locations:
- Engine: {PolyphaseDir}/Engine/Scripts/ -> {PackagedDir}/Engine/Scripts/
- Project: {ProjectDir}/Scripts/ -> {PackagedDir}/{ProjectName}/Scripts/
Note: LuaPanda.lua is removed for non-desktop platforms (saves 148KB)
Step 7: Copy Project Files
Copies to packaged directory:
- {ProjectName}.octp (project file)
- Config.ini (configuration)
Step 8: Platform-Specific Tasks
Vulkan Platforms (Windows, Linux, Android)
- Compiles SPIR-V shaders via
compile.bat(Windows) orcompile.sh(Linux) - Copies shader binaries to
{PackagedDir}/Engine/Shaders/GLSL/bin/
Static Content / Content Pak
If the profile enables Static Content, shipped content is obfuscated here —
either wrapped in place (ObfuscatePackagedContent) or folded into a single
Content.pak with the loose copies deleted (PackPackagedContent).
This step's position is load-bearing:
- It must run after Step 5 (embedded generators). ConvertFileToByteString
reads cooked files back through Stream::ReadFile, which decodes — sweeping
first would make the generators emit plaintext byte arrays.
- It must run before the romfs copy below, which clones the whole packaged
directory. Running after it ships 3DS in the clear.
See StaticContent.md for the format, runtime decode sites and the build-target addon opt-in.
Nintendo 3DS with Romfs
If useRomfs (N3DS + embedded):
- Copies all packaged content to {IntermediateDir}/Romfs/
Note useRomfs = (platform == N3DS) && embedded, and the embedded byte arrays in
Step 5 are gated on embedded && !useRomfs. So on 3DS "embedded" routes content
through romfs as loose files rather than compiling it into the executable —
anything that treats embedded as "content is in the binary" is wrong there.
Intermediate Directory:
- Standalone: {PolyphaseDir}/Standalone/Intermediate
- Project: {ProjectDir}/Intermediate
Headless vs Non-Headless Mode
Key Difference
The critical distinction is at line 407-411:
if (standalone && !IsHeadless() &&
(platform == Platform::Windows || platform == Platform::Linux))
{
needCompile = !SYS_DoesFileExist(prebuiltExeName.c_str(), false);
}
Non-Headless Mode (Editor)
| Aspect | Behavior |
|---|---|
| Compilation | Skipped if prebuilt Polyphase.exe/Polyphase.elf exists |
| Use Case | Faster iteration during development |
| Executable | Reuses existing editor binary |
| Assumption | Editor binary is suitable for running packaged game |
Flow:
1. Checks for existing Polyphase.exe or Polyphase.elf
2. If found, sets needCompile = false
3. Copies existing executable to packaged folder
4. Renames to project name
Headless Mode (CI/Full Build)
| Aspect | Behavior |
|---|---|
| Compilation | Always performed |
| Use Case | CI/CD pipelines, release builds |
| Executable | Fresh compile every time |
| Detection | IsHeadless() returns true when -headless flag AND -project path are set |
Flow:
1. needCompile defaults to true
2. Full compilation runs regardless of existing binaries
3. Builds optimized release executable
Headless Detection
// Engine/Source/Engine/Engine.cpp:183
bool IsHeadless()
{
return sEngineConfig.mHeadless && sEngineConfig.mProjectPath != "";
}
Both conditions required:
1. -headless command line flag
2. Valid project path specified via -project
Compilation by Platform
Windows
devenv Polyphase.sln /Build "Release|x64" /Project {ProjectName}
With Steam: Uses ReleaseSteam|x64 configuration
Linux
make -C {BuildProjDir} -f Makefile_TEMP -j 12
strip --strip-debug {BuildDir}/Linux/{ExeName}.elf
Android
cd {BuildProjDir}/Android && ./gradlew assembleRelease
Assets copied to Android/app/src/main/assets/ before build
GameCube/Wii/3DS
make -C {BuildProjDir} -f Makefile_TEMP -j 12
Uses DevkitPro toolchain (Makefile_GCN, Makefile_Wii, Makefile_3DS)
Output Paths Summary
Packaged Directory
{ProjectDir}/Packaged/{Platform}/
Build Output by Platform
| Platform | Build Path | Extension |
|---|---|---|
| Windows | Build/Windows/x64/Release/ |
.exe |
| Linux | Build/Linux/ |
.elf |
| Android | Android/app/build/outputs/apk/release/ |
.apk |
| GameCube | Build/GCN/ |
.dol |
| Wii | Build/Wii/ |
.dol |
| N3DS | Build/3DS/ |
.3dsx |
Generated Files
{ProjectDir}/Generated/
├── EmbeddedAssets.h
├── EmbeddedAssets.cpp
├── EmbeddedScripts.h
└── EmbeddedScripts.cpp
Example: Full Build Flow Comparison
Non-Headless (Editor) - Linux Build
1. Create Packaged/Linux/
2. Cook assets to .oct files
3. Generate AssetRegistry.txt
4. Generate embedded files (empty if not embedded)
5. Copy scripts to Packaged/Linux/Engine/Scripts/
6. Copy .octp and Config.ini
7. Compile SPIR-V shaders
8. Check for Polyphase.elf -> EXISTS -> skip compile
9. Copy existing Polyphase.elf to Packaged/Linux/
10. Rename to {ProjectName}.elf
Headless (CI) - Linux Build
1. Create Packaged/Linux/
2. Cook assets to .oct files
3. Generate AssetRegistry.txt
4. Generate embedded files
5. Copy scripts to Packaged/Linux/Engine/Scripts/
6. Copy .octp and Config.ini
7. Compile SPIR-V shaders
8. needCompile = true (always in headless)
9. Run: make -C {dir} -f Makefile_TEMP -j 12
10. Strip debug symbols
11. Copy fresh executable to Packaged/Linux/