Skip to content

Packaging a .clmod

Status: Implemented. Package signatures (clight sign), asset and fallback checks in clight check, and checking WASM imports against the API are Planned.

A CLight mod is distributed as one file: <mod_id>-<version>.clmod. This page describes the package format, how clight build and clight pack produce it, and what the loader checks when it opens one.

The format

A .clmod is a zip archive (deflate or stored entries) with a fixed layout:

ruby_tools-1.2.0.clmod
├── clight.mod.json                 required: the manifest (always at the root)
├── icon.png                        optional: 64–256 px square
├── bin/                            code, depending on runtime.type
│   └── ruby_tools.wasm             wasm (C, C++): one module for every platform
├── scripts/                        lua: main.lua and modules
├── assets/<ns>/…                   resource-pack layout (textures, models, lang, sounds.json + sounds/, ui)
├── datapack/<ns>/…                 data-pack layout without the data/ level (recipes, loot, tags, enchantments)
├── packs/<name>/                   optional built-in packs listed in the manifest's "packs"
├── api/*.clidl                     reserved for service interfaces (planned)
├── LICENSE                         recommended
└── META-INF/                       reserved for signatures (planned)

Rules:

  • Paths use forward slashes and are case-sensitive, even on Windows. clight check rejects names that differ only by case.
  • No absolute paths, .. segments, backslashes or symbolic links. clight check, the loader and the website refuse archives that contain them.
  • Only these top-level entries are allowed: clight.mod.json, icon.png, bin/, scripts/, assets/, datapack/, packs/, api/, META-INF/, LICENSE*, README*, CHANGELOG*. Anything else is a warning in clight check and is ignored by the loader.
  • data/ is not part of a package: it is the mod’s runtime read/write folder (mods/<id>/data/), created when the mod first writes.
  • assets/ follows the vanilla resource-pack layout exactly, and datapack/<ns>/ the data-pack layout under data/. The mod’s own namespace is its mod id; other namespaces (minecraft, c) are allowed for overrides and shared tags.

Building a package

Command Does
clight build Compiles (wasi-sdk for C/C++), validates, and writes build/<id>-<version>.clmod
clight build --release Optimised and stripped WebAssembly
clight pack Packs without compiling (Lua and data mods, or a prebuilt build/bin/)
clight check Validates an existing project or .clmod
clight sign planned: signs the package (Signing and trust)

CMake workflows use the <mod>_clmod target that clight_add_mod() creates.

Reproducible packages

clight pack writes entries in sorted order with fixed timestamps (1980-01-01), fixed attributes and a fixed compression level (deflate 9, stored when that doesn’t shrink a file). The same inputs therefore produce a byte-identical .clmod, which makes signatures and hashes meaningful and lets users verify a build.

What clight check validates

Check Today Example finding
Manifest schema ✔ sync missing; id doesn’t match ^[a-z][a-z0-9_]{1,63}$; runtime.type: "native" (not supported: every mod is sandboxed); the old "hooks" key
Mixins ✔ unknown targets or sites in "mixins" and in a built module’s CL_MIXIN records; side mismatches; overwrite warnings
Capabilities ✔ unknown capability; fs.read:saves, net.socket, process.spawn (never available)
Package paths ✔ absolute paths, .., backslashes, names that differ only by case, entries outside the allowed top level
Runtime files present ✔ runtime.entry points to a missing file; icon missing
WASM imports planned The module imports a function the declared API doesn’t have
Assets planned Missing lang keys, models referencing missing textures, unused files
Fallbacks planned Static entries without vanilla_fallback in a mod with vanilla_clients: allow
Size planned Uncompressed textures over 1 MiB, debug info in a release package

clight check --strict turns warnings into errors (use it in CI).

What the loader does with a package

  1. Discover: reads clight.mod.json from each .clmod and mod folder in mods/.
  2. Unpack: a .clmod is unpacked once into mods/<id>/ with a .clmod-source stamp, so packages and folders behave the same; an updated package replaces the folder (its data/ is kept).
  3. Resolve: versions, dependencies, breaks and load order (Versioning and dependencies).
  4. Mount: assets/ becomes the resource pack mod/<id> (above vanilla, below the player’s resource packs, so packs can retexture mod content); datapack/ becomes the data pack mod/<id> (above vanilla, below the world’s datapacks). Mods mount in load order.
  5. Load code:
    • Lua: scripts are read from the mod folder inside its sandbox.
    • WASM: compiled with Cranelift on first load and cached under cache/clight/.

Size guidelines

  • Compress textures as PNG; avoid huge textures (the atlas has limits and VRAM matters on integrated GPUs).
  • Sounds as Ogg Vorbis, mono for positional sounds (stereo sounds don’t attenuate with distance, as in vanilla).
  • Build releases with clight build --release (debug info stripped); keep a dev build if you want symbolized traps.

What the Celestial website checks

Uploading a version on the website (Distribution) runs the same rules again, without unpacking anything to disk:

Check Result
Unsafe paths, links, files that are also folders, names that differ only by case refused
Native code (.exe, .dll, .so, .dylib, executables found by their magic bytes) refused: mods are sandboxed
.wasm files without the WebAssembly header, or another binary version refused
The manifest: valid JSON, at most 256 KB, the same id as the project, a new version number refused otherwise
icon.png: a PNG up to 256 KB (64-256 px square recommended) refused / warned
No LICENSE file, no icon, data/ in the package, nested archives, textures over 1 MB, unknown top-level entries warnings

Then a background scan looks for things that have no reason to be in a sandboxed mod (imports outside the CLight API, Lua bytecode, obfuscation, packed payloads) and marks the file clean or flags it for a moderator.