Pack

Implemented in src/lua/luapackapi.cpp.

Every primitive here reads/writes a genuine native OS path, resolved exactly the way a plain C fopen() would - not routed through the mount-based virtual filesystem (SunLight::FileSystem) every other resource load in this engine uses. That's deliberate: packaging runs against a real, not-yet-bundled source directory on disk, before any of Scarab's own runtime mounting concepts (APP_DIR, a loaded .zip) are even relevant. These primitives exist purely for tools/pack.lua - no running game should ever call them.

Filesystem (native paths)

Recursively lists every regular file under path (a real native OS directory), returning a 1-based, ipairs-friendly table of paths relative to path itself, each using / regardless of host OS (matching this engine's own virtual-path convention elsewhere, so an entry name built from one of these reads naturally once it's inside a .zip). Returns an empty table (not nil) if path doesn't exist or contains nothing.

for _, relativePath in ipairs(pack_list_directory(sourceDir)) do
  local data = pack_read_file(sourceDir .. "/" .. relativePath)
  -- ... encrypt data, pack_add_entry(archive, relativePath, encrypted) ...
end

Archive (miniz-backed .zip writing)

Creates a new .zip archive at path (a real native OS path, overwritten if it already exists), ready for pack_add_entry calls. Returns 0 on failure (path not writable, etc).

Archive handles

pack_create_archive/pack_add_entry/pack_close_archive share one integer-handle convention: 0 always means failure (the same "0 is never a valid handle" shape sprite_acquire already uses), a real handle otherwise. Always call pack_close_archive once done with a handle - the underlying zip file isn't actually finalized/valid until then.