8  Chapter 6: NCS Format (Nexus Config Store)

Try searching BL4’s pak files for item pool definitions. For loot configuration tables. For achievement data. You’ll find nothing — these classes don’t exist as standard .uasset files. Gearbox moved critical game configuration into a custom binary format embedded directly in pak chunks, invisible to normal Unreal Engine extraction tools.

That format is NCS: Nexus Config Store. It holds item pools, drop rates, inventory part definitions, actor configurations, achievement data, and hundreds of DataTable serializations. If you want to understand how BL4’s loot system works — what can drop, where it drops, which parts are valid for which weapons — you need to understand NCS.

This chapter is the format specification. It covers everything from Oodle-compressed pak chunks down to bit-packed tag values in the binary section. It’s long, and deliberately so: NCS is a complex format with multiple encoding strategies, and the details matter when you’re writing a parser.


8.1 Overview

NCS stores typed configuration records. Each NCS file has a content type (achievement, inv, itempoollist, loot_config, etc.) and contains structured entries with string-valued and binary-encoded fields. The format is space-efficient — strings are differentially encoded, values are bit-packed, and the whole thing is Oodle-compressed before being stored in a pak chunk.

The data exists at two levels:

  1. NCS Chunks — Oodle-compressed blocks within pak files, located via an NCS manifest
  2. Decompressed Content — The actual typed configuration data: header, strings, and binary section

Figure 6.1 shows the decompression pipeline. Figure 6.2 shows the layout of decompressed content.

flowchart LR
    A[PAK File] --> B[NCS Chunk<br/>16-byte header]
    B --> C{Format Flags}
    C -->|0x00000000| D[Single Block<br/>Oodle decompress]
    C -->|0x03030812| E[Multi Block<br/>256KB blocks]
    D --> F[Decompressed<br/>Content]
    E --> F
flowchart TB
    A[BlobHeader<br/>16 bytes] --> B[Header Strings<br/>null-terminated]
    B --> C[TypeCodeTable<br/>type codes + bit matrix]
    C --> D[String Blocks<br/>3 blocks: values, kinds, keys]
    D --> E[Binary Data<br/>bit-packed tables/records/entries]

8.2 Compression

NCS data is Oodle-compressed and wrapped in a two-layer header structure. The outer header describes the NCS chunk; the inner header describes the Oodle compression format.

8.2.1 Outer Header (16 bytes)

Offset  Size  Type   Description
------  ----  ----   -----------
0x00    1     u8     Version (always 0x01)
0x01    3     bytes  Magic: "NCS" (0x4e 0x43 0x53)
0x04    4     u32    Compression flag (0 = raw, non-zero = Oodle)
0x08    4     u32    Decompressed size (little-endian)
0x0c    4     u32    Compressed size (little-endian)

The version byte has always been 0x01 in every file observed. The compression flag is zero for uncompressed data (rare) and non-zero for Oodle-compressed data (nearly all files).

8.2.2 Inner Header (16+ bytes)

Offset  Size  Type   Description
------  ----  ----   -----------
0x00    4     u32    Oodle magic: 0xb7756362 (big-endian)
0x04    4     bytes  Hash/checksum
0x08    4     u32    Format flags (big-endian)
0x0c    4     u32    Block count (big-endian)

8.2.3 Format Flags

The format flags determine whether the data is stored as a single Oodle-compressed block or split across multiple blocks:

Flags Format Description
0x00000000 Single-block Small files, one Oodle block
0x03030812 Multi-block Large files, 256KB blocks

Single-block is the common case. Multi-block files split the decompressed data into 256KB chunks, each independently Oodle-compressed. The block count field in the inner header gives the number of chunks.

8.2.4 Compatibility

Most NCS files decompress with open-source Oodle implementations. A small number (~2.4% of files) use compression parameters that require the official Oodle SDK:

File Size Notes
audio_event0 ~18MB Large audio mappings
coordinated_effect_filter0 ~300KB Effect filters
DialogQuietTime0 ~20MB Dialog timing
DialogStyle0 ~370KB Dialog styling

8.3 Content Format

