winres_edit/
version.rs

1use crate::error::Error;
2use crate::resources::Resource;
3use crate::result::Result;
4use crate::utils::*;
5use manual_serializer::*;
6use std::collections::HashMap;
7use std::fmt;
8use std::sync::Arc;
9
10/// Helper representing structure data header used in
11/// [`VS_VERSIONINFO`](https://learn.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo)
12/// and all related data structures.
13#[derive(Debug)]
14pub struct Header {
15    pub length: usize,
16    pub value_length: usize,
17    pub data_type: DataType,
18    pub key: String,
19    pub last: usize,
20}
21
22impl Header {
23    pub fn new(length: usize, value_length: usize, data_type: DataType, key: &str) -> Header {
24        Header {
25            length,
26            value_length,
27            data_type,
28            key: key.to_string(),
29            last: 0,
30        }
31    }
32}
33
34impl TrySerialize for Header {
35    type Error = Error;
36    fn try_serialize(&self, dest: &mut Serializer) -> Result<()> {
37        dest.try_align_u32()?;
38        dest.try_store_u16le(self.length as u16)?;
39        dest.try_store_u16le(self.value_length as u16)?;
40        match self.data_type {
41            DataType::Binary => {
42                dest.try_store_u16le(0)?;
43            }
44            DataType::Text => {
45                dest.try_store_u16le(1)?;
46            }
47        };
48        dest.try_store_utf16le_sz(&self.key)?;
49        dest.try_align_u32()?;
50        Ok(())
51    }
52}
53
54impl TryDeserialize for Header {
55    type Error = Error;
56
57    fn try_deserialize(src: &mut Deserializer) -> Result<Header> {
58        src.try_align_u32()?;
59
60        let cursor = src.cursor();
61        let length = src.try_load_u16le()? as usize;
62        let value_length = src.try_load_u16le()? as usize;
63        let data_type = src.try_load_u16le()?;
64        // println!("@ cursor: {cursor} length: {length} value_length: {value_length} data_type: {data_type}");
65        let data_type = match data_type {
66            0 => DataType::Binary,
67            1 => DataType::Text,
68            _ => return Err("Header::try_deserealize(): invalid resource data type (must be 1 or 0) - possible misalignment/corruption".into())
69        };
70        let key = src.try_load_utf16le_sz()?;
71
72        let padding = src.cursor() % 4;
73        src.try_offset(padding)?;
74        let last = cursor + length;
75
76        let header = Header {
77            length,
78            value_length,
79            data_type,
80            key,
81            last,
82        };
83        // println!("{:#?}", header);
84        Ok(header)
85    }
86}
87
88fn try_build_struct(
89    key: &str,
90    data_type: DataType,
91    value_len: usize,
92    value: &[u8],
93) -> Result<Vec<u8>> {
94    let mut dest = Serializer::new(4096);
95    let header = Header::new(0, 0, data_type, key);
96    dest.try_store(&header)?;
97    dest.try_store_u8_slice(value)?;
98    let mut vec = dest.to_vec();
99    store_u16le(&mut vec[0..2], dest.len() as u16);
100    store_u16le(&mut vec[2..4], value_len as u16);
101    Ok(vec)
102}
103
104/// Windows Version value represented as `[u16;4]` array.
105/// This version struct is helpful for serialization and
106/// deserialization of the versions packed into a `u64` value
107/// represented as 2 LE-encoded `u32` (DWORD) values.
108#[derive(Debug, Clone, Default)]
109pub struct Version([u16; 4]);
110
111impl TryDeserialize for Version {
112    type Error = Error;
113    fn try_deserialize(src: &mut Deserializer) -> Result<Self> {
114        let ms = src.try_load_u32le()?;
115        let ls = src.try_load_u32le()?;
116        Ok(Version([
117            (ms >> 16) as u16,
118            (ms & 0xffff) as u16,
119            (ls >> 16) as u16,
120            (ls & 0xffff) as u16,
121        ]))
122    }
123}
124
125impl TrySerialize for Version {
126    type Error = Error;
127    fn try_serialize(&self, dest: &mut Serializer) -> Result<()> {
128        dest.try_store_u32le((self.0[0] as u32) << 16 | (self.0[1] as u32))?;
129        dest.try_store_u32le((self.0[2] as u32) << 16 | (self.0[3] as u32))?;
130        Ok(())
131    }
132}
133
134impl fmt::Display for Version {
135    fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
136        if self.0[3] == 0 {
137            write!(f, "{}.{}.{}", self.0[0], self.0[1], self.0[2])?;
138        } else {
139            write!(f, "{}.{}.{}.{}", self.0[0], self.0[1], self.0[2], self.0[3])?;
140        }
141        Ok(())
142    }
143}
144
145/// Date helper used for serializing and deserializing 2 LE-encoded u32
146/// values into a single `u64` value.
147#[derive(Debug, Clone, Default)]
148pub struct Date(u64);
149
150impl TryDeserialize for Date {
151    type Error = Error;
152    fn try_deserialize(src: &mut Deserializer) -> Result<Self> {
153        let ms = src.try_load_u32le()? as u64;
154        let ls = src.try_load_u32le()? as u64;
155        Ok(Date(ms << 32 | ls))
156    }
157}
158
159impl TrySerialize for Date {
160    type Error = Error;
161    fn try_serialize(&self, dest: &mut Serializer) -> Result<()> {
162        dest.try_store_u32le((self.0 >> 32) as u32)?;
163        dest.try_store_u32le((self.0 & 0xffffffff) as u32)?;
164        Ok(())
165    }
166}
167
168impl fmt::Display for Date {
169    fn fmt(&self, f: &mut fmt::Formatter) -> std::result::Result<(), fmt::Error> {
170        write!(f, "{}", self.0)?;
171        Ok(())
172    }
173}
174
175/// Rust representation of [`VS_FIXEDFILEINFO`](https://learn.microsoft.com/en-us/windows/win32/api/VerRsrc/ns-verrsrc-vs_fixedfileinfo)
176/// structure.
177#[derive(Debug, Clone)]
178pub struct FileInfo {
179    pub signature: u32,
180    pub struc_version: u32,
181    pub file_version: Version,
182    pub product_version: Version,
183    pub file_flags_mask: u32,
184    pub file_flags: u32,
185    pub file_os: u32,
186    pub file_type: u32,
187    pub file_subtype: u32,
188    pub file_date: Date,
189}
190
191impl Default for FileInfo {
192    fn default() -> Self {
193        FileInfo {
194            signature: 0xfeef04bd,
195            struc_version: 0,
196            file_version: Version::default(),
197            product_version: Version::default(),
198            file_flags_mask: 0,
199            file_flags: 0,
200            file_os: 0,
201            file_type: 0,
202            file_subtype: 0,
203            file_date: Date::default(),
204        }
205    }
206}
207
208impl FileInfo {
209    pub fn print(&self) {
210        println!("signature: 0x{:x}", self.signature);
211        println!("struc_version: 0x{:x}", self.struc_version);
212        println!("file_version: {}", self.file_version);
213        println!("product_version: {}", self.product_version);
214        println!("file_flags_mask: 0x{:x}", self.file_flags_mask);
215        println!("file_flags: 0x{:x}", self.file_flags);
216        println!("file_os: 0x{:x}", self.file_os);
217        println!("file_type: 0x{:x}", self.file_type);
218        println!("file_subtype: 0x{:x}", self.file_subtype);
219        println!("file_date: {}", self.file_date);
220    }
221}
222// impl TryFrom<&mut Deserializer<'_>> for FileInfo {
223impl TryDeserialize for FileInfo {
224    type Error = Error;
225    fn try_deserialize(src: &mut Deserializer) -> Result<FileInfo> {
226        // let src = Deserializer::new(data);
227
228        let info = FileInfo {
229            signature: src.try_load_u32le()?,
230            struc_version: src.try_load_u32le()?,
231            file_version: src.try_load()?,
232            product_version: src.try_load()?,
233            file_flags_mask: src.try_load_u32le()?,
234            file_flags: src.try_load_u32le()?,
235            file_os: src.try_load_u32le()?,
236            file_type: src.try_load_u32le()?,
237            file_subtype: src.try_load_u32le()?,
238            file_date: src.try_load()?,
239        };
240
241        if info.signature != 0xfeef04bd {
242            return Err(format!("FileInfo: invalid signature 0x{:8x}", info.signature).into());
243        }
244
245        Ok(info)
246    }
247}
248
249impl TrySerialize for FileInfo {
250    type Error = Error;
251    fn try_serialize(&self, dest: &mut Serializer) -> Result<()> {
252        dest.try_store_u32le(self.signature)?
253            .try_store_u32le(self.struc_version)?
254            .try_store(&self.file_version)?
255            .try_store(&self.product_version)?
256            .try_store_u32le(self.file_flags_mask)?
257            .try_store_u32le(self.file_flags)?
258            .try_store_u32le(self.file_os)?
259            .try_store_u32le(self.file_type)?
260            .try_store_u32le(self.file_subtype)?
261            .try_store(&self.file_date)?;
262
263        Ok(())
264    }
265}
266
267/// Helper enum for serializing and deserializing StringFileInfo and VarFileInfo child structures.
268#[derive(Debug, Clone)]
269pub enum VersionInfoChild {
270    StringFileInfo {
271        tables: HashMap<String, HashMap<String, Data>>,
272    },
273    VarFileInfo {
274        vars: HashMap<String, Vec<u32>>,
275    },
276}
277
278/// Wrapper of the underlying data that may be represented as a binary buffer or a text string.
279#[derive(Debug, Clone)]
280pub enum Data {
281    Binary(Vec<u8>),
282    Text(String),
283}
284
285impl TrySerialize for VersionInfoChild {
286    type Error = Error;
287    fn try_serialize(&self, dest: &mut Serializer) -> Result<()> {
288        match self {
289            VersionInfoChild::StringFileInfo { tables } => {
290                for (key_lang, map) in tables {
291                    let mut lang_records = Serializer::default();
292                    for (key_record, data) in map {
293                        let (data_type, data) = match data {
294                            Data::Binary(data) => (DataType::Binary, data.clone()),
295                            Data::Text(text) => (DataType::Text, string_to_u8vec_sz(text)),
296                        };
297
298                        let string_record =
299                            try_build_struct(key_record, data_type, data.len() / 2, &data)?;
300                        lang_records.try_align_u32()?;
301                        lang_records.try_store_u8_slice(&string_record)?;
302                    }
303
304                    let string_table =
305                        try_build_struct(key_lang, DataType::Binary, 0, &lang_records.to_vec())?;
306                    let string_file_info =
307                        try_build_struct("StringFileInfo", DataType::Binary, 0, &string_table)?;
308                    dest.try_align_u32()?;
309                    dest.try_store_u8_slice(&string_file_info)?;
310                }
311            }
312            VersionInfoChild::VarFileInfo { vars } => {
313                let mut var_records = Serializer::default();
314                for (k, data) in vars {
315                    let var_record = try_build_struct(
316                        k,
317                        DataType::Binary,
318                        data.len() / 2,
319                        &u32slice_to_u8vec(data),
320                    )?;
321                    var_records.try_align_u32()?;
322                    var_records.try_store_u8_slice(&var_record)?;
323                }
324                let var_file_info =
325                    try_build_struct("VarFileInfo", DataType::Binary, 0, &var_records.to_vec())?;
326                dest.try_align_u32()?;
327                dest.try_store_u8_slice(&var_file_info)?;
328            }
329        }
330
331        Ok(())
332    }
333}
334
335impl TryDeserialize for VersionInfoChild {
336    type Error = Error;
337    fn try_deserialize(src: &mut Deserializer) -> Result<VersionInfoChild> {
338        let header: Header = src.try_load()?;
339
340        let data = match header.key.as_str() {
341            "StringFileInfo" => {
342                let mut tables = HashMap::new();
343                while src.cursor() < header.last {
344                    let string_table_header: Header = src.try_load()?;
345                    let lang = string_table_header.key;
346                    let mut data = HashMap::new();
347
348                    while src.cursor() < string_table_header.last {
349                        let string_header: Header = src.try_load()?;
350                        match string_header.data_type {
351                            DataType::Binary => {
352                                let len = string_header.value_length * 2;
353                                let vec = src.try_load_u8_vec(len)?;
354                                data.insert(string_header.key, Data::Binary(vec));
355                            }
356                            DataType::Text => {
357                                let text = src.try_load_utf16le_sz()?;
358                                data.insert(string_header.key, Data::Text(text));
359                            }
360                        };
361                    }
362
363                    tables.insert(lang, data);
364                }
365
366                VersionInfoChild::StringFileInfo { tables }
367            }
368            "VarFileInfo" => {
369                let mut vars = HashMap::new();
370                while src.cursor() < header.last {
371                    // let var_header = Header::try_from(&mut *src)?;
372                    let var_header: Header = src.try_load()?;
373
374                    let mut values = Vec::new();
375                    while src.cursor() < var_header.last {
376                        values.push(src.try_load_u32le()?);
377                    }
378
379                    vars.insert(var_header.key, values);
380                }
381
382                VersionInfoChild::VarFileInfo { vars }
383            }
384            _ => return Err(format!("Unknown child type: {}", header.key).into()),
385        };
386
387        Ok(data)
388    }
389}
390
391/// Data type flag indicating if data is encoded as a binary or a text string.
392#[derive(Debug, Clone)]
393pub enum DataType {
394    Binary,
395    Text,
396}
397
398/// [`VS_VERSIONINFO`](https://learn.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo)
399/// resource structure representation.
400#[derive(Debug, Clone)]
401pub struct VersionInfo {
402    /// Associated [`Resource`]
403    pub resource: Arc<Resource>,
404    /// Binary or text data type
405    pub data_type: DataType,
406    /// resource key string (language code-page hex string)
407    pub key: String,
408    /// VS_FIXEDFILEINFO representation.
409    /// [https://learn.microsoft.com/en-us/windows/win32/api/VerRsrc/ns-verrsrc-vs_fixedfileinfo](https://learn.microsoft.com/en-us/windows/win32/api/VerRsrc/ns-verrsrc-vs_fixedfileinfo)
410    pub info: FileInfo,
411    /// VS_VERSIONINFO child structures. One or multiple of:
412    /// - [StringFileInfo](https://learn.microsoft.com/en-us/windows/win32/menurc/stringfileinfo)
413    /// - [VarFileInfo](https://learn.microsoft.com/en-us/windows/win32/menurc/varfileinfo)
414    pub children: Vec<VersionInfoChild>,
415}
416
417impl TryFrom<Arc<Resource>> for VersionInfo {
418    type Error = Error;
419    fn try_from(resource: Arc<Resource>) -> Result<VersionInfo> {
420        // let clone = resource.clone();
421        let data = resource.encoded.lock().unwrap();
422        let mut src = Deserializer::new(&data);
423
424        let header: Header = src.try_load()?;
425        let info: FileInfo = src.try_load()?;
426        let skip = src.cursor() % 4;
427        src.try_offset(skip)?;
428
429        let mut children = Vec::new();
430        let mut remaining = src.remaining();
431        while remaining > 0 {
432            let child: VersionInfoChild = src.try_load()?;
433            children.push(child);
434            remaining = src.remaining();
435        }
436
437        let info = VersionInfo {
438            resource: resource.clone(),
439            data_type: header.data_type,
440            key: header.key,
441            info,
442            children,
443        };
444
445        Ok(info)
446    }
447}
448
449impl VersionInfo {
450    pub fn try_to_vec(&self) -> Result<Vec<u8>> {
451        let mut dest = Serializer::default();
452
453        let mut child_data = Serializer::default();
454        for child in &self.children {
455            child_data.try_store(child)?;
456            child_data.try_align_u32()?;
457        }
458        let child_data = child_data.to_vec();
459
460        let file_info_data = Serializer::default().try_store(&self.info)?.to_vec();
461
462        let data = Serializer::default()
463            .try_store_u8_slice(&file_info_data)?
464            .try_align_u32()?
465            .try_store_u8_slice(&child_data)?
466            .to_vec();
467
468        let version_info = try_build_struct(
469            "VS_VERSION_INFO",
470            DataType::Binary,
471            file_info_data.len(),
472            &data,
473        )?;
474        dest.try_store_u8_slice(&version_info)?;
475
476        Ok(dest.to_vec())
477    }
478
479    pub fn set_file_version(&mut self, v: &[u16; 4]) -> &mut Self {
480        self.info.file_version = Version(*v);
481        self.insert_string("FileVersion", &Version(*v).to_string());
482        self
483    }
484
485    pub fn set_product_version(&mut self, v: &[u16; 4]) -> &mut Self {
486        self.info.product_version = Version(*v);
487        self.insert_string("ProductVersion", &Version(*v).to_string());
488        self
489    }
490
491    pub fn set_version(&mut self, v: &[u16; 4]) -> &mut Self {
492        self.set_file_version(v);
493        self.set_product_version(v);
494        self
495    }
496
497    pub fn replace_string(&mut self, key: &str, text: &str) -> &mut Self {
498        for child in self.children.iter_mut() {
499            if let VersionInfoChild::StringFileInfo { tables } = child {
500                for table in tables.values_mut() {
501                    if table.get(key).is_some() {
502                        table.insert(key.to_string(), Data::Text(text.to_string()));
503                    }
504                }
505            }
506        }
507        self
508    }
509
510    pub fn insert_string(&mut self, key: &str, text: &str) -> &mut Self {
511        for child in self.children.iter_mut() {
512            if let VersionInfoChild::StringFileInfo { tables } = child {
513                for table in tables.values_mut() {
514                    table.insert(key.to_string(), Data::Text(text.to_string()));
515                }
516            }
517        }
518        self
519    }
520
521    pub fn insert_strings(&mut self, tuples: &[(&str, &str)]) -> &mut Self {
522        for child in self.children.iter_mut() {
523            if let VersionInfoChild::StringFileInfo { tables } = child {
524                for table in tables.values_mut() {
525                    for (key, text) in tuples {
526                        table.insert(key.to_string(), Data::Text(text.to_string()));
527                    }
528                }
529            }
530        }
531        self
532    }
533
534    pub fn remove_string(&mut self, key: &str) -> &mut Self {
535        for child in self.children.iter_mut() {
536            if let VersionInfoChild::StringFileInfo { tables } = child {
537                for table in tables.values_mut() {
538                    table.remove(key);
539                }
540            }
541        }
542        self
543    }
544
545    pub fn ensure_language(&mut self, lang: &str) -> &mut Self {
546        for child in self.children.iter_mut() {
547            if let VersionInfoChild::StringFileInfo { tables } = child {
548                if tables.get(lang).is_none() {
549                    tables.insert(lang.to_string(), HashMap::new());
550                }
551            }
552        }
553        self
554    }
555
556    pub fn update(&mut self) -> Result<()> {
557        self.resource.replace(&self.try_to_vec()?)?.update()?;
558        Ok(())
559    }
560}