6  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.


6.1 What’s Encoded in a Serial

A serial is self-contained. Given just the string, the game can create an identical item anywhere—your inventory, a friend’s inventory, another platform entirely. This is how “gun codes” work in Borderlands communities. Copy a serial, share it, and the recipient gets the exact same weapon.

Inside that string: - Item type (weapon, shield, class mod) - Manufacturer - Level - Every part (barrel, grip, scope, magazine) - Random seed for stat calculations - Any special modifiers

The encoding is compact. A 40-character serial describes an item that would need hundreds of bytes in a more verbose format.


6.2 The Decoding Pipeline

Serials transform through multiple stages. Understanding each stage reveals how the pieces fit together.

"@Ugr$ZCm/&tH!..."  →  Strip "@U" prefix
"gr$ZCm/&tH!..."    →  Base85 decode to bytes
[0x84, 0xA5, ...]   →  Bit-mirror each byte
[0x21, 0xA5, ...]   →  Parse as bitstream tokens
{Category: 22, Level: 50, Parts: [...]}

The prefix @U marks this as a BL4 serial. The third character indicates item type—r for a weapon, e for equipment, and so on. After stripping the prefix, everything else is Base85-encoded binary data.


6.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]

6.4 Bit Mirroring: The Obfuscation Layer

After Base85 decoding, each byte gets bit-reversed. 0x87 (binary 10000111) becomes 0xE1 (binary 11100001).

Why? Probably obfuscation. It makes casual inspection harder and might relate to how the game’s internal serialization works. For our purposes, it’s just another step to reverse:

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
}

6.5 Token Parsing: The Real Structure

The mirrored bytes form a bitstream parsed MSB-first. 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.

VarBit starts with a 5-bit length, then that many bits of data. 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.


6.6 Item Type: Determined by First Token, Not Character

Important discovery: The third character in a serial (like b, e, r) is NOT an explicit type encoding. It’s simply a byproduct of Base85-encoding the first token’s bits. 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 “type characters” might represent the same category of item, and vice versa. Always determine type from the bitstream, not the character.

6.7 Two Serial Formats

BL4 uses two distinct token structures, distinguished by the first token after the 7-bit magic header:

6.7.1 Weapon Format (VarInt-first)

Weapons start with a VarInt encoding a combined manufacturer/weapon-type ID:

[0] VarInt: manufacturer_weapon_id   (e.g., 2 = Jakobs Pistol, 14 = Ripper Shotgun)
[1] SoftSeparator
[2] VarInt: 0
[3] SoftSeparator
[4] VarInt: 8
[5] SoftSeparator
[6] VarInt: level_code               <- LEVEL ENCODED HERE
[7] Separator
[8] VarInt: 4
[9] SoftSeparator
[10] VarInt: seed                    <- Random seed for stats
[11] Separator
[12] Separator
[13+] Part tokens...

6.7.2 Equipment Format (VarBit-first)

Equipment (shields, grenades, class mods) starts with a VarBit encoding the category:

[0] VarBit: category_identifier      <- Category * divisor
[1] Separator
[2] VarBit: level_code               <- LEVEL ENCODED HERE
[3] Separator
[4] String: (often empty)
[5] VarInt: (varies)
[6+] More data and parts...

For VarBit-first serials, the category is extracted using a divisor:

Category ≈ first_varbit / 385   (most equipment)
Category ≈ first_varbit / 8192  (some shields)

The divisor isn’t exact—categories are approximate matches. The bl4 tools handle this automatically.

6.8 Type-Aware Category Lookups

Critical: Category numbers overlap between item types. The same category number means different things for different item types.

For example, category 20: - For weapons: Daedalus SMG - For r-type shields: Energy Shield

This means you must know the item type before interpreting the category. The bl4 tools use type-aware lookup:

pub fn category_name_for_type(item_type: char, category: i64) -> Option<&'static str> {
    match item_type {
        'r' => SHIELD_CATEGORY_NAMES.get(&category)
            .or_else(|| CATEGORY_NAMES.get(&category)),
        _ => CATEGORY_NAMES.get(&category),
    }
}