After decompression, the data follows a structured binary format parsed by the parse/ module pipeline: blob.rstypecodes.rsdecode.rs.

8.3.1 BlobHeader (16 bytes)

Every decompressed NCS payload starts with a 16-byte header:

Offset  Size  Type   Description
------  ----  ----   -----------
0x00    4     u32    Entry count (little-endian)
0x04    4     u32    Flags
0x08    4     u32    String bytes (total size of header strings)
0x0c    4     u32    Reserved

8.3.2 Header Strings

Immediately after the BlobHeader, string_bytes worth of null-terminated ASCII strings. The first string is the table type name (e.g., achievement, inv, itempoollist). Remaining strings are dependency table names — for inv.bin, these include part slot categories like inv_comp, barrel, body, element, firmware, etc.

8.3.3 TypeCodeTable

After the header strings, the body section begins with a TypeCodeTable:

Offset  Size       Description
------  ----       -----------
0x00    1          type_code_count (number of type code chars)
0x01    2          type_index_count (u16 LE)
0x03    N          type code characters (e.g., 'a', 'b', 'c', 'e', 'f', 'h', 'i', 'j', 'l')
0x03+N  ceil(type_index_count * type_code_count / 8) bytes  Bit matrix (row flags)

The type codes are single ASCII characters that describe the tag types used in the binary data section. Each character maps to a bit position in the row flags:

Tag Bit Description
a 0 Key name (pair_vec string)
b 1 U32 value
c 2 F32 value
d 3 Name list D
e 4 Name list E
f 5 Name list F
h 7 Has-self-key flag (bit 7 of row flags)
i 8 (format-specific)
j 9 (format-specific)
l 11 (format-specific)
p 15 Variant (nested node)
z Tag section terminator (not in bit matrix)

The bit matrix has type_index_count rows and type_code_count columns. Each row’s bits are combined into a row_flags u32 that determines how nodes of that type are decoded — bits 0-1 encode the node kind (null/leaf/array/map), bit 7 encodes whether the node carries its own key.

8.3.4 Three String Blocks

After the bit matrix, three sequential string blocks contain the decode vocabulary:

  1. Value strings — the actual data values referenced by index
  2. Value kinds — type annotations (e.g., Asset, game_region, map)
  3. Key strings — entry and field names (e.g., serialindex, mappath, displayname)

Each block has a 16-byte header: count (u16), declared_count (u16), then byte_length bytes of null-terminated strings.

8.3.5 Binary Data Section

After the string blocks, the remaining bytes are the bit-packed binary data. This is decoded by decode.rs using the row flags from the TypeCodeTable and the string tables for value resolution.

The decode loop reads: - Table IDs referencing header strings - Dependency lists per table - Remap arrays (FixedWidthIntArray) for key and value string index remapping - Records with byte-aligned length prefixes - Tags (a through z) per record — metadata like key names, numeric values, name lists - Entries with key-value pairs decoded recursively as null/leaf/array/map nodes - Dependency entries linking records to dependent tables with serial indices


8.4 Binary Section

The binary section contains bit-packed structured data decoded by the decode.rs module. All NCS files use the same decode algorithm — the TypeCodeTable’s row flags determine how each node type is interpreted.

8.4.1 String Indexing

The binary section references strings by index into the three string tables parsed from the TypeCodeTable:

  • Header strings — table name and dependency names (from BlobHeader)
  • Value strings — data values (from string block 1)
  • Value kinds — type annotations like Asset, game_region (from string block 2)
  • Key strings — entry and field names (from string block 3)

Bit widths for indices are computed from the declared counts: bit_width(count) gives the minimum bits needed to index into each table.

8.4.2 Decode Loop

The binary data is read as a bitstream. The outer loop reads table IDs (referencing header strings), dependency lists, and per-table remap arrays. Each table contains byte-aligned records:

[table_id] [dep_id...0] [remap_a] [remap_b] [records...]

Each record has: 1. Length prefix (32-bit, byte-aligned) — total record size in bytes 2. Tags — metadata bytes (a through z) until the z terminator 3. Entries — key-value pairs with 2-bit opcodes (0=end, 1=null, 2=node, 3=ref) 4. Dependency entries — cross-references to dependent tables

8.4.3 Record Tags

Tags provide per-record metadata. Each tag is a single ASCII byte followed by tag-specific data:

Tag Description Data
a Key name pair_vec string reference
b U32 value 32 bits
c F32 value 32 bits (interpreted as both u32 and f32)
d, e, f Name lists Sequence of pair_vec strings until “none”
p Variant Nested node value
z Terminator End of tag section

8.4.4 Node Types

Entry values are decoded recursively. The type_index from the bitstream selects a row from the TypeCodeTable’s bit matrix, which determines the node kind:

Kind (bits 0-1) Type Decoding
0 Null No data
1 Leaf Value string + value kind
2 Array Continuation-bit loop of child nodes
3 Map Continuation-bit loop of key-value pairs

If bit 7 of the row flags is set (or kind is Map), the node reads a self-key string before the value.

8.4.5 Remap Arrays

Each table has two FixedWidthIntArrays (24-bit count + 8-bit value width): - Remap A — remaps key string indices - Remap B — remaps value string indices

These compress the index space when a table only uses a subset of the global string tables.

8.4.6 Hash Function

FNV-1a 64-bit is used for field name hashing:

const OFFSET_BASIS: u64 = 0xcbf29ce484222325;
const PRIME: u64 = 0x100000001b3;

fn fnv1a_64(data: &[u8]) -> u64 {
    let mut hash = OFFSET_BASIS;
    for byte in data {
        hash ^= *byte as u64;
        hash = hash.wrapping_mul(PRIME);
    }
    hash
}

8.5 Inventory Parts (inv.bin)

The inv.bin file is the authoritative source for valid weapon and gear parts. BL3 used explicit PartSet and PartPool assets in the pak files. BL4 moved all of that into NCS.

inv.bin uses the full tag-based binary format — the most complex NCS encoding. The file is large (1.4MB decompressed, 18,393 strings, 976KB of binary data) and contains the complete inventory definition for every weapon, shield, and gear item in the game.

8.5.1 File Structure

Offset    Content
------    -------
0x00      BlobHeader (16 bytes)
0x10      Header strings ("inv" + 39 dependency names)
0x0d      Dependencies (39 null-terminated strings)
0x1fb     Format code ("abcefhijl")
0x225     String table (18,393 null-terminated strings)
0x169e7c  END OF FILE

The 39 dependency strings define valid part slot categories:

inv_comp              primary_augment        secondary_augment
core_augment          barrel                 barrel_acc
body                  body_acc               foregrip
grip                  magazine               magazine_ted_thrown
magazine_acc          scope                  scope_acc
secondary_ammo        hyperion_secondary_acc payload_augment
payload               class_mod_body         passive_points
action_skill_mod      body_bolt              body_mag
element               firmware               stat_augment
body_ele              unique                 turret_weapon
tediore_acc           tediore_secondary_acc  endgame
enemy_augment         active_augment         underbarrel
underbarrel_acc_vis   underbarrel_acc        barrel_licensed

8.5.2 Parser Pipeline

The NCS parser processes inv.bin through a three-stage pipeline in the parse/ module:

  1. blob.rs — Reads the decompressed header: entry count, flags, string byte counts. Extracts header strings (dependencies, type name).
  2. typecodes.rs — Builds a TypeCodeTable from the format code. Reads type codes, bit matrix, and row flags. Parses 3 string blocks (value strings, value kinds, key strings) with string repair for missing null terminators.
  3. decode.rs — Decodes the bit-packed table data into structured output: tables, records, tags, entries, and values.

The decoder produces a Document containing Tables of Records. Each record has Tags (type-annotated metadata), Entrys (key-value data), and DepEntrys (dependency references with serial indices).

Serial index extraction from inv0.bin yields 649 of 655 known indices. The 6 missing are likely due to test data from an older game version.

8.5.3 Weapon Type Definitions

Weapon types follow the pattern {MANUFACTURER}_{WEAPONTYPE}:

