1use 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
16const BL4_BASE85_ALPHABET: &[u8; 85] =
18 b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{/}~";
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Element {
23 Kinetic, Corrosive, Shock, Radiation, Cryo, Fire, }
30
31impl Element {
32 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum Rarity {
61 Common,
62 Uncommon,
63 Rare,
64 Epic,
65 Legendary,
66}
67
68impl Rarity {
69 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 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), 2 => Some(Rarity::Rare), 3 => Some(Rarity::Legendary), _ => None,
94 }
95 }
96
97 pub fn from_weapon_level_code(code: u64) -> Option<Self> {
100 if code <= 145 {
103 Some(Rarity::Common)
104 } else {
105 match code {
108 192 => Some(Rarity::Epic),
109 200 => Some(Rarity::Legendary),
110 146..=180 => Some(Rarity::Uncommon),
112 181..=195 => Some(Rarity::Epic),
113 196.. => Some(Rarity::Legendary),
114 _ => None,
115 }
116 }
117 }
118}
119
120const MAX_REASONABLE_PART_INDEX: u64 = 1000;
124
125#[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
138struct BitReader {
140 bytes: Vec<u8>,
141 bit_offset: usize,
142}
143
144struct 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 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); 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 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); break;
190 } else {
191 self.write_bits(1, 1); }
193 }
194 }
195
196 fn write_varbit(&mut self, value: u64) {
198 if value == 0 {
199 self.write_bits(0, 5); return;
201 }
202
203 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 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 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); 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 fn read_varint(&mut self) -> Option<u64> {
251 let mut result = 0u64;
252 let mut shift = 0;
253
254 for _ in 0..4 {
256 let nibble = self.read_bits(4)?;
257 result |= nibble << shift;
258 shift += 4;
259
260 let cont = self.read_bits(1)?;
262 if cont == 0 {
263 return Some(result);
264 }
265 }
266
267 Some(result)
268 }
269
270 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 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#[derive(Debug, Clone, PartialEq)]
291pub enum Token {
292 Separator, SoftSeparator, VarInt(u64), VarBit(u64), Part { index: u64, values: Vec<u64> }, String(String), }
299
300#[derive(Debug, Clone)]
302pub struct ItemSerial {
303 pub original: String,
305
306 pub raw_bytes: Vec<u8>,
308
309 pub item_type: char,
311
312 pub tokens: Vec<Token>,
314
315 pub manufacturer: Option<u64>,
318 pub level: Option<u64>,
320 pub raw_level: Option<u64>,
322 pub seed: Option<u64>,
324 pub elements: Vec<Element>,
326 pub rarity: Option<Rarity>,
328}
329
330fn decode_base85(input: &str) -> Result<Vec<u8>, SerialError> {
332 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 for chunk in chars.chunks(5) {
343 let mut value: u64 = 0;
344
345 for &ch in chunk.iter() {
348 let byte_val = lookup[ch as usize] as u64;
349 value = value * 85 + byte_val;
350 }
351 for _ in chunk.len()..5 {
353 value = value * 85 + 84;
354 }
355
356 let num_bytes = if chunk.len() == 5 { 4 } else { chunk.len() - 1 };
358 for i in (0..num_bytes).rev() {
359 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#[inline]
375fn mirror_byte(byte: u8) -> u8 {
376 byte.reverse_bits()
377}
378
379fn encode_base85(bytes: &[u8]) -> String {
381 let mut result = String::new();
382
383 for chunk in bytes.chunks(4) {
385 let mut value: u64 = 0;
387 for &byte in chunk {
388 value = (value << 8) | (byte as u64);
389 }
390 if chunk.len() < 4 {
392 value <<= (4 - chunk.len()) * 8;
393 }
394
395 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 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
412fn encode_tokens(tokens: &[Token]) -> Vec<u8> {
414 let mut writer = BitWriter::new();
415
416 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 writer.write_bits(0, 1);
442 writer.write_bits(0b10, 2);
443 } else if values.len() == 1 {
444 writer.write_bits(1, 1);
446 writer.write_varint(values[0]);
447 writer.write_bits(0b000, 3);
448 } else {
449 writer.write_bits(0, 1);
451 writer.write_bits(0b01, 2);
452 for v in values {
453 writer.write_bits(0b100, 3);
455 writer.write_varint(*v);
456 }
457 writer.write_bits(0b00, 2); }
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
473fn parse_tokens(reader: &mut BitReader) -> Vec<Token> {
475 parse_tokens_impl(reader, false)
476}
477
478fn parse_tokens_impl(reader: &mut BitReader, debug: bool) -> Vec<Token> {
480 let mut tokens = Vec::new();
481
482 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 let mut iteration = 0;
499 while iteration < 100 {
500 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 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 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 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 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 if let Some(index) = reader.read_varint() {
578 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 if let Some(val) = reader.read_varint() {
594 values.push(val);
595 }
596 let _ = reader.read_bits(3);
598 } else {
599 if let Some(subtype) = reader.read_bits(2) {
601 match subtype {
602 0b10 => {
603 }
605 0b01 => {
606 loop {
609 let start_pos = reader.bit_offset;
611 if let Some(peek) = reader.read_bits(2) {
612 if peek == 0b00 {
613 break;
615 }
616 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 reader.bit_offset = start_pos;
638 break;
639 }
640 }
641 } else {
642 break;
643 }
644 } else {
645 break;
646 }
647 }
648 }
649 _ => {
650 }
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 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 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 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 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
716pub 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 pub fn decode(serial: &str) -> Result<Self, SerialError> {
729 if !serial.starts_with("@Ug") {
731 return Err(SerialError::InvalidEncoding(
732 "Serial must start with @Ug".to_string(),
733 ));
734 }
735
736 let item_type = serial.chars().nth(3).ok_or_else(|| {
738 SerialError::InvalidEncoding("Serial too short - no item type".to_string())
739 })?;
740
741 let encoded_data = &serial[2..]; let decoded = decode_base85(encoded_data)?;
746
747 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 let mut reader = BitReader::new(raw_bytes.clone());
759 let tokens = parse_tokens(&mut reader);
760
761 let mut manufacturer = None;
763 let mut level = None;
764 let mut raw_level = None;
765 let mut seed = None;
766
767 let is_varbit_first = matches!(tokens.first(), Some(Token::VarBit(_)));
769
770 if is_varbit_first {
771 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 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 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 if !header_varints.is_empty() {
818 manufacturer = Some(header_varints[0]);
819 }
820 if header_varints.len() >= 4 {
821 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 }
830 }
831 if after_first_sep.len() >= 2 {
832 seed = Some(after_first_sep[1]);
833 }
834 }
835
836 let elements: Vec<Element> = tokens
838 .iter()
839 .filter_map(|token| {
840 if let Token::Part { index, .. } = token {
841 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 let rarity = if is_varbit_first {
856 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 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 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 pub fn encode(&self) -> String {
910 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 pub fn encode_from_tokens(&self) -> String {
924 let bytes = encode_tokens(&self.tokens);
926
927 let mirrored: Vec<u8> = bytes.iter().map(|&b| mirror_byte(b)).collect();
929
930 let encoded = encode_base85(&mirrored);
932
933 format!("@U{}", encoded)
935 }
936
937 pub fn with_tokens(&self, tokens: Vec<Token>) -> Self {
939 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(), 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, }
967 }
968
969 pub fn hex_dump(&self) -> String {
971 hex::encode(&self.raw_bytes)
972 }
973
974 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 pub fn item_type_description(&self) -> &'static str {
1011 item_type_name(self.item_type)
1012 }
1013
1014 pub fn manufacturer_name(&self) -> Option<&'static str> {
1016 self.manufacturer.and_then(manufacturer_name)
1017 }
1018
1019 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 pub fn rarity_name(&self) -> Option<&'static str> {
1033 self.rarity.map(|r| r.name())
1034 }
1035
1036 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 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 pub fn parts_category(&self) -> Option<i64> {
1069 if let Some(cat) = self.part_group_id() {
1071 return Some(cat);
1072 }
1073
1074 self.manufacturer
1076 .map(|id| serial_id_to_parts_category(id) as i64)
1077 }
1078
1079 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 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 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 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 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 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 assert_eq!(item.raw_bytes[0], 0x21);
1150 }
1151
1152 #[test]
1153 fn test_decode_equipment_serial() {
1154 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); }
1162
1163 #[test]
1164 fn test_decode_utility_serial() {
1165 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 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 let result = decode_base85("g").unwrap();
1186 assert_eq!(result.len(), 0); }
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 let item = ItemSerial::decode("@Ugr$ZCm/&tH!t{KgK/Shxu>k").unwrap();
1202 assert_eq!(item.part_group_id(), Some(22));
1203
1204 let item = ItemSerial::decode("@Uge8jxm/)@{!gQaYMipv(G&-b*Z~_").unwrap();
1206 assert_eq!(item.part_group_id(), Some(279));
1207
1208 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 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 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 let item = ItemSerial::decode("@Uge98>m/)}}!c5JeNWCvCXc7").unwrap();
1231 assert_eq!(item.level, Some(50));
1232
1233 let item = ItemSerial::decode("@Uge8Xtm/)}}!elF;NmXinbwH6?9}OPi1ON").unwrap();
1235 assert_eq!(item.level, Some(50));
1236
1237 let item = ItemSerial::decode("@Uge8;)m/)@{!X>!SqTZJibf`hSk4B2r6#)").unwrap();
1239 assert_eq!(item.level, None);
1240
1241 let item = ItemSerial::decode("@Ugr$)Nm/%P$!bIqxL{(~iG&p36L=sIx00").unwrap();
1243 assert_eq!(item.level, Some(50));
1244
1245 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 let test_serials = [
1254 "@Ugd_t@FmVuJyjIXzRG}JG7S$K^1{DjH5&-",
1256 "@UgbV{rFjEj=bZ<~-RG}KRs7TF2b*c{P7OEuz",
1258 "@Uge98>m/)}}!c5JeNWCvCXc7",
1260 "@Ug!pHG2}TYgjMfjzn~K!T)XUVX)U4Eu)Qi+?RPAVZh!@!b00",
1262 "@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 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); }
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 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 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 let item = ItemSerial::decode("@Uge98>m/)}}!c5JeNWCvCXc7").unwrap();
1321 assert!(item.elements.is_empty());
1322 assert_eq!(item.element_names(), None);
1323 }
1324}