Using libpakka from C

include/pakka.h exposes an opaque-handle C99 API for opening, inspecting, extracting from, and mutating pak archives. Every exported symbol is prefixed pakka_; make symbol-audit fails the build if anything else leaks out, so the library is namespace-clean against any host symbol table.

man 3 pakka (man/pakka.3 in this tree, or ${MANDIR}/man3/pakka.3 after make install) is the full reference. This page is the inventory and call-pattern overview.

Error model

Library functions never call exit and never write to stdout or stderr. Every failure returns a pakka_status_t and optionally populates a caller-provided pakka_error_t with structured detail: errno or Win32 GetLastError, operation name, entry index, file offset, message. Callers that don’t need structured errors can pass NULL for the pakka_error_t * argument.

Inventory

Version

Archive lifecycle

Read introspection

Entry accessors

These read fields from the opaque pakka_entry_t:

Streaming reads

Mutation

Integrity

Tuning

Memory convenience

Transactional commit

Whether a pakka_commit is atomic — fully applied or the on-disk file left untouched, never a torn intermediate — depends on the format and how the archive was opened:

Operation PAK / SiN / Daikatana / IWAD / PWAD PK3 / PK4
pakka_create (new file) atomic (temp → rename on close) atomic
pakka_delete then commit atomic (rebuild → rename) atomic
pakka_add_* then commit, PAKKA_OPEN_READ_WRITE in place — a mid-write failure can corrupt the original atomic
pakka_add_* then commit, PAKKA_OPEN_READ_WRITE_ATOMIC atomic (staged → rebuild → rename) atomic
pakka_rename / pakka_copy then commit, PAKKA_OPEN_READ_WRITE in place directory rewrite — index-only, payload untouched, not crash-atomic atomic (rebuild → rename)
pakka_rename / pakka_copy then commit, PAKKA_OPEN_READ_WRITE_ATOMIC atomic (rebuild → rename) atomic

The PAK family defaults to streaming adds straight into the live file: fast and bounded-memory, but an interrupted add or commit can leave the archive torn. Open with PAKKA_OPEN_READ_WRITE_ATOMIC to stage adds and publish every commit through a temp-file rebuild and rename instead, at the cost of rewriting the archive on each commit. For ZIP the flag is a no-op — PK3 / PK4 commits already rebuild and rename.

A consumer that wants uniform behavior shouldn’t hardcode this table; call pakka_commit_is_atomic(handle) instead. It returns nonzero for any PK3 / PK4 handle, any pakka_create handle, any handle opened PAKKA_OPEN_READ_WRITE_ATOMIC, and any read-only handle; zero only for a PAK-class handle opened plain PAKKA_OPEN_READ_WRITE. The recommended pattern is: open atomic when you need durability, or query and fall back to your own backup-and-replace when it returns zero.

Note the atomic STORED contract matches PK3 STORED: pakka_add_file records the source’s size + CRC32 and re-reads the file at commit, so the source must stay readable and unchanged until pakka_commit / pakka_close returns (a changed source makes commit refuse with PAKKA_ERR_FORMAT). Use pakka_add_memory to pin bytes at call time.

Call-pattern example

test/c_api_test.c exercises the entire public API against the installed libpakka.a only — no internal headers. It’s the canonical example of call patterns and structured-error handling.

Threading and ownership

A pakka_archive_t * is owned by exactly one thread at a time. Concurrent calls on the same handle are undefined; concurrent calls on different handles to the same on-disk archive are unsupported (file locking is not pakka’s responsibility). Entry pointers returned by pakka_entry_at / pakka_find_entry are invalidated by any mutation (pakka_add_file, pakka_add_memory, pakka_delete, pakka_rename, pakka_copy, pakka_commit). Open pakka_reader_t handles are likewise invalidated by a mutation or commit — a rebuild commit reopens the underlying file — so close and reopen readers across one.