Pattern Example Description
DAD_PS Daedalus Pistol 56 valid parts
JAK_SG Jakobs Shotgun Shotgun parts
VLA_AR Vladof AR Assault rifle parts
TOR_HW Torgue Heavy Weapon Heavy weapon parts

Manufacturers: BOR, DAD, JAK, MAL, ORD, TED, TOR, VLA

Weapon Types: PS (Pistol), SG (Shotgun), AR (Assault Rifle), SM (SMG), SR (Sniper), HW (Heavy)

Parts are listed sequentially after their weapon type definition, continuing until the next weapon type entry:

DAD_PS                          <- Weapon type entry
  NexusSerialized, ..., Daedalus Pistol
  Weapon_PS
  /Game/Gear/Weapons/Pistols/DAD/Body_DAD_PS.Body_DAD_PS
  ...
  DAD_PS_Barrel_01              <- Valid parts start
  DAD_PS_Barrel_01_A
  DAD_PS_Barrel_01_B
  DAD_PS_Barrel_01_C
  DAD_PS_Barrel_01_D
  DAD_PS_Body
  DAD_PS_Body_A
  DAD_PS_Body_B
  ...
  DAD_PS_Underbarrel_06         <- Last part
DAD_SG                          <- Next weapon type

Part names follow {MANUFACTURER}_{WEAPONTYPE}_{SLOT}_{VARIANT}:

  • DAD_PS_Barrel_01 — Daedalus Pistol, Barrel slot, base variant
  • DAD_PS_Barrel_01_A — Daedalus Pistol, Barrel slot, A variant
  • JAK_SG_Grip_03 — Jakobs Shotgun, Grip slot, variant 3

8.5.4 Serial Index Structure

Each part has a serialindex field for serialization. The structure encodes both the index number and scope:

serialindex: {
  status: "Active" | "Inactive"
  index: u32                // 0-127 typically
  _category: "inv_type"    // Always "inv_type" for inventory
  _scope: "Root" | "Sub"   // "Root" for item types, "Sub" for parts
}

Root scope entries identify base item types (e.g., DAD_PS, BOR_SG). In serialized items, Root indices occupy the range 0–127 (bit 7 = 0).

Sub scope entries identify individual parts within an item type. Indices are unique within each type but may repeat across types. In serialized items, Sub indices use 128–255 (bit 7 = 1).

8.5.4.1 Bit 7 Flag

In actual serialized items, the Root vs Sub distinction is encoded as bit 7 of the part token index:

For Part indices > 142 (beyond element range):
  Bit 7 = 0 -> Root scope (core parts: body, barrel, scope)
  Bit 7 = 1 -> Sub scope (attachments: grips, foregrips, underbarrel)

Actual part index = serial_index & 0x7F  (strip bit 7)

Examples from Rainbow Vomit (Jakobs Shotgun):

  • Serial index 4 -> part_body_b (Root, bit 7 = 0)
  • Serial index 170 -> 42 -> part_grip_03 (Sub, bit 7 = 1, actual = 42)
  • Serial index 166 -> 38 -> part_grip_04_hyp (Sub, bit 7 = 1, actual = 38)

The NCS _scope field directly corresponds to how parts are encoded in serialized items.

8.5.5 Legendary Compositions

Legendary weapons are defined by comp_05_legendary_* entries with mandatory parts:

comp_05_legendary_Zipgun
  uni_zipper                    <- Unique naming part (red text)
  part_barrel_01_Zipgun         <- Mandatory unique barrel

comp_05_legendary_DiscJockey
  uni_discjockey
  part_barrel_02_DiscJockey

comp_05_legendary_OM            <- Oscar Mike
  part_barrel_unique_OM

comp_05_legendary_GoreMaster
  part_barrel_02_GoreMaster

Each composition has: an identifier (comp_05_legendary_{name}), a unique naming part (uni_{name}) that provides the display name and red text, and one or more mandatory parts (usually a unique barrel).

8.5.6 NCS vs Memory Part Names

Part names differ between NCS extraction and runtime memory dumps:

