bl4/
serial.rs

1//! Item serial number decoding for Borderlands 4
2//!
3//! Item serials use a custom Base85 encoding with bit-packed data.
4//!
5//! Format:
6//! 1. Serials start with `@U` prefix
7//! 2. Encoded with custom Base85 alphabet
8//! 3. Decoded bytes have mirrored bits
9//! 4. Data is a variable-length bitstream with tokens
10
11use crate::parts::{
12    item_type_name, level_from_code, manufacturer_name, serial_format, serial_id_to_parts_category,
13    weapon_info_from_first_varint,
14};
15
16/// Custom Base85 alphabet used by Borderlands 4
17const BL4_BASE85_ALPHABET: &[u8; 85] =
18    b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{/}~";
19
20/// Element types for weapons
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Element {
23    Kinetic,   // ID 0
24    Corrosive, // ID 5
25    Shock,     // ID 8
26    Radiation, // ID 9
27    Cryo,      // ID 13
28    Fire,      // ID 14
29}
30
31impl Element {
32    /// Convert element ID to Element type
33    pub fn from_id(id: u64) -> Option<Self> {
34        match id {
35            0 => Some(Element::Kinetic),
36            5 => Some(Element::Corrosive),
37            8 => Some(Element::Shock),
38            9 => Some(Element::Radiation),
39            13 => Some(Element::Cryo),
40            14 => Some(Element::Fire),
41            _ => None,
42        }
43    }
44
45    /// Get element name
46    pub fn name(&self) -> &'static str {
47        match self {
48            Element::Kinetic => "Kinetic",
49            Element::Corrosive => "Corrosive",
50            Element::Shock => "Shock",
51            Element::Radiation => "Radiation",
52            Element::Cryo => "Cryo",
53            Element::Fire => "Fire",
54        }
55    }
56}
57
58/// Item rarity levels
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum Rarity {
61    Common,
62    Uncommon,
63    Rare,
64    Epic,
65    Legendary,
66}
67
68impl Rarity {
69    /// Get rarity name
70    pub fn name(&self) -> &'static str {
71        match self {
72            Rarity::Common => "Common",
73            Rarity::Uncommon => "Uncommon",
74            Rarity::Rare => "Rare",
75            Rarity::Epic => "Epic",
76            Rarity::Legendary => "Legendary",
77        }
78    }
79
80    /// Extract rarity from VarBit-first equipment format
81    /// Rarity is encoded in bits 6-7 of (first_varbit % divisor)
82    pub fn from_equipment_varbit(varbit: u64, divisor: u64) -> Option<Self> {
83        if divisor == 0 {
84            return None;
85        }
86        let remainder = varbit % divisor;
87        let rarity_bits = (remainder >> 6) & 0x3;
88        match rarity_bits {
89            0 => Some(Rarity::Common),
90            1 => Some(Rarity::Epic),      // Observed: 64 >> 6 = 1 for Epic
91            2 => Some(Rarity::Rare),      // Hypothetical
92            3 => Some(Rarity::Legendary), // Observed: 192 >> 6 = 3 for Legendary
93            _ => None,
94        }
95    }
96
97    /// Extract rarity from VarInt-first weapon format
98    /// For level codes > 145 (max level 50), rarity is encoded in the offset
99    pub fn from_weapon_level_code(code: u64) -> Option<Self> {
100        // Level codes 128-145 encode levels 16-50 (Common rarity)
101        // Codes > 145 encode level 50 + rarity
102        if code <= 145 {
103            Some(Rarity::Common)
104        } else {
105            // Known codes from database samples:
106            // 192 = Epic, 200 = Legendary
107            match code {
108                192 => Some(Rarity::Epic),
109                200 => Some(Rarity::Legendary),
110                // For unknown codes, estimate based on value range
111                146..=180 => Some(Rarity::Uncommon),
112                181..=195 => Some(Rarity::Epic),
113                196.. => Some(Rarity::Legendary),
114                _ => None,
115            }
116        }
117    }
118}
119
120/// Maximum reasonable part index. Part indices above this threshold indicate
121/// we're parsing garbage data (likely over-reading past the end of valid tokens).
122/// Based on analysis: most parts have indices < 100, max observed valid is ~300.
123const MAX_REASONABLE_PART_INDEX: u64 = 1000;
124
125/// Errors that can occur during serial decoding
126#[derive(Debug, thiserror::Error)]
127pub enum SerialError {
128    #[error("Invalid Base85 encoding: {0}")]
129    InvalidEncoding(String),
130
131    #[error("Serial too short: expected at least {expected} bytes, got {actual}")]
132    TooShort { expected: usize, actual: usize },
133
134    #[error("Unknown item type: {0}")]
135    UnknownItemType(char),
136}
137
138/// Bitstream reader for parsing variable-length tokens
139struct BitReader {
140    bytes: Vec<u8>,
141    bit_offset: usize,
142}
143
144/// Bitstream writer for encoding variable-length tokens
145struct BitWriter {
146    bytes: Vec<u8>,
147    bit_offset: usize,
148}
149
150impl BitWriter {
151    fn new() -> Self {
152        Self {
153            bytes: Vec::new(),
154            bit_offset: 0,
155        }
156    }
157
158    /// Write N bits from a u64 value (MSB-first)
159    fn write_bits(&mut self, value: u64, count: usize) {
160        for i in (0..count).rev() {
161            let bit = ((value >> i) & 1) as u8;
162            let byte_idx = self.bit_offset / 8;
163            let bit_idx = 7 - (self.bit_offset % 8); // Write from MSB (bit 7) down to LSB (bit 0)
164
165            // Extend bytes vector if needed
166            while byte_idx >= self.bytes.len() {
167                self.bytes.push(0);
168            }
169
170            if bit == 1 {
171                self.bytes[byte_idx] |= 1 << bit_idx;
172            }
173            self.bit_offset += 1;
174        }
175    }
176
177    /// Write a VARINT (4-bit nibbles with continuation bits)
178    fn write_varint(&mut self, value: u64) {
179        let mut remaining = value;
180
181        loop {
182            let nibble = remaining & 0xF;
183            remaining >>= 4;
184
185            self.write_bits(nibble, 4);
186
187            if remaining == 0 {
188                self.write_bits(0, 1); // Continuation = 0 (stop)
189                break;
190            } else {
191                self.write_bits(1, 1); // Continuation = 1 (more)
192            }
193        }
194    }
195
196    /// Write a VARBIT (5-bit length prefix + variable data)
197    fn write_varbit(&mut self, value: u64) {
198        if value == 0 {
199            self.write_bits(0, 5); // Length 0 means value 0
200            return;
201        }
202
203        // Calculate number of bits needed
204        let bits_needed = 64 - value.leading_zeros() as usize;
205        self.write_bits(bits_needed as u64, 5);
206        self.write_bits(value, bits_needed);
207    }
208
209    /// Get the final bytes (padded to byte boundary)
210    fn finish(self) -> Vec<u8> {
211        self.bytes
212    }
213}
214
215impl BitReader {
216    fn new(bytes: Vec<u8>) -> Self {
217        Self {
218            bytes,
219            bit_offset: 0,
220        }
221    }
222
223    /// Read N bits as a u64 value (MSB-first)
224    /// Bits are read from the stream and assembled with first bit = MSB
225    fn read_bits(&mut self, count: usize) -> Option<u64> {
226        if count > 64 {
227            return None;
228        }
229
230        let mut result = 0u64;
231        for _ in 0..count {
232            let byte_idx = self.bit_offset / 8;
233            let bit_idx = 7 - (self.bit_offset % 8); // Read from MSB (bit 7) down to LSB (bit 0)
234
235            if byte_idx >= self.bytes.len() {
236                return None;
237            }
238
239            let bit = (self.bytes[byte_idx] >> bit_idx) & 1;
240            result = (result << 1) | (bit as u64);
241            self.bit_offset += 1;
242        }
243
244        Some(result)
245    }
246
247    /// Read a VARINT (4-bit nibbles with continuation bits)
248    /// Format: [4-bit value][1-bit continuation]... Values assembled LSB-first.
249    /// Continuation bit 1 = more nibbles follow, 0 = stop.
250    fn read_varint(&mut self) -> Option<u64> {
251        let mut result = 0u64;
252        let mut shift = 0;
253
254        // Max 4 nibbles (16 bits total)
255        for _ in 0..4 {
256            let nibble = self.read_bits(4)?;
257            result |= nibble << shift;
258            shift += 4;
259
260            // Read continuation bit (1 = continue, 0 = stop)
261            let cont = self.read_bits(1)?;
262            if cont == 0 {
263                return Some(result);
264            }
265        }
266
267        Some(result)
268    }
269
270    /// Read a VARBIT (5-bit length prefix + variable data)
271    /// Format: [5-bit length][N-bit value]. Length 0 means value is 0.
272    fn read_varbit(&mut self) -> Option<u64> {
273        let length = self.read_bits(5)? as usize;
274        self.read_bits(length)
275    }
276
277    #[allow(dead_code)]
278    fn current_bit_offset(&self) -> usize {
279        self.bit_offset
280    }
281
282    /// Returns the number of bits remaining in the stream
283    fn remaining_bits(&self) -> usize {
284        let total_bits = self.bytes.len() * 8;
285        total_bits.saturating_sub(self.bit_offset)
286    }
287}
288
289/// Token types in the bitstream
290#[derive(Debug, Clone, PartialEq)]
291pub enum Token {
292    Separator,                             // 00
293    SoftSeparator,                         // 01
294    VarInt(u64),                           // 100 + varint data
295    VarBit(u64),                           // 110 + varbit data
296    Part { index: u64, values: Vec<u64> }, // 101 + part data
297    String(String),                        // 111 + length + ascii
298}
299
300/// Decoded item serial information
301#[derive(Debug, Clone)]
302pub struct ItemSerial {
303    /// Original ASCII85-encoded serial
304    pub original: String,
305
306    /// Raw decoded bytes
307    pub raw_bytes: Vec<u8>,
308
309    /// Item type character (r=weapon, e=equipment, etc.)
310    pub item_type: char,
311
312    /// Parsed tokens from bitstream
313    pub tokens: Vec<Token>,
314
315    /// Decoded fields (extracted from tokens)
316    /// For VarInt-first format: Combined manufacturer + weapon type ID
317    pub manufacturer: Option<u64>,
318    /// Item level (fourth VarInt for VarInt-first format), capped at 50
319    pub level: Option<u64>,
320    /// Raw decoded level before capping (if > 50, our decoding may be wrong)
321    pub raw_level: Option<u64>,
322    /// Random seed for stat rolls (second VarInt after first separator)
323    pub seed: Option<u64>,
324    /// Detected elements (from Part tokens with index 128-142)
325    pub elements: Vec<Element>,
326    /// Detected rarity (from level code or equipment VarBit)
327    pub rarity: Option<Rarity>,
328}
329
330/// Decode Base85 with custom BL4 alphabet
331fn decode_base85(input: &str) -> Result<Vec<u8>, SerialError> {
332    // Build reverse lookup table
333    let mut lookup = [0u8; 256];
334    for (i, &ch) in BL4_BASE85_ALPHABET.iter().enumerate() {
335        lookup[ch as usize] = i as u8;
336    }
337
338    let mut result = Vec::new();
339    let chars: Vec<char> = input.chars().collect();
340
341    // Process in chunks of 5 characters -> 4 bytes
342    for chunk in chars.chunks(5) {
343        let mut value: u64 = 0;
344
345        // For partial chunks, pad with highest value (84 = 'u') to make 5 chars
346        // This ensures we decode to the most significant bytes
347        for &ch in chunk.iter() {
348            let byte_val = lookup[ch as usize] as u64;
349            value = value * 85 + byte_val;
350        }
351        // Pad remaining positions with 84 (highest value)
352        for _ in chunk.len()..5 {
353            value = value * 85 + 84;
354        }
355
356        // Extract bytes from most significant first
357        let num_bytes = if chunk.len() == 5 { 4 } else { chunk.len() - 1 };
358        for i in (0..num_bytes).rev() {
359            // For partial chunks, extract from high bytes (shift by 24, 16, etc.)
360            let shift = if chunk.len() == 5 {
361                i * 8
362            } else {
363                (3 - (num_bytes - 1 - i)) * 8
364            };
365            result.push(((value >> shift) & 0xFF) as u8);
366        }
367    }
368
369    Ok(result)
370}
371
372/// Mirror bits in a byte (reverse bit order)
373/// Example: 0b10000111 -> 0b11100001
374#[inline]
375fn mirror_byte(byte: u8) -> u8 {
376    byte.reverse_bits()
377}
378
379/// Encode bytes to Base85 with custom BL4 alphabet
380fn encode_base85(bytes: &[u8]) -> String {
381    let mut result = String::new();
382
383    // Process in chunks of 4 bytes -> 5 characters
384    for chunk in bytes.chunks(4) {
385        // Build value from bytes (big-endian), pad with zeros to 4 bytes
386        let mut value: u64 = 0;
387        for &byte in chunk {
388            value = (value << 8) | (byte as u64);
389        }
390        // Pad partial chunks with zeros (shift left to fill 4 bytes)
391        if chunk.len() < 4 {
392            value <<= (4 - chunk.len()) * 8;
393        }
394
395        // Convert to 5 base85 chars, then take first N+1 for partial chunks
396        let mut chars = [0u8; 5];
397        for i in (0..5).rev() {
398            chars[i] = BL4_BASE85_ALPHABET[(value % 85) as usize];
399            value /= 85;
400        }
401
402        // Take first N+1 chars for partial chunks
403        let num_chars = if chunk.len() == 4 { 5 } else { chunk.len() + 1 };
404        for &ch in &chars[0..num_chars] {
405            result.push(ch as char);
406        }
407    }
408
409    result
410}
411
412/// Encode tokens back to bitstream bytes
413fn encode_tokens(tokens: &[Token]) -> Vec<u8> {
414    let mut writer = BitWriter::new();
415
416    // Write magic header (7 bits = 0010000)
417    writer.write_bits(0b0010000, 7);
418
419    for token in tokens {
420        match token {
421            Token::Separator => {
422                writer.write_bits(0b00, 2);
423            }
424            Token::SoftSeparator => {
425                writer.write_bits(0b01, 2);
426            }
427            Token::VarInt(v) => {
428                writer.write_bits(0b100, 3);
429                writer.write_varint(*v);
430            }
431            Token::VarBit(v) => {
432                writer.write_bits(0b110, 3);
433                writer.write_varbit(*v);
434            }
435            Token::Part { index, values } => {
436                writer.write_bits(0b101, 3);
437                writer.write_varint(*index);
438
439                if values.is_empty() {
440                    // SUBTYPE_NONE: type=0, subtype=10
441                    writer.write_bits(0, 1);
442                    writer.write_bits(0b10, 2);
443                } else if values.len() == 1 {
444                    // SUBTYPE_INT: type=1, value, 000 terminator
445                    writer.write_bits(1, 1);
446                    writer.write_varint(values[0]);
447                    writer.write_bits(0b000, 3);
448                } else {
449                    // SUBTYPE_LIST: type=0, subtype=01, values, 00 terminator
450                    writer.write_bits(0, 1);
451                    writer.write_bits(0b01, 2);
452                    for v in values {
453                        // For simplicity, encode as VarInt
454                        writer.write_bits(0b100, 3);
455                        writer.write_varint(*v);
456                    }
457                    writer.write_bits(0b00, 2); // Separator to terminate
458                }
459            }
460            Token::String(s) => {
461                writer.write_bits(0b111, 3);
462                writer.write_varint(s.len() as u64);
463                for ch in s.bytes() {
464                    writer.write_bits(ch as u64, 7);
465                }
466            }
467        }
468    }
469
470    writer.finish()
471}
472
473/// Parse tokens from bitstream
474fn parse_tokens(reader: &mut BitReader) -> Vec<Token> {
475    parse_tokens_impl(reader, false)
476}
477
478/// Parse tokens with optional debug output
479fn parse_tokens_impl(reader: &mut BitReader, debug: bool) -> Vec<Token> {
480    let mut tokens = Vec::new();
481
482    // Verify magic header (7 bits = 0010000)
483    if let Some(magic) = reader.read_bits(7) {
484        if magic != 0b0010000 {
485            if debug {
486                eprintln!("Warning: Invalid magic header: {:07b}", magic);
487            }
488            return tokens;
489        }
490        if debug {
491            eprintln!("[bit {:3}] Magic header OK", reader.bit_offset);
492        }
493    } else {
494        return tokens;
495    }
496
497    // Parse tokens until terminator (00) or end of data
498    let mut iteration = 0;
499    while iteration < 100 {
500        // Safety limit
501        iteration += 1;
502        let bit_pos = reader.bit_offset;
503
504        let prefix2 = match reader.read_bits(2) {
505            Some(p) => p,
506            None => break,
507        };
508
509        if debug {
510            eprintln!("[bit {:3}] Prefix: {:02b}", bit_pos, prefix2);
511        }
512
513        match prefix2 {
514            0b00 => {
515                if debug {
516                    eprintln!(
517                        "         -> Separator (remaining bits: {})",
518                        reader.remaining_bits()
519                    );
520                }
521                tokens.push(Token::Separator);
522                // If we have very few bits left after a separator, stop parsing.
523                // This prevents interpreting trailing padding/garbage as tokens.
524                // Minimum meaningful token is 3 bits (prefix) + 5 bits (min varint) = 8 bits
525                if reader.remaining_bits() < 8 {
526                    if debug {
527                        eprintln!("         -> Insufficient bits remaining, stopping parse");
528                    }
529                    break;
530                }
531            }
532            0b01 => {
533                if debug {
534                    eprintln!("         -> SoftSeparator");
535                }
536                tokens.push(Token::SoftSeparator);
537            }
538            0b10 | 0b11 => {
539                // Need one more bit to distinguish
540                let bit3 = match reader.read_bits(1) {
541                    Some(b) => b,
542                    None => break,
543                };
544
545                let prefix3 = (prefix2 << 1) | bit3;
546
547                if debug {
548                    eprintln!("         -> 3-bit prefix: {:03b}", prefix3);
549                }
550
551                match prefix3 {
552                    0b100 => {
553                        // VARINT
554                        if let Some(val) = reader.read_varint() {
555                            if debug {
556                                eprintln!("         -> VarInt({})", val);
557                            }
558                            tokens.push(Token::VarInt(val));
559                        }
560                    }
561                    0b110 => {
562                        // VARBIT
563                        if let Some(val) = reader.read_varbit() {
564                            if debug {
565                                eprintln!("         -> VarBit({})", val);
566                            }
567                            tokens.push(Token::VarBit(val));
568                        }
569                    }
570                    0b101 => {
571                        // Part structure:
572                        // [VARINT index][1-bit type flag]
573                        //   Type 1: [VARINT value][000 terminator]
574                        //   Type 0: [2-bit subtype]
575                        //     10 = no data
576                        //     01 = value list until 00 terminator
577                        if let Some(index) = reader.read_varint() {
578                            // Validate part index is reasonable. If it's too large,
579                            // we're likely parsing garbage data past the end of valid tokens.
580                            if index > MAX_REASONABLE_PART_INDEX {
581                                if debug {
582                                    eprintln!("         -> Part index {} exceeds max ({}), stopping parse",
583                                              index, MAX_REASONABLE_PART_INDEX);
584                                }
585                                break;
586                            }
587
588                            let mut values = Vec::new();
589
590                            if let Some(type_flag) = reader.read_bits(1) {
591                                if type_flag == 1 {
592                                    // SUBTYPE_INT: single VARINT value + 000 terminator
593                                    if let Some(val) = reader.read_varint() {
594                                        values.push(val);
595                                    }
596                                    // Read 000 terminator (3 bits)
597                                    let _ = reader.read_bits(3);
598                                } else {
599                                    // Type 0: read 2-bit subtype
600                                    if let Some(subtype) = reader.read_bits(2) {
601                                        match subtype {
602                                            0b10 => {
603                                                // SUBTYPE_NONE: no additional data
604                                            }
605                                            0b01 => {
606                                                // SUBTYPE_LIST: read values until 00 separator
607                                                // Values can be VARINT or VARBIT
608                                                loop {
609                                                    // Peek at next 2 bits to check for terminator
610                                                    let start_pos = reader.bit_offset;
611                                                    if let Some(peek) = reader.read_bits(2) {
612                                                        if peek == 0b00 {
613                                                            // Hard separator - end of list
614                                                            break;
615                                                        }
616                                                        // Not a terminator, need to parse a value
617                                                        // Read 1 more bit to get 3-bit prefix
618                                                        if let Some(bit3) = reader.read_bits(1) {
619                                                            let prefix3 = (peek << 1) | bit3;
620                                                            match prefix3 {
621                                                                0b100 => {
622                                                                    if let Some(v) =
623                                                                        reader.read_varint()
624                                                                    {
625                                                                        values.push(v);
626                                                                    }
627                                                                }
628                                                                0b110 => {
629                                                                    if let Some(v) =
630                                                                        reader.read_varbit()
631                                                                    {
632                                                                        values.push(v);
633                                                                    }
634                                                                }
635                                                                _ => {
636                                                                    // Unknown prefix, rewind and break
637                                                                    reader.bit_offset = start_pos;
638                                                                    break;
639                                                                }
640                                                            }
641                                                        } else {
642                                                            break;
643                                                        }
644                                                    } else {
645                                                        break;
646                                                    }
647                                                }
648                                            }
649                                            _ => {
650                                                // Unknown subtype
651                                            }
652                                        }
653                                    }
654                                }
655                            }
656
657                            if debug {
658                                eprintln!(
659                                    "         -> Part {{ index: {}, values: {:?} }}",
660                                    index, values
661                                );
662                            }
663                            tokens.push(Token::Part { index, values });
664                        }
665                    }
666                    0b111 => {
667                        // String: VARINT length + 7-bit ASCII chars
668                        if let Some(length) = reader.read_varint() {
669                            if debug {
670                                eprintln!(
671                                    "         -> String length: {} (would need {} bits)",
672                                    length,
673                                    length * 7
674                                );
675                            }
676                            // Sanity check - don't read more than 128 chars
677                            if length > 128 {
678                                if debug {
679                                    eprintln!("         -> String too long, skipping");
680                                }
681                                continue;
682                            }
683                            let mut chars = Vec::new();
684                            for _ in 0..length {
685                                // 7-bit ASCII (LSB-first)
686                                if let Some(ch) = reader.read_bits(7) {
687                                    chars.push(ch as u8);
688                                }
689                            }
690                            if let Ok(s) = String::from_utf8(chars.clone()) {
691                                if debug {
692                                    eprintln!("         -> String({:?})", s);
693                                }
694                                tokens.push(Token::String(s));
695                            } else {
696                                if debug {
697                                    eprintln!("         -> String (binary): {:?}", chars);
698                                }
699                                // Store as lossy string anyway
700                                tokens.push(Token::String(
701                                    String::from_utf8_lossy(&chars).to_string(),
702                                ));
703                            }
704                        }
705                    }
706                    _ => break,
707                }
708            }
709            _ => break,
710        }
711    }
712
713    tokens
714}
715
716/// Debug version of parse_tokens that prints what it sees
717/// Note: expects already-mirrored bytes (as stored in ItemSerial.raw_bytes)
718pub fn parse_tokens_debug(bytes: &[u8]) -> Vec<Token> {
719    let mut reader = BitReader::new(bytes.to_vec());
720    parse_tokens_impl(&mut reader, true)
721}
722
723impl ItemSerial {
724    /// Decode a Borderlands 4 item serial
725    ///
726    /// Format: `@Ug<type><base85_data>`
727    /// Example: @Ugr$ZCm/&tH!t{KgK/Shxu>k
728    pub fn decode(serial: &str) -> Result<Self, SerialError> {
729        // Check for @Ug prefix
730        if !serial.starts_with("@Ug") {
731            return Err(SerialError::InvalidEncoding(
732                "Serial must start with @Ug".to_string(),
733            ));
734        }
735
736        // Extract item type (character after @Ug)
737        let item_type = serial.chars().nth(3).ok_or_else(|| {
738            SerialError::InvalidEncoding("Serial too short - no item type".to_string())
739        })?;
740
741        // Strip @Ug prefix, keeping the item type and data
742        let encoded_data = &serial[2..]; // Keep everything after @U
743
744        // Decode Base85
745        let decoded = decode_base85(encoded_data)?;
746
747        // Mirror all bits
748        let raw_bytes: Vec<u8> = decoded.iter().map(|&b| mirror_byte(b)).collect();
749
750        if raw_bytes.len() < 4 {
751            return Err(SerialError::TooShort {
752                expected: 4,
753                actual: raw_bytes.len(),
754            });
755        }
756
757        // Parse the bitstream
758        let mut reader = BitReader::new(raw_bytes.clone());
759        let tokens = parse_tokens(&mut reader);
760
761        // Extract common fields from tokens based on format
762        let mut manufacturer = None;
763        let mut level = None;
764        let mut raw_level = None;
765        let mut seed = None;
766
767        // Determine format from first token (more reliable than type character)
768        let is_varbit_first = matches!(tokens.first(), Some(Token::VarBit(_)));
769
770        if is_varbit_first {
771            // Equipment format: VBIT(category) SEP VBIT(level) ...
772            // Level is the VarBit after the first separator
773            let mut seen_first_sep = false;
774            for token in &tokens {
775                match token {
776                    Token::Separator if !seen_first_sep => {
777                        seen_first_sep = true;
778                    }
779                    Token::VarBit(v) if seen_first_sep && level.is_none() => {
780                        // Equipment levels are 0-indexed in storage, add 1 for display
781                        // Verified: all /)}} items (level 50) have VarBit=49
782                        let adjusted = v.saturating_add(1);
783                        if let Some((capped, raw)) = level_from_code(adjusted) {
784                            level = Some(capped as u64);
785                            raw_level = Some(raw as u64);
786                        }
787                        break;
788                    }
789                    _ => {}
790                }
791            }
792        } else {
793            // Weapon format: VINT(mfg) SOFT VINT(0) SOFT VINT(8) SOFT VINT(level) SEP ...
794            // Collect VarInts before first separator for header analysis
795            let mut header_varints: Vec<u64> = Vec::new();
796            let mut after_first_sep: Vec<u64> = Vec::new();
797            let mut seen_separator = false;
798
799            for token in &tokens {
800                match token {
801                    Token::VarInt(v) => {
802                        if seen_separator {
803                            after_first_sep.push(*v);
804                        } else {
805                            header_varints.push(*v);
806                        }
807                    }
808                    Token::Separator => {
809                        seen_separator = true;
810                    }
811                    _ => {}
812                }
813            }
814
815            // VarInt-first format detected from tokens - extract weapon info
816            // regardless of type character (some type 'e' items are weapons with Fme!K header)
817            if !header_varints.is_empty() {
818                manufacturer = Some(header_varints[0]);
819            }
820            if header_varints.len() >= 4 {
821                // Only extract level if it decodes to a valid value (1-50)
822                // If raw > 50, the 4th VarInt is not a level code (e.g., Fme!K format marker 55256)
823                if let Some((capped, raw)) = level_from_code(header_varints[3]) {
824                    if raw <= 50 {
825                        level = Some(capped as u64);
826                        raw_level = Some(raw as u64);
827                    }
828                    // If raw > 50, don't set level - it's not a valid level code
829                }
830            }
831            if after_first_sep.len() >= 2 {
832                seed = Some(after_first_sep[1]);
833            }
834        }
835
836        // Extract elements from Part tokens with index in element range (128-142)
837        let elements: Vec<Element> = tokens
838            .iter()
839            .filter_map(|token| {
840                if let Token::Part { index, .. } = token {
841                    // Element IDs are encoded as 128 + element_id
842                    if *index >= 128 && *index <= 142 {
843                        let element_id = index - 128;
844                        Element::from_id(element_id)
845                    } else {
846                        None
847                    }
848                } else {
849                    None
850                }
851            })
852            .collect();
853
854        // Extract rarity based on item format
855        let rarity = if is_varbit_first {
856            // VarBit-first equipment: rarity from first VarBit
857            let first_varbit = tokens.iter().find_map(|t| {
858                if let Token::VarBit(v) = t {
859                    Some(*v)
860                } else {
861                    None
862                }
863            });
864            if let Some(varbit) = first_varbit {
865                // Get divisor for this item type
866                let divisor = serial_format(item_type)
867                    .map(|f| f.category_divisor)
868                    .unwrap_or(384);
869                Rarity::from_equipment_varbit(varbit, divisor)
870            } else {
871                None
872            }
873        } else {
874            // VarInt-first weapons: rarity from 4th VarInt (level code)
875            // Extract 4th VarInt from header
876            let mut header_varints: Vec<u64> = Vec::new();
877            for token in &tokens {
878                match token {
879                    Token::VarInt(v) => header_varints.push(*v),
880                    Token::Separator => break,
881                    _ => {}
882                }
883            }
884            if header_varints.len() >= 4 {
885                Rarity::from_weapon_level_code(header_varints[3])
886            } else {
887                None
888            }
889        };
890
891        Ok(ItemSerial {
892            original: serial.to_string(),
893            raw_bytes,
894            item_type,
895            tokens,
896            manufacturer,
897            level,
898            raw_level,
899            seed,
900            elements,
901            rarity,
902        })
903    }
904
905    /// Encode this item serial back to a Base85 string
906    ///
907    /// This encodes the current tokens back to a serial string.
908    /// Useful for modifying an item and getting the new serial.
909    pub fn encode(&self) -> String {
910        // For now, we can't perfectly re-encode because we don't preserve
911        // the original bytes that encode the item type. Instead, re-encode
912        // from the original raw_bytes which preserves all information.
913        //
914        // TODO: Implement proper token-to-bytes encoding that includes item type
915        let mirrored: Vec<u8> = self.raw_bytes.iter().map(|&b| mirror_byte(b)).collect();
916        let encoded = encode_base85(&mirrored);
917        format!("@U{}", encoded)
918    }
919
920    /// Encode with modified tokens (experimental)
921    /// This attempts to encode tokens back to bytes, but may not preserve
922    /// all original data like item type encoding.
923    pub fn encode_from_tokens(&self) -> String {
924        // Encode tokens to bytes
925        let bytes = encode_tokens(&self.tokens);
926
927        // Mirror all bits (reverse of decode)
928        let mirrored: Vec<u8> = bytes.iter().map(|&b| mirror_byte(b)).collect();
929
930        // Encode to Base85
931        let encoded = encode_base85(&mirrored);
932
933        // Build final serial with prefix (just @U since g and item_type are in the bytes)
934        format!("@U{}", encoded)
935    }
936
937    /// Create a new ItemSerial with modified tokens
938    pub fn with_tokens(&self, tokens: Vec<Token>) -> Self {
939        // Re-extract elements from the new tokens
940        let elements: Vec<Element> = tokens
941            .iter()
942            .filter_map(|token| {
943                if let Token::Part { index, .. } = token {
944                    if *index >= 128 && *index <= 142 {
945                        Element::from_id(index - 128)
946                    } else {
947                        None
948                    }
949                } else {
950                    None
951                }
952            })
953            .collect();
954
955        ItemSerial {
956            original: self.original.clone(),
957            raw_bytes: self.raw_bytes.clone(), // Will be stale but that's OK
958            item_type: self.item_type,
959            tokens,
960            manufacturer: self.manufacturer,
961            level: self.level,
962            raw_level: self.raw_level,
963            seed: self.seed,
964            elements,
965            rarity: self.rarity, // Preserve rarity from original
966        }
967    }
968
969    /// Display hex dump of raw bytes
970    pub fn hex_dump(&self) -> String {
971        hex::encode(&self.raw_bytes)
972    }
973
974    /// Format tokens as human-readable string
975    /// Example: `134, 0, 8, 196| 4, 2379|| {8} {4} {2} {8:3} {34}`
976    pub fn format_tokens(&self) -> String {
977        let mut output = String::new();
978
979        for token in &self.tokens {
980            match token {
981                Token::Separator => output.push('|'),
982                Token::SoftSeparator => output.push_str(", "),
983                Token::VarInt(v) => output.push_str(&format!("{}", v)),
984                Token::VarBit(v) => output.push_str(&format!("{}", v)),
985                Token::Part { index, values } => {
986                    if values.is_empty() {
987                        output.push_str(&format!("{{{}}}", index));
988                    } else if values.len() == 1 {
989                        output.push_str(&format!("{{{}:{}}}", index, values[0]));
990                    } else {
991                        let vals: Vec<String> = values.iter().map(|v| v.to_string()).collect();
992                        output.push_str(&format!("{{{}:[{}]}}", index, vals.join(" ")));
993                    }
994                }
995                Token::String(s) => {
996                    if s.is_empty() {
997                        output.push_str("\"\"");
998                    } else {
999                        output.push_str(&format!("{:?}", s));
1000                    }
1001                }
1002            }
1003            output.push(' ');
1004        }
1005
1006        output.trim().to_string()
1007    }
1008
1009    /// Get item type description
1010    pub fn item_type_description(&self) -> &'static str {
1011        item_type_name(self.item_type)
1012    }
1013
1014    /// Get manufacturer name if known
1015    pub fn manufacturer_name(&self) -> Option<&'static str> {
1016        self.manufacturer.and_then(manufacturer_name)
1017    }
1018
1019    /// Get element names as a formatted string
1020    /// Returns None if no elements detected, otherwise returns comma-separated list
1021    pub fn element_names(&self) -> Option<String> {
1022        if self.elements.is_empty() {
1023            None
1024        } else {
1025            let names: Vec<&str> = self.elements.iter().map(|e| e.name()).collect();
1026            Some(names.join(", "))
1027        }
1028    }
1029
1030    /// Get rarity name
1031    /// Returns None if rarity not detected
1032    pub fn rarity_name(&self) -> Option<&'static str> {
1033        self.rarity.map(|r| r.name())
1034    }
1035
1036    /// Get weapon info (manufacturer, weapon type) for VarInt-first format serials
1037    ///
1038    /// Returns None for VarBit-first formats or if the ID is unknown.
1039    pub fn weapon_info(&self) -> Option<(&'static str, &'static str)> {
1040        let fmt = serial_format(self.item_type)?;
1041        if fmt.has_weapon_info {
1042            self.manufacturer.and_then(weapon_info_from_first_varint)
1043        } else {
1044            None
1045        }
1046    }
1047
1048    /// Extract Part Group ID (category) from the serial
1049    ///
1050    /// Uses the format's category_divisor to extract category from first VarBit.
1051    /// Returns None if this format doesn't use VarBit categories.
1052    pub fn part_group_id(&self) -> Option<i64> {
1053        let fmt = serial_format(self.item_type)?;
1054        let first_varbit = self.tokens.iter().find_map(|t| {
1055            if let Token::VarBit(v) = t {
1056                Some(*v)
1057            } else {
1058                None
1059            }
1060        })?;
1061        fmt.extract_category(first_varbit)
1062    }
1063
1064    /// Get the parts database category for this item
1065    ///
1066    /// For VarBit-first items (shields, etc), uses the extracted category.
1067    /// For VarInt-first items (weapons), converts the serial ID to parts DB category.
1068    pub fn parts_category(&self) -> Option<i64> {
1069        // First try VarBit-first extraction
1070        if let Some(cat) = self.part_group_id() {
1071            return Some(cat);
1072        }
1073
1074        // Fall back to VarInt-first: convert serial ID to parts category
1075        self.manufacturer
1076            .map(|id| serial_id_to_parts_category(id) as i64)
1077    }
1078
1079    /// Get all Part tokens from this serial
1080    /// Returns (index, values) pairs for each Part token
1081    pub fn parts(&self) -> Vec<(u64, Vec<u64>)> {
1082        self.tokens
1083            .iter()
1084            .filter_map(|t| {
1085                if let Token::Part { index, values } = t {
1086                    Some((*index, values.clone()))
1087                } else {
1088                    None
1089                }
1090            })
1091            .collect()
1092    }
1093
1094    /// Display detailed byte-by-byte breakdown
1095    pub fn detailed_dump(&self) -> String {
1096        let mut output = String::new();
1097        output.push_str(&format!("Serial: {}\n", self.original));
1098        output.push_str(&format!("Item type: {}\n", self.item_type));
1099        output.push_str(&format!("Bytes: {} total\n\n", self.raw_bytes.len()));
1100
1101        // Show extracted fields
1102        output.push_str("Extracted fields:\n");
1103        if let Some(m) = self.manufacturer {
1104            output.push_str(&format!("  Manufacturer: {}\n", m));
1105        }
1106        if let Some(s) = self.seed {
1107            output.push_str(&format!("  Seed: {}\n", s));
1108        }
1109        if let Some(l) = self.level {
1110            output.push_str(&format!("  Level: {}\n", l));
1111        }
1112        output.push('\n');
1113
1114        // Show parsed tokens
1115        output.push_str(&format!("Tokens: {} total\n", self.tokens.len()));
1116        for (i, token) in self.tokens.iter().enumerate() {
1117            output.push_str(&format!("  [{:2}] {:?}\n", i, token));
1118        }
1119        output.push('\n');
1120
1121        // Show raw bytes
1122        output.push_str("Raw bytes:\n");
1123        for (i, byte) in self.raw_bytes.iter().enumerate() {
1124            output.push_str(&format!(
1125                "[{:3}] = {:3} (0x{:02x}) (0b{:08b})\n",
1126                i, byte, byte, byte
1127            ));
1128        }
1129
1130        output
1131    }
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136    use super::*;
1137
1138    #[test]
1139    fn test_decode_weapon_serial() {
1140        // Example weapon serial from memory dump
1141        let serial = "@Ugr$ZCm/&tH!t{KgK/Shxu>k";
1142        let item = ItemSerial::decode(serial).unwrap();
1143
1144        assert_eq!(item.item_type, 'r');
1145        assert!(!item.raw_bytes.is_empty());
1146        assert!(!item.tokens.is_empty(), "Should parse at least one token");
1147
1148        // First byte should be 0x21 (contains magic header 0010000)
1149        assert_eq!(item.raw_bytes[0], 0x21);
1150    }
1151
1152    #[test]
1153    fn test_decode_equipment_serial() {
1154        // Equipment serial from save file
1155        let serial = "@Uge8jxm/)@{!gQaYMipv(G&-b*Z~_";
1156        let item = ItemSerial::decode(serial).unwrap();
1157
1158        assert_eq!(item.item_type, 'e');
1159        assert!(!item.raw_bytes.is_empty());
1160        assert_eq!(item.raw_bytes[0], 0x21); // Magic header
1161    }
1162
1163    #[test]
1164    fn test_decode_utility_serial() {
1165        // Utility item - first VarInt is item subtype identifier
1166        let serial = "@Uguq~c2}TYg3/>%aRG}8ts7KXA-9&{!<w2c7r9#z0g+sMN<wF1";
1167        let item = ItemSerial::decode(serial).unwrap();
1168
1169        assert_eq!(item.item_type, 'u');
1170        assert!(!item.tokens.is_empty());
1171
1172        // First VarInt(128) is the item subtype identifier for utility items
1173        assert_eq!(item.manufacturer, Some(128));
1174    }
1175
1176    #[test]
1177    fn test_invalid_serial_prefix() {
1178        let result = ItemSerial::decode("InvalidSerial");
1179        assert!(result.is_err());
1180    }
1181
1182    #[test]
1183    fn test_base85_decode() {
1184        // Test basic Base85 decoding
1185        let result = decode_base85("g").unwrap();
1186        assert_eq!(result.len(), 0); // Single char decodes to 0 bytes with partial chunk
1187    }
1188
1189    #[test]
1190    fn test_mirror_byte() {
1191        assert_eq!(mirror_byte(0b10000000), 0b00000001);
1192        assert_eq!(mirror_byte(0b11000000), 0b00000011);
1193        assert_eq!(mirror_byte(0b10101010), 0b01010101);
1194        assert_eq!(mirror_byte(0b00000000), 0b00000000);
1195        assert_eq!(mirror_byte(0b11111111), 0b11111111);
1196    }
1197
1198    #[test]
1199    fn test_part_group_id_extraction() {
1200        // Weapon serial - Vladof SMG (group 22)
1201        let item = ItemSerial::decode("@Ugr$ZCm/&tH!t{KgK/Shxu>k").unwrap();
1202        assert_eq!(item.part_group_id(), Some(22));
1203
1204        // Equipment serial - Shield (group 279)
1205        let item = ItemSerial::decode("@Uge8jxm/)@{!gQaYMipv(G&-b*Z~_").unwrap();
1206        assert_eq!(item.part_group_id(), Some(279));
1207
1208        // Utility items don't use Part Group ID
1209        let item =
1210            ItemSerial::decode("@Uguq~c2}TYg3/>%aRG}8ts7KXA-9&{!<w2c7r9#z0g+sMN<wF1").unwrap();
1211        assert_eq!(item.part_group_id(), None);
1212    }
1213
1214    #[test]
1215    fn test_parts_extraction() {
1216        // Weapon with one part
1217        let item = ItemSerial::decode("@Ugr$ZCm/&tH!t{KgK/Shxu>k").unwrap();
1218        let parts = item.parts();
1219        assert!(!parts.is_empty(), "Should have at least one part");
1220
1221        // Check first part has index 0 and value
1222        let (index, values) = &parts[0];
1223        assert_eq!(*index, 0u64);
1224        assert!(!values.is_empty());
1225    }
1226
1227    #[test]
1228    fn test_equipment_level_extraction() {
1229        // Shield type-e with VarBit 49 = Level 50 (0-indexed storage)
1230        let item = ItemSerial::decode("@Uge98>m/)}}!c5JeNWCvCXc7").unwrap();
1231        assert_eq!(item.level, Some(50));
1232
1233        // Grenade with VarBit 49 = Level 50
1234        let item = ItemSerial::decode("@Uge8Xtm/)}}!elF;NmXinbwH6?9}OPi1ON").unwrap();
1235        assert_eq!(item.level, Some(50));
1236
1237        // Class mod with VarBit 50 = Level 51 (invalid, returns None)
1238        let item = ItemSerial::decode("@Uge8;)m/)@{!X>!SqTZJibf`hSk4B2r6#)").unwrap();
1239        assert_eq!(item.level, None);
1240
1241        // Shield type-r with VarBit 49 = Level 50
1242        let item = ItemSerial::decode("@Ugr$)Nm/%P$!bIqxL{(~iG&p36L=sIx00").unwrap();
1243        assert_eq!(item.level, Some(50));
1244
1245        // Weapon still works - level 30
1246        let item = ItemSerial::decode("@Ugb)KvFg_4rJ}%H-RG}IbsZG^E#X_Y-00").unwrap();
1247        assert_eq!(item.level, Some(30));
1248    }
1249
1250    #[test]
1251    fn test_encode_roundtrip() {
1252        // Test that decode -> encode produces the original serial
1253        let test_serials = [
1254            // Hellwalker (Fire shotgun)
1255            "@Ugd_t@FmVuJyjIXzRG}JG7S$K^1{DjH5&-",
1256            // Jakobs Pistol (Corrosive)
1257            "@UgbV{rFjEj=bZ<~-RG}KRs7TF2b*c{P7OEuz",
1258            // Energy Shield
1259            "@Uge98>m/)}}!c5JeNWCvCXc7",
1260            // Class Mod
1261            "@Ug!pHG2}TYgjMfjzn~K!T)XUVX)U4Eu)Qi+?RPAVZh!@!b00",
1262            // Grenade
1263            "@Ugr$N8m/)}}!q9r4K/ShxuK@",
1264        ];
1265
1266        for serial in test_serials {
1267            let item = ItemSerial::decode(serial).unwrap();
1268            let re_encoded = item.encode();
1269            assert_eq!(
1270                re_encoded, serial,
1271                "Round-trip failed for {}: got {}",
1272                serial, re_encoded
1273            );
1274        }
1275    }
1276
1277    #[test]
1278    fn test_element_from_id() {
1279        // Verify element ID mapping
1280        assert_eq!(Element::from_id(0), Some(Element::Kinetic));
1281        assert_eq!(Element::from_id(5), Some(Element::Corrosive));
1282        assert_eq!(Element::from_id(8), Some(Element::Shock));
1283        assert_eq!(Element::from_id(9), Some(Element::Radiation));
1284        assert_eq!(Element::from_id(13), Some(Element::Cryo));
1285        assert_eq!(Element::from_id(14), Some(Element::Fire));
1286        assert_eq!(Element::from_id(99), None); // Unknown ID
1287    }
1288
1289    #[test]
1290    fn test_element_names() {
1291        assert_eq!(Element::Kinetic.name(), "Kinetic");
1292        assert_eq!(Element::Corrosive.name(), "Corrosive");
1293        assert_eq!(Element::Shock.name(), "Shock");
1294        assert_eq!(Element::Radiation.name(), "Radiation");
1295        assert_eq!(Element::Cryo.name(), "Cryo");
1296        assert_eq!(Element::Fire.name(), "Fire");
1297    }
1298
1299    #[test]
1300    fn test_element_extraction_fire() {
1301        // Hellwalker (Fire shotgun) - verified in-game
1302        let item = ItemSerial::decode("@Ugd_t@FmVuJyjIXzRG}JG7S$K^1{DjH5&-").unwrap();
1303        assert_eq!(item.elements.len(), 1);
1304        assert_eq!(item.elements[0], Element::Fire);
1305        assert_eq!(item.element_names(), Some("Fire".to_string()));
1306    }
1307
1308    #[test]
1309    fn test_element_extraction_corrosive() {
1310        // Jakobs Pistol (Corrosive)
1311        let item = ItemSerial::decode("@UgbV{rFjEj=bZ<~-RG}KRs7TF2b*c{P7OEuz").unwrap();
1312        assert_eq!(item.elements.len(), 1);
1313        assert_eq!(item.elements[0], Element::Corrosive);
1314        assert_eq!(item.element_names(), Some("Corrosive".to_string()));
1315    }
1316
1317    #[test]
1318    fn test_element_extraction_none() {
1319        // Energy Shield - no weapon element
1320        let item = ItemSerial::decode("@Uge98>m/)}}!c5JeNWCvCXc7").unwrap();
1321        assert!(item.elements.is_empty());
1322        assert_eq!(item.element_names(), None);
1323    }
1324}