Skip to content

NKDS NkFs Format

Nanook edited this page Sep 24, 2026 · 1 revision

created: 2025-06-12 modified: 2025-06-12 tags: [format, nkds-format, datastore, nkfs] type: format status: active

NkFs Binary Filesystem Format

Compact binary representation of filesystem trees — fixed-size entries with O(1) lookup, inspired by GameCube/Wii FST.

Overview

NkFs is the binary format used to represent filesystem trees within NKDS image metadata. It replaces the variable-length YAML text format (filesystem.yaml via FsYaml) with a fixed-size binary format. Produces smaller output than UTF-8 YAML for non-trivial trees (100+ entries).

Key characteristics:

  • 12-byte fixed entries — O(1) index-based lookup
  • Flat navigation — No in-memory tree built; traversal via parent/next-entry indices
  • Big-endian — Matches GC/Wii FST convention
  • Directory name deduplication — Shared names reference same string table offset
  • Multi-extent file support — Non-contiguous files via entry chaining
  • Round-trip fidelity — Bidirectional conversion with FsYaml

Binary Layout

Offset 0x00:
┌──────────────────────────────────────────────────────────┐
│  Header (12 bytes)                                       │
│    Magic, Version, Reserved, EntryCount                  │
├──────────────────────────────────────────────────────────┤  ← 0x0C
│  Entry Table (N × 12 bytes)                              │
│    Entry 0: Root directory                               │
│    Entry 1..N-1: depth-first pre-order                   │
├──────────────────────────────────────────────────────────┤  ← 12 + N×12
│  String Table (variable length, 2-byte aligned)          │
│    Directories: [name\0] + optional pad                  │
│    Files: [prefix byte][fields][name\0] + optional pad   │
└──────────────────────────────────────────────────────────┘

String table offset = 12 + EntryCount × 12 (computed, not stored in header).

Header (12 bytes)

Offset Size Type Field Description
0x00 4 uint32 Magic 0x4E4B4653 (ASCII "NKFS")
0x04 2 uint16 Version Currently 1
0x06 2 uint16 Reserved Must be 0
0x08 4 int32 EntryCount Total entries including root

Entry Format (12 bytes)

Offset Size Type Field Description
0x00 4 uint32 FlagsAndNameOffset High 3 bits = Flags, low 29 bits = NameOffset (×2 for byte offset)
0x04 8 varies — File: ImageOffset (int64). Directory: ParentIndex (int32) + NextEntryIndex (int32)

Flags (bits 31–29)

Bit Name Description
0 is_directory 1 = directory, 0 = file
1 system_flag System marker (hidden from normal VFS views)
2 image_file Image file system reference

NameOffset Encoding

packed = (flags << 29) | (nameOffset & 0x1FFFFFFF)
actualByteOffset = nameOffset × 2      // 2-byte alignment, 1 GiB addressable

File Entry (0x04–0x0B)

Field Size Description
ImageOffset 8 bytes (int64) Byte offset in source image

Size, checksums, and image ID stored in string table prefix.

Directory Entry (0x04–0x0B)

Field Size Description
ParentIndex 4 bytes (int32) Parent directory entry index
NextEntryIndex 4 bytes (int32) First entry NOT a descendant

Root: index 0, ParentIndex = 0 (self), NextEntryIndex = EntryCount.

String Table

Directory Entries

[name\0] + optional pad byte (2-byte alignment)

Directory names eligible for deduplication (identical names share same offset).

File Entries

[1 byte prefix flags]
[size_width bytes: file size, big-endian]
[if has_checksums: 8 bytes xxHash64 + 4 bytes CRC32, big-endian]
[imageid_width bytes: image index, big-endian]
[name\0]
+ optional pad byte

Prefix Byte Layout

Bit:  7   6   5   4   3   2   1   0
     [res][res][HME][iw1][iw0][sw1][sw0][chk]
Bits Name Description
0 has_checksums 1 = 12 bytes xxHash64+CRC32 present
1-2 size_width 0/1/2/3 → 0/2/4/8 bytes
3-4 imageid_width 0/1/2/3 → 0/2/4/8 bytes
5 has_more_extents 1 = next sibling is continuation extent
6-7 reserved Must be 0

Width Encoding

Value Bytes Range
0 0 value = 0
1 2 ≤ 0xFFFF
2 4 ≤ 0xFFFFFFFF
3 8 > 0xFFFFFFFF

Navigation Algorithms

Child Listing

children(N):
  nextEntry = entry[N].NextEntryIndex
  i = N + 1
  while i < nextEntry:
    yield entry[i]
    if entry[i].is_directory:
      i = entry[i].NextEntryIndex    // skip subtree
    else:
      i = i + 1

Path Resolution

resolve("dir1/dir2/file.txt"):
  current = 0 (root)
  for each segment in path.split('/'):
    scan children of entry[current]
    if match found: current = child index
    else: return -1
  return current

Multi-Extent Files

Files spanning non-contiguous disc regions use consecutive same-name entries linked by has_more_extents (bit 5):

Entry i:    Name="video.m2ts", Offset=0x1000, Size=0x8000, HME=1
Entry i+1:  Name="video.m2ts", Offset=0xF000, Size=0x6000, HME=1
Entry i+2:  Name="video.m2ts", Offset=0x20000, Size=0x4000, HME=0 (final)

Total logical size = sum of all extent sizes.

Backward Compatibility

Readers unaware of bit 5 see each extent as an independent file with the same name — degraded but non-crashing interpretation.

Related

Clone this wiki locally