NCS Name Memory Name
DAD_PS_Barrel_01 DAD_PS.part_barrel_01
DAD_PS_Body_A DAD_PS.part_body_a
DAD_PS_Grip_04 DAD_PS.part_grip_04_hyp

NCS consistently has more parts (56 for DAD_PS) than memory extraction (34), because memory dumps only capture parts that exist as runtime UObjects during the dump. Always use NCS as the authoritative source.

8.5.7 Extracting Parts

# Extract all item parts (weapons + shields) to JSON
bl4 ncs extract inv4.bin -t item-parts --json -o item_parts.json

# View weapon parts for a specific pakchunk
bl4 ncs extract /path/to/ncsdata/pakchunk4-Windows_0_P -t item-parts

Output:

[
  {
    "item_id": "DAD_PS",
    "parts": ["DAD_PS_Barrel_01", "DAD_PS_Barrel_01_A", "..."],
    "legendary_compositions": ["..."]
  },
  {
    "item_id": "Armor_Shield",
    "parts": ["part_core_atl_protractor", "part_ra_armor_segment_primary", "..."],
    "legendary_compositions": []
  }
]

8.6 Actor Definitions (gbxactor.bin)

The gbxactor.bin file defines game actors: characters, AI behaviors, abilities, teams, and spawn patterns. It uses the abef format code with approximately 1,740 entries.

8.6.1 Entry Categories

Category Pattern Description
Actor_* Actor_PLD_AS_Scourge_* Player abilities, projectiles
Char_AI Char_AI Base AI character definition
Char_Enemy Char_Enemy Base enemy character
Char_NPC Char_NPC Base NPC character
Char_{Type} Char_Paladin, Char_ExoSoldier Player character types
Char_{Enemy} Char_ArmyBandit_*, Char_Psycho* Enemy types
Char_Gadget_* Char_Gadget_AutoTurret_Base Gadget/turret actors
Team_* Team_Player, Team_Bandit Team definitions
MPart_* MPartRand_Skin_Human Mesh part randomizers

Characters inherit from base types:

Char_AI
+-- Char_Enemy
|   +-- Char_ArmyBandit_SHARED
|   +-- Char_PsychoBasic
|   +-- ...
+-- Char_NPC
+-- Char_Gadget_AutoTurret_Base

Actor entries define behavior properties like PatrolPauseTime, bCanEngagePlayers, Element (NoElement, Corrosive, Cryo, Fire, Shock, Radiation), and spawn patterns. Weapon parts are not in gbxactor.bin — those are exclusively in inv.bin.


8.7 Entity Display Names (NameData)

NCS files contain NameData_* entries that map internal type names to in-game display names. Each entry follows the pattern:

NameData_<InternalType>, <UUID>, <DisplayName>

8.7.1 Boss Names

Internal Type UUID Display Name
NameData_Meathead D342D6EE47173677CE1C068BADA88F69 Saddleback
NameData_Meathead B8EAFB724DAB6362B39A5592718B54B0 The Immortal Boneface
NameData_Meathead 33D8546645185A85D4575F984C7DC44B Saddleback & Boneface
NameData_Meathead B632671F4B8F70E97E878BA8CFEEC00B Big Encore Saddleback

8.7.2 Enemy Variants

Enemies have elemental and rank variants, each with a unique UUID:

NameData_Thresher, 35D7CBFD4E844BB1624140B84DE69546, Vile Thresher
NameData_Thresher, 7505A0A34FC98F3916DABBA70974675F, Badass Thresher
NameData_Thresher, 4829F4F643423CB6F1F144B4F5A2F2CB, Burning Badass Thresher
NameData_Thresher, 2A0DEEE34653F2E0BD3C8BABC4D1353D, Boreal Badass Thresher

NameData_Bat, 160168F945945478127AD496A3BB0673, Badass Kratch
NameData_Bat, 52A45B3F42B1A9480C36188401A6C801, Vile Kratch
NameData_Bat, 05E8994641C36AF561B109ACD197D81D, Airstrike Kratch

8.7.3 Boss Replay and Challenge Text

Table_BossReplay_Costs entries reference boss display names with location context:

