Skip to main content

halbu/items/
mod.rs

1//! Items section placeholder support.
2//!
3//! Item payload is preserved as raw bytes.
4//! If raw bytes are empty, encoding emits a known empty-inventory trailer for the target layout.
5
6use serde::{Deserialize, Serialize};
7use serde_with::{serde_as, Bytes};
8
9const V99_EMPTY_ITEMS_CLASSIC: [u8; 4] = [0x4A, 0x4D, 0x00, 0x00];
10const V99_EMPTY_ITEMS_EXPANSION: [u8; 13] =
11    [0x4A, 0x4D, 0x00, 0x00, 0x4A, 0x4D, 0x00, 0x00, 0x6A, 0x66, 0x6B, 0x66, 0x00];
12const V99_EMPTY_ITEMS_EXPANSION_MERC: [u8; 17] = [
13    0x4A, 0x4D, 0x00, 0x00, 0x4A, 0x4D, 0x00, 0x00, 0x6A, 0x66, 0x4A, 0x4D, 0x00, 0x00, 0x6B, 0x66,
14    0x00,
15];
16
17const V105_EMPTY_ITEMS_CLASSIC: [u8; 4] = [0x4A, 0x4D, 0x00, 0x00];
18const V105_EMPTY_ITEMS_EXPANSION: [u8; 13] =
19    [0x4A, 0x4D, 0x00, 0x00, 0x4A, 0x4D, 0x00, 0x00, 0x6A, 0x66, 0x6B, 0x66, 0x00];
20const V105_EMPTY_ITEMS_EXPANSION_MERC: [u8; 17] = [
21    0x4A, 0x4D, 0x00, 0x00, 0x4A, 0x4D, 0x00, 0x00, 0x6A, 0x66, 0x4A, 0x4D, 0x00, 0x00, 0x6B, 0x66,
22    0x00,
23];
24const V105_EMPTY_ITEMS_ROTW: [u8; 19] = [
25    0x4A, 0x4D, 0x00, 0x00, 0x4A, 0x4D, 0x00, 0x00, 0x6A, 0x66, 0x6B, 0x66, 0x00, 0x01, 0x00, 0x6C,
26    0x66, 0x00, 0x00,
27];
28
29const V105_EMPTY_ITEMS_ROTW_MERC: [u8; 23] = [
30    0x4A, 0x4D, 0x00, 0x00, 0x4A, 0x4D, 0x00, 0x00, 0x6A, 0x66, 0x4A, 0x4D, 0x00, 0x00, 0x6B, 0x66,
31    0x00, 0x01, 0x00, 0x6C, 0x66, 0x00, 0x00,
32];
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub enum EmptyLayout {
36    /// Legacy D2R classic empty-item trailer.
37    LegacyClassic,
38    /// Legacy D2R expansion empty-item trailer.
39    LegacyExpansion,
40    /// V105 classic empty-item trailer.
41    V105Classic,
42    /// V105 expansion empty-item trailer.
43    V105Expansion,
44    /// V105 RotW empty-item trailer.
45    V105RotW,
46}
47
48/// Raw items-section payload placeholder.
49#[serde_as]
50#[derive(PartialEq, Eq, Debug, Default, Clone, Serialize, Deserialize)]
51pub struct Placeholder {
52    #[serde_as(as = "Bytes")]
53    data: Vec<u8>,
54    #[serde(default)]
55    original_mercenary_hired: bool,
56}
57
58/// Store item bytes without decoding.
59pub fn parse(byte_vector: &[u8], mercenary_hired: bool) -> Placeholder {
60    Placeholder { data: byte_vector.to_vec(), original_mercenary_hired: mercenary_hired }
61}
62
63impl Placeholder {
64    pub(crate) fn mercenary_hire_state_changed(&self, mercenary_hired: bool) -> bool {
65        self.original_mercenary_hired != mercenary_hired
66    }
67}
68
69/// Generate item bytes.
70///
71/// If `placeholder` contains raw bytes, they are returned unchanged.
72/// Otherwise, a known empty-item layout trailer is emitted.
73pub fn generate(
74    placeholder: &Placeholder,
75    empty_layout: EmptyLayout,
76    mercenary_hired: bool,
77) -> Vec<u8> {
78    if !placeholder.data.is_empty() {
79        return placeholder.data.clone();
80    }
81
82    match (empty_layout, mercenary_hired) {
83        (EmptyLayout::LegacyClassic, _) => V99_EMPTY_ITEMS_CLASSIC.to_vec(),
84        (EmptyLayout::LegacyExpansion, false) => V99_EMPTY_ITEMS_EXPANSION.to_vec(),
85        (EmptyLayout::LegacyExpansion, true) => V99_EMPTY_ITEMS_EXPANSION_MERC.to_vec(),
86        (EmptyLayout::V105Classic, _) => V105_EMPTY_ITEMS_CLASSIC.to_vec(),
87        (EmptyLayout::V105Expansion, false) => V105_EMPTY_ITEMS_EXPANSION.to_vec(),
88        (EmptyLayout::V105Expansion, true) => V105_EMPTY_ITEMS_EXPANSION_MERC.to_vec(),
89        (EmptyLayout::V105RotW, false) => V105_EMPTY_ITEMS_ROTW.to_vec(),
90        (EmptyLayout::V105RotW, true) => V105_EMPTY_ITEMS_ROTW_MERC.to_vec(),
91    }
92}