Skip to content

Proposal: Built-in PCK packer to eliminate Godot editor dependency for content mods #54

@BSchneppe

Description

@BSchneppe

Problem

Currently, STS2 mod developers need the Godot editor (or MegaDot fork) installed just to export a .pck file containing localization JSON and card art. For simple content mods that only add cards, relics, or potions, this is a heavy requirement — the actual mod code is a single .dll, but the assets need a full Godot project and export pipeline.

This creates friction for new mod developers and makes CI/CD difficult (Godot headless export requires specific binaries).

Would this fit BaseLib's vision?

I ran into this while building a simple content mod (sts2-secret-script) and ended up reverse-engineering the PCK and ctex formats to avoid needing the Godot editor. I have a working proof of concept and would be happy to contribute a proper C# implementation if this fits the direction you see for BaseLib/the mod template.

If you'd prefer this lives as a separate package or in the mod template rather than BaseLib itself, I'm open to that too — just wanted to check before building it out.

Proposed Solution

A standalone PCK packer that runs as part of the dotnet build pipeline, potentially distributed as a NuGet package (e.g. Alchyr.Sts2.PckPacker alongside the existing Alchyr.Sts2.BaseLib and Alchyr.Sts2.ModAnalyzers).

It would:

  1. Pack localization JSON files — passed through as-is into the PCK
  2. Convert PNG images to Godot .ctex format — lossless WebP with the GST2 header
  3. Generate .import remap files — so Godot's resource loader finds the textures
  4. Produce a valid Godot 4.5 format-v3 PCK — ready to deploy to the mods directory

Usage

Integrated as an MSBuild target, something like:

<PackageReference Include="Alchyr.Sts2.PckPacker" Version="*" />

With a build target that runs after compile:

<Target Name="PackPck" AfterTargets="Build">
  <!-- automatically packs ModName/ directory into ModName.pck -->
</Target>

Technical Findings

PCK Format (Godot 4.5, format version 3)

The PCK is a simple binary archive. Header (104 bytes):

Offset Size Field Value
0x00 4 Magic GDPC
0x04 4 Format version 3
0x08 4 Engine major 4
0x0C 4 Engine minor 5
0x10 4 Engine patch 1
0x14 4 Pack flags 0x02 (PACK_REL_FILEBASE)
0x18 8 Files base offset (header size)
0x20 8 Directory offset (after all file data)
0x28 64 Reserved zeros

File data follows immediately after the header, aligned to 32-byte boundaries. The directory index (at the end) contains per-file entries:

Size Field
4 Path length (padded to 4-byte boundary)
var UTF-8 path (e.g. res://ModName/localization/eng/cards.json)
8 File offset (relative to files base)
8 File size
16 MD5 hash
4 Flags (0 = none)

ctex Format (CompressedTexture2D)

Godot cannot load raw PNGs from a PCK — they must be imported to .ctex format. The ctex file is a 56-byte header followed by a WebP payload:

Offset Size Field Value
0x00 4 Magic GST2
0x04 4 Version 1
0x08 4 Width image width
0x0C 4 Height image height
0x10 4 Flags 0x0D000000 (lossless, no mipmaps)
0x14 4 Limiter -1 (0xFFFFFFFF)
0x18 12 Padding zeros
0x24 4 Data format 2
0x28 4 Packed dimensions (height << 16) | width
0x2C 4 Padding 0
0x30 4 Image format 5 (WebP)
0x34 4 WebP data size size of following RIFF/WebP blob
0x38 var WebP data lossless WebP image (RGBA)

This was reverse-engineered from BaseLib.pck and the game's own imported textures. The compress/mode=0 + lossy_quality=1.0 settings used by the game correspond to lossless WebP encoding.

Import Remap Files

For Godot to find textures via their original res://ModName/images/... paths, the PCK must also contain .import remap files:

[remap]
importer="texture"
type="CompressedTexture2D"
path="res://.godot/imported/filename.png-imported.ctex"
metadata={
"vram_texture": false
}

[deps]
source_file="res://ModName/images/card_portraits/filename.png"
dest_files=["res://.godot/imported/filename.png-imported.ctex"]

[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=1.0
mipmaps/generate=false

What Goes in a Typical Content Mod PCK

Type Format in PCK Conversion needed
Localization JSON .json None (passthrough)
Card/relic/power art .ctex PNG → lossless WebP → GST2 header
Import remaps .import Auto-generated per PNG
Scenes (.tscn) .tscn or .scn None (passthrough)
Resources (.tres) .tres None (passthrough)

Proof of Concept

We have a working Python implementation (~130 lines) that successfully:

  • Converts PNGs to ctex (lossless WebP with GST2 header)
  • Generates import remap files
  • Packs everything into a valid format-v3 PCK
  • Produces PCKs that load correctly in STS2

See: pack_pck.py in BSchneppe/sts2-secret-script — a working STS2 mod that uses this packer to build its PCK in CI without the Godot editor.

Implementation Options

  1. C# MSBuild task in a NuGet package — Best integration. Runs automatically on build. Could use ImageSharp + a WebP encoder for PNG→ctex conversion. Ships alongside BaseLib and ModAnalyzers.

  2. Standalone dotnet tool — Simpler to build, less integrated. Developers wire it into their build manually.

  3. Extend the existing ModTemplate csproj targets — Add the packer as a build step in the template, removing the need for Godot export entirely for simple content mods.

Benefits

  • No Godot editor needed for simple content mods (cards, relics, potions with art + localization)
  • CI/CD friendlydotnet build produces everything needed
  • Lower barrier to entry for new mod developers
  • Consistent with existing BaseLib ecosystem (NuGet-based toolchain)

Offer

Happy to implement this as a PR if it fits the project's direction — either as part of BaseLib, a companion NuGet package, or an addition to the mod template. Let me know what you think and where you'd want it to live.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions