Skip to main content

me3_coalesced_parser/
shared.rs

1/// Magic bytes for ME3
2pub const ME3_MAGIC: u32 = 0x666D726D;
3/// Magic bytes for the ME3 tlk file
4pub const TLK_MAGIC: u32 = 0x006B6C54;
5
6pub type WChar = u16;
7pub type WString = Vec<u16>;
8
9/// Tlk file
10#[derive(Debug, Hash, serde::Serialize, serde::Deserialize)]
11pub struct Tlk {
12    pub version: u32,
13    pub min_version: u32,
14
15    /// Male tlk strings
16    pub male_values: Vec<TlkString>,
17    /// Female tlk strings
18    pub female_values: Vec<TlkString>,
19}
20
21impl Tlk {
22    /// Replaces a string with the provided ID with a new value
23    pub fn replace_male(&mut self, id: u32, value: WString) -> bool {
24        if let Some(entry) = self.male_values.iter_mut().find(|value| value.id == id) {
25            entry.value = value;
26            true
27        } else {
28            false
29        }
30    }
31
32    /// Inserts a value into the tlk attempting to replace an existing one
33    pub fn insert_male(&mut self, id: u32, value: WString) {
34        if self.replace_male(id, value.clone()) {
35            return;
36        }
37
38        self.male_values.push(TlkString { id, value })
39    }
40
41    /// Replaces a string with the provided ID with a new value
42    pub fn replace_female(&mut self, id: u32, value: WString) -> bool {
43        if let Some(entry) = self.female_values.iter_mut().find(|value| value.id == id) {
44            entry.value = value;
45            true
46        } else {
47            false
48        }
49    }
50
51    /// Inserts a value into the tlk attempting to replace an existing one
52    pub fn insert_female(&mut self, id: u32, value: WString) {
53        if self.replace_female(id, value.clone()) {
54            return;
55        }
56
57        self.female_values.push(TlkString { id, value })
58    }
59
60    // Replaces a string with the provided ID with a new value
61    pub fn replace_male_utf8(&mut self, id: u32, value: String) -> bool {
62        if let Some(entry) = self.male_values.iter_mut().find(|value| value.id == id) {
63            entry.value = value.encode_utf16().collect();
64            true
65        } else {
66            false
67        }
68    }
69
70    /// Inserts a value into the tlk attempting to replace an existing one
71    pub fn insert_male_utf8(&mut self, id: u32, value: String) {
72        if self.replace_male_utf8(id, value.clone()) {
73            return;
74        }
75
76        self.male_values.push(TlkString {
77            id,
78            value: value.encode_utf16().collect(),
79        })
80    }
81
82    /// Replaces a string with the provided ID with a new value
83    pub fn replace_female_utf8(&mut self, id: u32, value: String) -> bool {
84        if let Some(entry) = self.female_values.iter_mut().find(|value| value.id == id) {
85            entry.value = value.encode_utf16().collect();
86            true
87        } else {
88            false
89        }
90    }
91
92    /// Inserts a value into the tlk attempting to replace an existing one
93    pub fn insert_female_utf8(&mut self, id: u32, value: String) {
94        if self.replace_female_utf8(id, value.clone()) {
95            return;
96        }
97
98        self.female_values.push(TlkString {
99            id,
100            value: value.encode_utf16().collect(),
101        })
102    }
103}
104
105/// String within a tlk file
106#[derive(Debug, Hash, serde::Serialize, serde::Deserialize)]
107pub struct TlkString {
108    /// ID of the value
109    pub id: u32,
110    /// The string value itself
111    pub value: WString,
112}
113
114/// Coalesced file
115#[derive(Debug, Hash, serde::Serialize, serde::Deserialize)]
116pub struct Coalesced {
117    /// Coalesced version
118    pub version: u32,
119    /// Files within the coalesced
120    pub files: Vec<CoalFile>,
121}
122
123/// File within the coalesced
124#[derive(Debug, Hash, serde::Serialize, serde::Deserialize)]
125pub struct CoalFile {
126    /// The relative file path
127    pub path: String,
128    /// The sections within the file
129    pub sections: Vec<Section>,
130}
131
132#[derive(Debug, Hash, serde::Serialize, serde::Deserialize)]
133pub struct Section {
134    /// The section name
135    pub name: String,
136    /// Properties within the section
137    pub properties: Vec<Property>,
138}
139
140#[derive(Debug, Hash, serde::Serialize, serde::Deserialize)]
141pub struct Property {
142    /// The name of the property
143    pub name: String,
144    /// The values for this property
145    pub values: Vec<Value>,
146}
147
148#[derive(Debug, Hash, serde::Serialize, serde::Deserialize)]
149pub struct Value {
150    /// Value type
151    pub ty: ValueType,
152    /// Associated text value
153    pub text: Option<String>,
154}
155
156#[derive(Debug, Hash, serde::Serialize, serde::Deserialize, Clone, Copy)]
157#[repr(u8)]
158pub enum ValueType {
159    // Overwrite
160    New = 0,
161    // Remove entirely
162    RemoveProperty = 1,
163    // Add always
164    Add = 2,
165    // Add if unique
166    AddUnique = 3,
167    // Remove if same
168    Remove = 4,
169}
170
171pub struct UnknownValueType;
172
173impl TryFrom<u8> for ValueType {
174    type Error = UnknownValueType;
175
176    fn try_from(value: u8) -> Result<Self, Self::Error> {
177        Ok(match value {
178            0 => Self::New,
179            1 => Self::RemoveProperty,
180            2 => Self::Add,
181            3 => Self::AddUnique,
182            4 => Self::Remove,
183            _ => return Err(UnknownValueType),
184        })
185    }
186}
187
188/// Invests the order of the provided huffman pairs
189///
190/// The TLK format encodes them in the opposite direction
191/// to the Coalesced file so its easier to just flip them
192/// than write separate implementations
193pub(crate) fn invert_huffman_tree(pairs: &mut Vec<(i32, i32)>) {
194    let last_index = (pairs.len() - 1) as i32;
195
196    // Reverse the pair order
197    pairs.reverse();
198
199    // Update the pair indexes to match the new order
200    for pair in pairs {
201        if pair.0 > -1 {
202            pair.0 = last_index - pair.0
203        }
204
205        if pair.1 > -1 {
206            pair.1 = last_index - pair.1
207        }
208    }
209}