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:
- NCS Chunks — Oodle-compressed blocks within pak files, located via an NCS manifest
- 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[Header<br/>8 bytes] --> B[Type Name<br/>null-terminated]
B --> C[Format Section<br/>7 bytes]
C --> D[Entry Section<br/>variable]
D --> E[String Table<br/>variable]
E --> F[Control Section<br/>4 bytes]
F --> G[Category Names<br/>variable]
G --> H[Binary Data<br/>variable]
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. Every NCS file shares the same overall layout, though the details of each section vary by content type and format code.
8.3.1 Header (8 bytes)
Offset Size Type Description
------ ---- ---- -----------
0x00 6 bytes Zeros (reserved)
0x06 2 u16 Checksum/identifier (little-endian)
8.3.2 Type Name
A null-terminated ASCII string identifying the content type. This is the first thing you read after the header, and it determines how to interpret everything that follows.
Common types: achievement, ainodefollowsettings, attribute, gbx_ue_data_table, gbxactor, inv, itempool, itempoollist, loot_config, preferredparts.
8.3.3 Format Section (7 bytes)
Offset Size Type Description
------ ---- ---- -----------
0x00 3 bytes Format prefix (determines structure variant)
0x03 4 ascii Format code (e.g., "abjx", "abij")
The format prefix signals the structural variant:
| Prefix | Structure |
|---|---|
03 03 00 |
Compact format (no GUID) |
04 xx 00 |
Extended format (with 4-byte GUID) |
The format code is a short ASCII string that describes the tag types used in the binary section. Each letter corresponds to a different encoding strategy:
| Code | Description | Examples |
|---|---|---|
abjx |
Extended entries with dependents | achievement, preferredparts |
abij |
Indexed entries | itempoollist, aim_assist |
abjl |
Labeled entries | inv_name_part |
abhj |
Hash-indexed entries | inv_params |
abpe |
Property-based entries | audio_event |
abcefhijl |
Full tag-based encoding | inv (inventory) |
For complex NCS files like inv.bin, the format code lists every tag type used in the binary section. The code abcefhijl means the binary data uses tags a, b, c, e, f, h, i, j, and l.
8.3.4 Entry Section
The entry section sits between the format code and the string table. Its encoding varies by format code and content type — this is one of the trickier parts of the format to parse.
The section starts with an entry marker followed by field count information:
Offset Size Type Description
------ ---- ---- -----------
0x00 1 u8 Entry marker (typically 0x01)
0x01 1 u8 First field value
0x02 1 u8 Field count marker (0xc0 | count)
The field count encoding uses 0xc0 | field_count:
0xc2= 2 fields per entry0xc3= 3 fields per entry
But this is only the “simple” variant. The entry section has at least five encoding patterns depending on the format code:
Simple format (01 XX c?) — used by abjx compact files like achievement:
[0x01] [string_count] [0xc0 | field_count]
Example: 01 0a c2 = 10 strings, 2 fields per entry
Extended format (XX XX 01) — used by abjx with larger string counts:
[string_count] [field_count] [0x01]
Example: 1d 06 01 = 29 strings, 6 fields
Direct format (01 XX YY) — variable marker byte:
[0x01] [field_count] [marker]
Example: 01 03 30 = 3 fields, marker 0x30
Variable format (01 XX 7f) — special marker for non-standard entries:
[0x01] [count] [0x7f]
Example: 01 0b 7f = 11 entries with special handling
Hash-indexed format — entry marker 0xb0 instead of 0x01, used by abhj files.
Some NCS files contain inline entry names in the metadata bytes between the format code and string table. In inv.bin, the 28 bytes at offset 0x100-0x11b decode to short identifiers like CAaB, RQJ, and IEZ. These aren’t stored in the string table — they’re an optimization for short or special identifiers that the parser surfaces as entry names.
Each entry type within an NCS file can have its own field schema. The first entry doesn’t define a template for the rest. For example, in inv.bin:
- Entry “ammo” (index 0): 16 fields including
rarity,pingfeedback,cantpickupprompt - Entry “ammo_assaultrifle” (index 1): 9 fields including
basetype,body,aspects - Entry “classmod” (index 6): 33 fields including
class,slot,parttypes
The binary section encodes these heterogeneous schemas through its tag-based system.
8.3.5 String Table
The string table contains null-terminated strings for entry names and values. Strings are interleaved — entry name, then its field values, then the next entry name, and so on:
[Entry 1 Name\0][Value 1\0][Value 2\0]...
[Entry 2 Name\0][Value 1\0][Value 2\0]...
The string table is bounded by the entry section on one side and the control section marker (01 00 XX YY) on the other.
8.3.5.1 Differential Encoding
Entry names use differential encoding to compress common prefixes. The first entry stores the full name (sometimes abbreviated), and subsequent entries encode only the changed suffix:
| Entry | Encoded String | Reconstructed |
|---|---|---|
| 1 | ID_A_10_worldevents_colosseum |
ID_Achievement_10_worldevents_colosseum |
| 2 | 1airship |
ID_Achievement_11_worldevents_airship |
| 3 | 2meteor |
ID_Achievement_12_worldevents_meteor |
| 4 | 1224_missions_side |
ID_Achievement_24_missions_side |
| 5 | 9main |
ID_Achievement_29_missions_main |
The algorithm: the first entry provides the base string (with abbreviation expansion like ID_A_ to ID_Achievement_). Subsequent entries use leading digits to indicate where the new suffix starts, with the remaining text replacing the final segment.
8.3.5.2 Packed String Values
When the field count marker indicates multiple fields per entry, values are stored consecutively. Values can be packed together to save space — a numeric value and the next entry’s differential name concatenated into a single string:
String table for achievement (2 fields per entry):
[0] 'ID_A_10_worldevents_colosseum' -> Entry 0 name
[1] '10' -> Entry 0 achievementid
[2] '1airship' -> Entry 1 name (diff)
[3] '11' -> Entry 1 achievementid
[4] '2meteor' -> Entry 2 name (diff)
[5] '1224_missions_side' -> PACKED: Entry 2 id + Entry 3 name
[6] '24' -> Entry 3 achievementid
[7] '9main' -> Entry 4 name (diff)
[8] '29' -> Entry 4 achievementid
The string '1224_missions_side' packs two values: '12' (achievementid for Entry 2) and '24_missions_side' (differential name for Entry 3). The parser splits on the expected field width — achievement IDs are 2-digit numbers, so extract the first 2 characters as the value and treat the remainder as the next entry’s name.
8.3.5.3 Value Packing Patterns
NCS uses aggressive packing wherever values share boundaries:
- Numeric prefix + string suffix:
"1airship"splits to(1, "airship"),"5true"splits to(5, true) - Float + string:
"0.175128Session"splits to(0.175128, "Session") - Multiple numerics:
"1224_missions_side"splits to(12, "24_missions_side")
The binary section’s field abbreviations indicate expected types, which determine how to split packed values.
8.3.5.4 String Table for Large Files
Large NCS files (like inv.bin with 18,393 strings) use three separate string blocks instead of a single interleaved table. Each block stores a u64 byte-length prefix followed by the string data:
- Value strings — the actual data values
- Value kinds — type annotations for values
- Key strings — entry and field names
The parser performs string repair on these blocks, handling cases where null terminators are missing or strings are truncated.
8.3.6 Control Section (4 bytes)
Marks the transition from the string table to category names and binary data:
Offset Size Type Description
------ ---- ---- -----------
0x00 1 u8 Marker (always 0x01)
0x01 1 u8 Separator (always 0x00)
0x02 1 u8 Entry/index count
0x03 1 u8 Type/mode byte
The type/mode byte indicates the binary section’s encoding:
| Value | ASCII | Interpretation |
|---|---|---|
0x62 |
‘b’ | Text-based format |
0xe9 |
— | Encoded format (0xe0 | 0x09) |
0x06 |
— | Simple format |
To find the control section, scan for the byte pattern 01 00 followed by a valid count byte, then verify the subsequent bytes contain known category name strings like “none” or “base”.
8.3.7 Category Names
DLC and content pack identifiers, stored as null-terminated strings after the control section:
none\0
base\0
basegame\0
These indicate which DLC or content pack an entry belongs to. Category names are part of the combined string table used for binary section indexing — they occupy indices after the primary string table entries.
8.3.8 Field Abbreviations
Some format variants include compact field name abbreviations between the category names and binary data. These are distinct from category names by their use of . or ! characters:
corid_aid.a!
This encodes field names compactly: cor (correlation), id, aid (achievementid), .a (field separator), ! (terminator, stripped when indexing). Field abbreviations are part of the combined string table and occupy indices after category names.
8.4 Binary Section
The binary section is where NCS gets complicated. It contains the actual structured data — field values, type information, and cross-references — in a space-efficient binary encoding. The format varies significantly across NCS file types.
8.4.1 Combined String Table
Before diving into binary encodings, understand how indexing works. The binary section references strings by index into a combined string table built from multiple sources:
- Primary strings (indices 0 to N-1) — from the string table section
- Category names (indices N to N+K-1) — DLC identifiers like “none”, “base”
- Field abbreviation (next index) — if present, like “corid_aid.a”
- Type name (final index) — the schema identifier like “achievement”
For achievement.bin with 10 primary strings, 3 category names, 1 abbreviation, and the type name, the combined table has 15 entries. Index 13 points to “achievement” (the type name), which serves as the table_id.
8.4.2 Simple Binary Format (abjx)
For compact abjx files (like achievement), the binary section has this structure:
Offset Size Description
------ ---- -----------
0x00 12 ASCII field abbreviations (e.g., 'corid_aid.a!')
0x0c 4 u32 offset/count value
0x10 4 u32 secondary value
0x14 var Hash table or lookup data
... var Entry metadata (indices, flags)
... var Tail section with packed values
-4 4 Checksum (FNV-1a or CRC)
The section divider 7a 00 00 00 00 00 marks a transition point — before it: entry data markers (XX XX 00 00 patterns); after it: bit-packed binary data.
8.4.2.1 Bit-Packed String Indices
After the divider, the first ~32 bytes contain bit-packed indices into the combined string table. The bit width is determined by the table size:
- 6 strings -> 3 bits per index
- 14 strings -> 4 bits per index
- 18 strings -> 5 bits per index
Example from achievement.bin (4-bit indices, 14-entry table):
Raw bytes: 1d 15 d7 55 e3 fb 2d fb...
Decoded: 13, 1, 5, 1, 7, 13, 5, 5, 3, 14, 11, 15...
Index 13 maps to “achievement” (the table_id). Index 1 maps to “10” (a field value). The bit-packed section encodes the entire entry-to-field mapping in a few dozen bytes.
8.4.2.2 Structured Metadata Section
Following the bit-packed indices is a byte-based metadata section. Values are mostly in the 0x08–0x30 range, with 0x28 acting as a separator between entry groups:
15 11 13 14 14 14 12 25 08 08 | 28 | 13 0a 19 | 28 | 13 | 28 | 13 | 28 | 12 | 28 28 | 00 00
Entry 0: [21, 17, 19, 20, 20, 20, 18, 37, 8, 8]
Entry 1: [19, 10, 25]
Entry 2: [19]
Entry 3: [19]
Entry 4: [18]
The number of entry groups matches the number of entries. The 0x00 0x00 sequence terminates the section.
8.4.2.3 Compact Binary Format
Some NCS files (e.g., rarity) skip the separator-based format and use fixed-width records instead. This format is signaled by a 0x80 0x80 header after the bit-packed indices:
[Bit-packed indices] [0x80 0x80] [fixed-width records] [00 00] [tail data]
Each entry occupies a fixed number of bytes (typically 2) without separators:
rarity.bin (10 entries, 2 bytes each):
0x80 0x80 | 13 0f | 08 11 | 0d 0d | 23 08 | 08 24 | 27 11 | 0e 22 | 13 0d | 1c 1b | 26 27 | 00 00
Entry0 Entry1 Entry2 Entry3 Entry4 Entry5 Entry6 Entry7 Entry8 Entry9 Term
8.4.3 Extended Binary Format (abij)
Extended abij files (like itempoollist) store full field names instead of abbreviations:
Offset Size Description
------ ---- -----------
0x00 var Category names (none, base, basegame, ...)
var var Field names as null-terminated strings
var 4 u32 start marker
... var Hash/lookup tables
-4 4 Checksum
Field names section:
none\0
base\0
basegame\0
pad\0
cor_dbinstance\0
structtype\0
hand\0
items\0
rowname\0
columnvalue\0
pstbms\0
8.4.4 Tag-Based Binary Format
The most complex NCS files — inv.bin, gbxactor.bin — use a tag-based encoding system. Each byte in the binary section acts as a type tag that determines how to interpret the following data:
| Tag | ASCII | Type | Description |
|---|---|---|---|
0x61 |
a |
Pair | String reference (key-value) |
0x62 |
b |
U32 | 32-bit unsigned integer |
0x63 |
c |
U32F32 | Dual interpretation: u32 and f32 |
0x64–0x66 |
d–f |
List | List terminated by “none” |
0x68 |
h |
— | Hash/reference |
0x69 |
i |
— | Index |
0x6a |
j |
— | Jump/offset |
0x6c |
l |
— | Length-prefixed |
0x70 |
p |
Variant | 2-bit subtype selector |
0x7a |
z |
End | Record terminator |
This format uses remap tables (FixedWidthIntArrays with 24-bit count + 8-bit width) for index compression and variable-length encoding for complex hierarchical data structures.
8.4.5 Hash Function
Field names are hashed using FNV-1a 64-bit for lookup tables:
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.4.6 Format Variations Summary
| Format | Entry Marker | Control Pattern | Field Names |
|---|---|---|---|
abjx compact |
0x01 |
01 00 xx yy |
ASCII abbreviations in binary |
abij extended |
0xb0 |
xx yy 01 |
Full names as strings |
abhj |
0xb0 |
varies | Long ASCII abbreviation |
abcefhijl |
0x01 |
varies | Tag-based with type codes |
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 Header (8 bytes)
0x08 Type name ("inv")
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:
- blob.rs — Reads the decompressed header: entry count, flags, string byte counts. Extracts header strings (dependencies, type name).
- typecodes.rs — Builds a
TypeCodeTablefrom 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. - 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 variantDAD_PS_Barrel_01_A— Daedalus Pistol, Barrel slot, A variantJAK_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-partsOutput:
[
{
"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 TimekeeperSee 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 complete walkthrough of achievement.bin (278 bytes decompressed).
8.12.1 File Layout
0x000-0x008 ( 8 bytes): Header
0x009-0x015 ( 12 bytes): Type name: 'achievement'
0x015-0x01c ( 7 bytes): Format: 03 03 00 abjx
0x01c-0x01f ( 3 bytes): Entry marker + field info: 01 0a c2
0x01f-0x073 ( 84 bytes): String table (entries + values)
0x073-0x077 ( 4 bytes): Control section: 01 00 0b e9
0x077-0x08a ( 19 bytes): Category names: none, base, basegame
0x08a-0x116 (140 bytes): Binary data section
8.12.2 String Table Contents
| Offset | String | Purpose |
|---|---|---|
| 0x1f | ID_A_10_worldevents_colosseum |
Entry 1 name |
| 0x3d | 10 |
Entry 1 achievementid |
| 0x40 | 1airship |
Entry 2 differential |
| 0x49 | 11 |
Entry 2 achievementid |
| 0x4c | 2meteor |
Entry 3 differential |
| 0x54 | 1224_missions_side |
Entry 3+4 packed |
| 0x67 | 24 |
Entry 4 achievementid |
| 0x6a | 9main |
Entry 5 differential |
| 0x70 | 29 |
Entry 5 achievementid |
8.12.3 Parsed Output
{
"achievement": {
"records": [{
"entries": [
{
"id_achievement_10_worldevents_colosseum": {
"achievement": "ID_Achievement_10_worldevents_colosseum",
"achievementid": "10"
}
},
{
"id_achievement_11_worldevents_airship": {
"achievement": "ID_Achievement_11_worldevents_airship",
"achievementid": "11"
}
},
{
"id_achievement_12_worldevents_meteor": {
"achievement": "ID_Achievement_12_worldevents_meteor",
"achievementid": "12"
}
},
{
"id_achievement_24_missions_side": {
"achievement": "ID_Achievement_24_missions_side",
"achievementid": "24"
}
},
{
"id_achievement_29_missions_main": {
"achievement": "ID_Achievement_29_missions_main",
"achievementid": "29"
}
}
]
}]
}
}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:
- Hash table decoding — The u32 values after ASCII field abbreviations likely form a hash lookup table, but the exact structure isn’t confirmed.
- Entry-to-category mapping — How the binary section maps entries to their DLC categories.
- Cross-file references — How NCS files reference each other (e.g., ItemPool entries pointing to ItemPoolList records).
- Runtime behavior — How the game engine loads and indexes NCS data at runtime.
- 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. - 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. - Inline entry names — The
CAaB/RQJ/IEZidentifiers ininv.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.