7 Chapter 5: Item Serials
The first time you see an item serial—something like @Ugr$ZCm/&tH!t{KgK/Shxu>k—it looks like line noise. Random characters that couldn’t possibly mean anything. But that string contains a complete weapon: its manufacturer, every part attached to it, the level, the random seed that determined its stats. Everything needed to reconstruct the item perfectly.
This chapter decodes how serials work. By the end, you’ll understand every transformation from that cryptic string to a fully-described weapon.
7.1 What’s Encoded in a Serial
A serial is self-contained. Given just the string, the game can reconstruct the item completely—no external references needed. If you copy a serial from one save file into another, the recipient gets an exact duplicate of the weapon. This is an internal encoding detail, not something the game exposes to players, but understanding it is what makes save editing and item analysis possible.
Inside that string: - Item type (weapon, shield, class mod) - Manufacturer - Level - Element type (Kinetic, Corrosive, Shock, Radiation, Cryo, Fire) - Every part (barrel, grip, scope, magazine) - Random seed for stat calculations - Additional flags (some correlate with rarity in database)
The encoding is compact. A 40-character serial describes an item that would need hundreds of bytes in a more verbose format.
7.2 The Decoding Pipeline
Serials transform through multiple stages. Understanding each stage reveals how the pieces fit together.
flowchart LR
A["@Ugr$ZCm/&tH!..."] -->|Strip prefix| B["gr$ZCm/&tH!..."]
B -->|Base85 decode| C["[0x84, 0xA5, ...]"]
C -->|Bit-mirror| D["[0x21, 0xA5, ...]"]
D -->|Parse bitstream| E["Category: 269\nLevel: 33\nParts: [...]"]
The prefix @U marks this as a BL4 serial. After stripping the two-character prefix, everything else is Base85-encoded binary data. The character at position 3 (the first Base85 character) varies based on the magnitude of the first encoded value—it is NOT a type discriminator, despite appearing to correlate with item types at first glance.
7.3 Base85: Custom Alphabet
BL4 doesn’t use standard ASCII85. It uses a custom 85-character alphabet:
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{/}~
Every 5 characters encode 4 bytes. The math: 85⁵ ≈ 4.4 billion, which fits in 32 bits (4 bytes) with room to spare.
To decode, look up each character’s position in the alphabet, combine them as a base-85 number, then extract 4 bytes big-endian:
Characters: g r $ Z C
Positions: 42 53 64 35 12
Value = 42×85⁴ + 53×85³ + 64×85² + 35×85 + 12
= 2,225,440,262
Bytes: [0x84, 0xA5, 0x86, 0x06]
7.4 Bit Mirroring: The Obfuscation Layer
After Base85 decoding, each byte gets bit-reversed. 0x87 (binary 10000111) becomes 0xE1 (binary 11100001).
fn mirror_byte(b: u8) -> u8 {
let mut result = 0;
for i in 0..8 {
if (b >> i) & 1 == 1 {
result |= 1 << (7 - i);
}
}
result
}7.5 Bit Assembly: MSB Stream, LSB Data
After mirroring, the bytes form a bitstream that’s read MSB-first (bit 7 of each byte first). But data values within the stream are encoded LSB-first — the first bit read is the least significant bit of the value.
This means framing bits (prefixes, continuation flags, magic header) work correctly as-is, but multi-bit data values must have their bits reversed after reading. Each VarInt nibble (4 bits) and each VarBit length/value field gets its bits reversed within its width.
For example, reading 4 bits that come out as 1000 (8) from the stream actually represents the value 0001 (1) after reversal. This reversal is applied per-nibble for VarInts and per-field for VarBits.
7.6 Token Parsing: The Real Structure
The first 7 bits must be 0010000 (0x10)—a magic number validating this as a proper serial.
After the magic header, the stream contains tokens identified by prefix bits:
| Prefix | Token Type | Purpose |
|---|---|---|
00 |
Separator | Hard boundary between sections |
01 |
SoftSeparator | Softer boundary (like commas) |
100 |
VarInt | Variable-length integer |
101 |
Part | Part reference with optional value |
110 |
VarBit | Bit-length-prefixed integer |
111 |
String | Length-prefixed ASCII string |
VarInt encodes integers in nibbles (4-bit chunks). Each nibble has 4 bits of value plus 1 continuation bit. Keep reading nibbles until the continuation bit is 0. Each nibble’s bits are reversed after reading (MSB stream → LSB data).
VarBit starts with a 5-bit length (reversed), then that many bits of data (also reversed). More efficient for known-size values.
Part tokens reference parts by index, optionally with associated values. {42} means part index 42, {42:7} means part 42 with value 7.
7.7 Item Type: Determined by First Token
The actual item type is determined by parsing the first token after the magic header:
| First Token | Item Type | What It Contains |
|---|---|---|
VarInt (prefix 100) |
Weapon | Pistols, shotguns, rifles, SMGs, snipers |
VarBit (prefix 110) |
Equipment | Shields, grenades, class mods, gadgets |
This means two serials with different third characters might represent the same category of item. Always determine type from the bitstream, not the character.
7.8 Two Serial Formats
BL4 uses two distinct token structures, distinguished by the first token after the 7-bit magic header:
7.8.1 Weapon Format (VarInt-first)
Weapons start with a VarInt encoding a combined manufacturer/weapon-type ID:
[0] VarInt: manufacturer_weapon_id (e.g., 4 = Jakobs Pistol, 22 = Ripper SMG)
[1] SoftSeparator
[2] VarInt: 0
[3] SoftSeparator
[4] VarInt: 1
[5] SoftSeparator
[6] VarInt: level_code <- LEVEL ENCODED HERE
[7] Separator
[8] VarInt: 2
[9] SoftSeparator
[10] VarInt: seed <- Random seed for stats
[11] Separator
[12] Separator
[13+] Part tokens...
7.8.2 Equipment Format (VarBit-first)
Equipment (shields, grenades, class mods) starts with a VarBit encoding the category:
[0] VarBit: category_identifier <- Category * divisor
[1] SoftSeparator
[2] VarInt: 0
[3] SoftSeparator
[4] VarInt: 1
[5] SoftSeparator
[6] VarInt: level_code <- LEVEL ENCODED HERE
[7] Separator
[8] VarInt: seed
[9] SoftSeparator
[10] VarInt: (varies)
[11+] More data and parts...
For VarBit-first serials, the category is extracted using a divisor:
Category ≈ first_varbit / 384 (most equipment)
Category ≈ first_varbit / 8192 (weapons encoded as VarBit)
7.9 Part Group IDs (Categories)
The Part Group ID (also called Category ID) determines which part pool to use for decoding. Each ID corresponds to a manufacturer/weapon-type combination.
For weapons, the first VarInt (serial ID) maps directly to the NCS category. The bl4 tools handle this via serial_id_to_parts_category(), but for most weapons the serial ID matches the NCS category.
Pistols (2-6):
| ID | Manufacturer | Code |
|---|---|---|
| 2 | Daedalus | DAD_PS |
| 3 | Jakobs | JAK_PS |
| 4 | Order | ORD_PS |
| 5 | Tediore | TED_PS |
| 6 | Torgue | TOR_PS |
Shotguns (7-12):
| ID | Manufacturer | Code |
|---|---|---|
| 7 | Ripper | BOR_SG |
| 8 | Daedalus | DAD_SG |
| 9 | Jakobs | JAK_SG |
| 10 | Maliwan | MAL_SG |
| 11 | Tediore | TED_SG |
| 12 | Torgue | TOR_SG |
Assault Rifles (13-18, 27):
| ID | Manufacturer | Code |
|---|---|---|
| 13 | Daedalus | DAD_AR |
| 14 | Tediore | TED_AR |
| 15 | Order | ORD_AR |
| 17 | Torgue | TOR_AR |
| 18 | Vladof | VLA_AR |
| 27 | Jakobs | JAK_AR |
Snipers (16, 23-26):
| ID | Manufacturer | Code |
|---|---|---|
| 16 | Vladof | VLA_SR |
| 23 | Ripper | BOR_SR |
| 24 | Jakobs | JAK_SR |
| 25 | Maliwan | MAL_SR |
| 26 | Order | ORD_SR |
SMGs (19-22):
| ID | Manufacturer | Code |
|---|---|---|
| 19 | Ripper | BOR_SM |
| 20 | Daedalus | DAD_SM |
| 21 | Maliwan | MAL_SM |
| 22 | Vladof | VLA_SM |
7.10 Part Indices Are Context-Dependent
Part token {4} doesn’t mean the same part across all weapons. The index is relative to the Part Group. Index 4 on a Vladof SMG might be a specific barrel, while index 4 on a Jakobs Pistol is something completely different.
This is why you must decode the Part Group ID first. Without knowing which pool you’re indexing into, part tokens are meaningless.
7.11 Level Encoding
Level is encoded as a VarInt at position 6 in the token list for both weapon and equipment formats. For equipment, the level is stored as level - 1 (0-indexed).
7.12 Element Encoding
Element types are encoded as Part tokens. The element part index maps to the element type through the parts database — element parts have names like Kinetic, Corrosive, Fire, etc.
Multi-element weapons contain multiple element part tokens:
Parts: ... Corrosive, Kinetic, Radiation ... → Three elements
7.13 Decoding a Serial Manually
Let’s walk through @Ugr$ZCm/&tH!t{KgK/Shxu>k:
Step 1: Structure - Prefix: @U (stripped) - Base85 data: gr$ZCm/&tH!...
Step 2: Base85 decode First 5 characters gr$ZC: - Positions: 42, 53, 64, 35, 12 - Value: 2,225,440,262 - Bytes: [0x84, 0xA5, 0x86, 0x06]
Continue for remaining characters.
Step 3: Bit-mirror each byte
Original: 84 A5 86 06 ...
Mirrored: 21 A5 61 60 ...
Step 4: Parse bitstream
Binary: 00100001 10100101 01100001 ...
└──────┘ └────────────────...
Magic Tokens begin
(0x10)
First token after magic: prefix 110 = VarBit - 5-bit length (reversed): 9 - 9 bits of data (reversed): 269
Category = 269 (Vladof Repair Kit)
The bl4 tool handles all this:
bl4 serial decode '@Ugr$ZCm/&tH!t{KgK/Shxu>k'
# Vladof Repair Kit (269) ✓
# Level: 337.14 Decoding Examples
7.14.1 Weapon Serial
Serial: @UgxFw!2}TYgOs)+YRG}7?s3AisQ8!UBQ8Q6BQDIPXP<2qdQ2P)
Decoded tokens:
22 , 0 , 1 , 50 | 2 , 3417 | | {1} {2} {5} {4} {1:12} {68} {75} {72} {73} {74} {16} {25} {26} {45} {61}
- First VarInt (22): Ripper SMG
- 4th VarInt (50): Level 50
- Seed (3417): Random seed after first separator
- Part tokens resolve to barrels, body parts, grips, elements, and legendary composition
7.14.2 Equipment Serial
Serial: @Ugr%Scm/)}}$pj({qzigfrP>z<v^$y<L5*r(1po
Decoded tokens:
321 , 0 , 1 , 50 | 9 , 1 | 2 , 2880 | | {7} {10} {246:24} {237} , 9 2 | {6} {246:10}
- First VarBit (321): Torgue Shield
- Level: 50
- Part tokens: legendary rarity component, body armor, stat modifiers, unique legendary part
7.15 Exercises
Exercise 1: Identify Item Types
Given these serials, what category is each? 1. @Uge8aum/(OZ$pj+I_5#Y(pw{;WbgA{xWRhC/ 2. @UgxFw!2}TYgOs)+YRG}7?s3AisQ8!UBQ8Q6BQDIPXP<2qdQ2P) 3. @Ugr%Scm/)}}$pj({qzigfrP>z<v^$y<L5*r(1po
Exercise 2: Decode a Manufacturer
Use bl4 serial decode on a weapon serial. What Part Group ID does it use? What manufacturer does that correspond to?
Exercise 3: Compare Two Items
Find two similar weapons in your inventory. Decode both serials. Which tokens differ? Can you correlate the differences to visible stats?
Exercise 1 Answers
Decode each serial with bl4 serial decode:
- First token is VarBit (272) → Order Grenade Gadget
- First token is VarInt (22) → Ripper SMG
- First token is VarBit (321) → Torgue Shield
7.16 What’s Next
Now that we understand how serials encode items, we need to understand the NCS format that stores part definitions, item pools, and loot configuration. NCS is the foundational data format that makes serial decoding meaningful.
Next: Chapter 6: NCS Format