Table_BossReplay_Costs, 2DCA8E674F8F83E700B52B959C65C2D2, Meathead Riders: Saddleback, The Immortal Boneface

UVH challenge strings embed boss names with their locations:

UVH_Rankup_2_Challenges, ..., Kill Bramblesong in UVH 1 (Abandoned Auger Mine, Stoneblood Forest, Terminus Range).
UVH_Rankup_2_Challenges, ..., Kill Bio-Thresher Omega in UVH 1 (Fades District, Dominion).
UVH_Rankup_4_Challenges, ..., Kill Mimicron in UVH 3 (Order Bunker, Idolator's Noose, Fadefields).

8.7.4 Internal to Display Name Mapping

Known boss translations from itempoollist internal names to NameData display names:

Internal Name (itempoollist) Display Name (NameData)
MeatheadRider_Jockey Saddleback
Thresher_BioArmoredBig Bio-Thresher Omega
Timekeeper_Guardian Guardian Timekeeper
BatMatriarch Skyspanner Kratch
TrashThresher Sludgemaw
StrikerSplitter Mimicron
Destroyer Bramblesong

8.7.5 Extracting NameData

# Get all NameData entries
strings /path/to/ncs_native/*/*.bin | grep "^NameData_" | sort -u

# Get specific boss type mappings
strings /path/to/ncs_native/*/*.bin | grep "^NameData_Meathead"

# Get challenge text with boss names
strings /path/to/ncs_native/*/*.bin | grep "UVH.*Kill"

8.8 NCS Manifest

Each pak file contains an NCS manifest at the _NCS/ path. The manifest lists every NCS chunk in the pak and provides the index needed to locate each chunk.

8.8.1 Manifest Header

Offset  Size  Type   Description
------  ----  ----   -----------
0x00    5     bytes  Magic: "_NCS/" (0x5f 0x4e 0x43 0x53 0x2f)
0x05    1     u8     Null terminator
0x06    2     u16    Entry count (little-endian)
0x08    2     u16    Unknown (typically 0x0000)
0x0a    var   Entry  Entry records

8.8.2 Manifest Entry

length (u32) | filename (length-1 bytes) | null (u8) | index (u32)

Sort entries by index to get the correct order matching NCS chunk offsets in the pak file.


8.9 DataTable Relationship

NCS files contain serialized DataTable rows that reference schemas in .uasset files. The GUID portion of a schema name matches across both formats:

Schema file (Struct_DedicatedDropProbability.uasset):

{
  "name": "Primary_2_A7EABE6349CCFEA454C199BC8C113D94",
  "value_type": "Double",
  "float_value": 0.0
}

NCS reference:

Table_DedicatedDropProbability
Prim2_A7EABE6349CCFEA454C199BC8C113D94

Numeric values (weights, probabilities) are stored as strings in NCS: "0.200000", "1.500000". The binary section’s bit-packed indices point into the string table where these values live.


8.10 Known File Types

Type Description Count
achievement Achievement definitions 1
aim_assist_parameters Aim assist config 1
ainodefollowsettings AI follow settings 1
ainodeLeadsettings AI lead settings 1
attribute Game attributes ~10
audio_event Audio event mappings 1
coordinated_effect_filter Effect filters 1
gbx_ue_data_table Gearbox data tables many
gbxactor Actor definitions 1
inv Inventory part definitions 1
itempool Item pool definitions many
itempoollist Item pool lists (boss drops) many
loot_config Loot configuration many
Mission Mission data many
preferredparts Part preferences 1
trait_pool Trait pool definitions many
vending_machine Vending inventory many

8.10.1 Key Files for Loot Analysis

File Purpose
itempoollist.bin Boss-to-legendary mappings. ItemPoolList_<BossName> records with dedicated drops.
itempool.bin General item pools: rarity weights, world drops, Black Market items.
loot_config.bin Global loot configuration parameters.
preferredparts.bin Part preferences for weapon/gear generation.
inv.bin Complete inventory definitions: parts, compositions, serial indices.

8.10.2 Extracting Drop Information

# Generate drops manifest from NCS data
bl4 drops generate "/path/to/ncs_native" -o share/manifest/drops.json --manifest-dir share/manifest

# Find where an item drops
bl4 drops find hellwalker

# List drops from a specific boss
bl4 drops source Timekeeper

See Appendix C: Loot System Internals for detailed drop table documentation.


8.11 Type Prefixes

Some string table values carry single-letter type prefixes:

Prefix Type Example
T Text/String Tnone = string “none”
b Base/Boolean context-dependent
F Float context-dependent

8.12 Worked Example: achievement.bin

To tie the format together, here’s a walkthrough of achievement.bin (576 bytes decompressed).

8.12.1 File Layout

0x000-0x00F ( 16 bytes): BlobHeader (entry_count=2, flags=0, string_bytes=13)
0x010-0x01C ( 13 bytes): Header strings: "achievement"
0x01D        (  3 bytes): TypeCodeTable header: type_code_count=3, type_index_count=3
0x020        (  3 bytes): Type codes: "abj"
0x023        (  2 bytes): Bit matrix (3×3 = 9 bits, padded to 2 bytes)
0x025+       (variable):  Three string blocks (value strings, value kinds, key strings)
...          (remaining):  Binary data (bit-packed tables, records, entries)

8.12.2 Parsed Output

The parser produces a single achievement table with one record containing 6 tags and 5 entries:

{
  "achievement": {
    "name": "achievement",
    "deps": [],
    "records": [{
      "tags": [
        {"__tag": "a", "pair": "none"},
        {"__tag": "b", "value": 1},
        {"__tag": "c", "u32_value": 1065353216, "f32_value": 1.0}
      ],
      "entries": [
        {"key": "id_achievement_10_worldevents_colosseum",
         "value": {"achievementid": "10", "achievement": "ID_Achievement_10_worldevents_colosseum"}},
        {"key": "id_achievement_11_worldevents_airship",
         "value": {"achievementid": "11", "achievement": "ID_Achievement_11_worldevents_airship"}},
        {"key": "id_achievement_12_worldevents_meteor",
         "value": {"achievementid": "12", "achievement": "ID_Achievement_12_worldevents_meteor"}}
      ]
    }]
  }
}

8.13 Compatibility Issues

8.13.1 Oodle SDK Requirements

The four files listed in the Compression section (~2.4% of all NCS files) fail to decompress with open-source Oodle implementations. They require the official Oodle SDK. All other NCS files decompress correctly with open-source tools.

8.13.2 String Validation

When parsing the string table, valid strings should be at least 2 characters long and contain no garbage characters. Pure numeric strings ("10", "24") are valid. Short strings (2–3 characters) should be all lowercase or known keywords.

Watch for these invalid patterns:

  • Mixed-case short strings like "zR" or "D3" — binary data misinterpreted as text
  • Trailing or leading spaces
  • High underscore-to-letter ratio

8.14 Future Work

Several areas of the format remain partially understood:

  1. Hash table decoding — The u32 values after ASCII field abbreviations likely form a hash lookup table, but the exact structure isn’t confirmed.
  2. Entry-to-category mapping — How the binary section maps entries to their DLC categories.
  3. Cross-file references — How NCS files reference each other (e.g., ItemPool entries pointing to ItemPoolList records).
  4. Runtime behavior — How the game engine loads and indexes NCS data at runtime.
  5. Structured section encoding — For inv.bin, the 107 bit-packed indices in the first entry map to 16 JSON fields, but the exact mapping algorithm isn’t fully decoded.
  6. Format code semantics — The format code letters (abcehijl) clearly correspond to tag types, but the full specification for each letter’s encoding rules is still being worked out.
  7. Inline entry names — The CAaB/RQJ/IEZ identifiers in inv.bin’s metadata section are parsed but their semantic meaning is unclear.

The NCS format is approximately 95% reverse-engineered. The compression layer, string tables, differential encoding, and tag-based binary section are all well understood and implemented in the bl4-ncs parser. The remaining unknowns are in the binary section’s finer details — hash table structures, cross-file reference resolution, and the exact semantics of per-entry schema encoding. Chapter 7 covers how to extract usable game data from these parsed NCS documents.