Known shield categories (r-type items): | Category | Type | |———-|——| | 16 | Energy Shield | | 20 | Energy Shield | | 21 | Energy Shield | | 24 | Energy Shield | | 28 | Armor Shield | | 31 | Armor Shield |


6.9 Decoding a Serial Manually

Let’s walk through @Ugr$ZCm/&tH!t{KgK/Shxu>k:

Step 1: Structure - Prefix: @U (stripped) - Type character at position 3: r (weapon) - 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: 16 - 16 bits of data: 180928

Part Group ID = 180928 / 8192 = 22 (Vladof SMG)

The bl4 tool handles all this:

bl4 decode '@Ugr$ZCm/&tH!t{KgK/Shxu>k'
# Output shows tokens: 180928 | 50 | {0:1} 21 {4} , 2 , , 105 102 41

6.10 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.

Important: The first VarInt in a weapon serial (the “serial ID”) is NOT the same as the Part Group ID. There’s a mapping between them. For example, serial ID 2 = Jakobs Pistol, but Part Group ID 2 = Daedalus Pistol. The bl4 tools handle this conversion automatically via serial_id_to_parts_category().

Pistols (2-7):

ID Manufacturer Code
2 Daedalus DAD_PS
3 Jakobs JAK_PS
4 Tediore TED_PS
5 Torgue TOR_PS
6 Order ORD_PS
7 Vladof VLA_PS

Shotguns (8-12):

ID Manufacturer Code
8 Daedalus DAD_SG
9 Jakobs JAK_SG
10 Tediore TED_SG
11 Torgue TOR_SG
12 Bor BOR_SG

Assault Rifles (13-18):

ID Manufacturer Code
13 Daedalus DAD_AR
14 Jakobs JAK_AR
15 Tediore TED_AR
16 Torgue TOR_AR
17 Vladof VLA_AR
18 Order ORD_AR

SMGs (20-23):

ID Manufacturer Code
20 Daedalus DAD_SM
21 Bor BOR_SM
22 Vladof VLA_SM
23 Maliwan MAL_SM

Snipers (26-29):

ID Manufacturer Code
26 Jakobs JAK_SR
27 Vladof VLA_SR
28 Order ORD_SR
29 Maliwan MAL_SR

Heavy Weapons (244-247):

ID Manufacturer Code
244 Vladof VLA_HW
245 Torgue TOR_HW
246 Bor BOR_HW
247 Maliwan MAL_HW

Shields (279-288):

ID Type Code
279 Energy Shield energy_shield
280 Bor Shield bor_shield
281-288 Manufacturer variants dad/jak/mal/ord/ted/tor/vla_shield

Equipment/Gadgets (300-409):

Gadget categories use a type/subtype pattern. The base type is category / 10 * 10, and the subtype is category % 10:

Category Type Code
300-309 Grenade Gadget grenade_gadget
310-319 Turret Gadget turret_gadget
320-329 Repair Kit repkit
330-339 Terminal Gadget terminal_gadget
400-409 Enhancement enhancement

For example, category 321 = Repair Kit (type 32), subtype 1.

The bl4 tools handle this automatically:

// For gadget range (300-399), try base type
if (300..400).contains(&category) {
    let base = category / 10 * 10;
    return CATEGORY_NAMES.get(&base);
}

6.11 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.

Common part indices (within each category):

Index Typical Part Type
2 Damage modifier
3 Crit damage modifier
4 Reload speed modifier
5 Magazine size modifier
7-10 Body variants
15-18 Barrel variants

The full parts database (share/manifest/parts_database.json) contains 2,615 parts across 53 categories, extracted from memory analysis.


6.12 Level Encoding

Level is encoded at different positions depending on item format:

  • Weapons: 4th VarInt (position 6 in token list) — direct encoding
  • Equipment: VarBit immediately after the first separator (position 2) — 0-indexed storage

6.12.1 Equipment Level Storage (0-indexed)

Equipment levels are stored as level - 1:

VarBit Value Display Level Notes
0 1 Minimum level
29 30 Mid-game
49 50 Max level
50+ Invalid Beyond current cap

Verification: All items with /)}} pattern (level 50) have VarBit=49. Tested across Throwing Knives, Energy Shields, Class Mods, and Grenades.

6.12.2 Weapon Level Encoding

Weapons use a different formula with codes that may include rarity information:

fn level_from_code(code: u64) -> Option<u8> {
    if code >= 128 {
        // High-level encoding: level = 2 * (code - 120)
        Some((2 * (code - 120)) as u8)
    } else if code <= 50 {
        // Direct encoding for levels 1-50
        Some(code as u8)
    } else {
        None  // Invalid range 51-127
    }
}
Code Level Formula
1-50 1-50 Direct (code = level)
128 16 2 × (128-120) = 16
135 30 2 × (135-120) = 30
145 50 2 × (145-120) = 50

Equipment uses VarBit for level, not VarInt. This was a key discovery—equipment items encode level as a VarBit token, while weapons use VarInt. The bl4 tools detect the format automatically by checking if the first token is VarInt or VarBit.


6.13 The UE5 Part System

Behind serials, parts are defined as UE5 objects. The GbxSerialNumberIndex structure links parts to their encoding:

GbxSerialNumberIndex
├── Category (Int64): Part Group ID
├── scope (Byte): Root=1, Sub=2
├── status (Byte): Active, Static, etc.
└── Index (Int16): Position in category

Each InventoryPartDef contains this structure plus the part’s stat modifiers, visual mesh references, and other properties.

The game’s internal registration order determines indices—not alphabetical sorting. This is why we extract mappings from memory dumps rather than inferring them from file names.


6.14 Comparing Serials

When you have two similar items and want to find what differs:

Serial 1: @Ugd$YMq/.&{!gQaYQ1)<G9C8...
Serial 2: @Ugd$YMq/.&{!gQaYQ1)<?B8b...

Decode both, align the tokens, find where they diverge. The difference reveals what that section encodes. Two weapons with identical parts but different accuracy will differ only in the accuracy-related bytes.


6.15 Practical Usage

The bl4 tool decodes serials instantly:

# Weapon
bl4 decode '@Ugb)KvFg_4rJ}%H-RG}IbsZG^E#X_Y-00'

# Output:
Serial: @Ugb)KvFg_4rJ}%H-RG}IbsZG^E#X_Y-00
Item type: b (Weapon)
Weapon: Jakobs Pistol
Level: 30
Seed: 2591
Tokens: 2 ,  0 ,  8 ,  135 | 4 ,  2591 | | {175} {4} {6} ...

Equipment items now show levels too:

# Shield
bl4 decode '@Uge98>m/)}}!c5JeNWCvCXc7'

# Output:
Serial: @Uge98>m/)}}!c5JeNWCvCXc7
Item type: e (Item)
Category: Shield Variant (289)
Level: 49
Tokens: 111296 | 49 | "" 4 | ,  | 171 | ...

# Class Mod
bl4 decode '@Uge8;)m/)@{!X>!SqTZJibf`hSk4B2r6#)'

# Output:
Serial: @Uge8;)m/)@{!X>!SqTZJibf`hSk4B2r6#)
Item type: e (Item)
Category: Paladin Class Mod (55)
Level: 50

For more detail:

bl4 decode --verbose '@Ugr$ZCm/&...'   # Shows raw bytes and bit positions
bl4 decode --debug '@Ugr$ZCm/&...'     # Shows bit-by-bit parsing

6.16 Exercises

Exercise 1: Identify Item Types

Given these serials, what category is each? 1. @Uge8jxm/)@{!gQaYMipv(G&-b*Z~_ 2. @Ugw$Yw2}TYgOvDMQhbq)?p-8<%Z7L5c7pfd;cmn_ 3. @Ug!$ZCm/&tH!t{KgK/Shxu>k

Exercise 2: Decode a Manufacturer

Use bl4 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
  1. e at position 3 → Equipment (shield/enhancement)
  2. w at position 3 → Weapon (SMG category)
  3. ! at position 3 → Class Mod

6.17 What’s Next

Serials encode items completely—but where do the part definitions come from? The Part Group IDs we use come from analyzing game data. The mappings between indices and actual parts come from memory dumps and pak file extraction.

Next, we’ll explore how to extract data from BL4’s game files, including the investigation into whether authoritative category mappings exist anywhere we can reach them.

Next: Chapter 6: Data Extraction