rpkg_rs/resource/pdefs/
hm3_parser.rs

1use crate::encryption::xtea::Xtea;
2use crate::misc::resource_id::ResourceID;
3use crate::resource::pdefs::{
4    PackageDefinitionError, PackageDefinitionParser, PartitionId, PartitionInfo, PartitionType,
5};
6use lazy_regex::regex;
7use std::str::FromStr;
8
9pub struct HM3Parser;
10
11impl PackageDefinitionParser for HM3Parser {
12    fn parse(data: &[u8]) -> Result<Vec<PartitionInfo>, PackageDefinitionError> {
13        let deciphered_data = match Xtea::is_encrypted_text_file(data) {
14            true => Xtea::decrypt_text_file(data)?,
15            false => match String::from_utf8(data.to_vec()) {
16                Ok(v) => v,
17                Err(e) => return Err(PackageDefinitionError::TextEncodingError(e)),
18            },
19        };
20
21        let mut partitions: Vec<PartitionInfo> = vec![];
22
23        //define the regex
24        let partition_regex =
25            regex!(r"@partition name=(.+?) parent=(.+?) type=(.+?) patchlevel=(.\d*)");
26        let resource_path_regex = regex!(r"(\[[a-z]+:/.+?]).([a-z]+)");
27
28        //try to match the regex on a per-line basis
29        for line in deciphered_data.lines() {
30            if partition_regex.is_match(line) {
31                if let Some(m) = partition_regex.captures_iter(line).next() {
32                    partitions.push(PartitionInfo {
33                        name: m[1].parse().ok(),
34                        parent: find_parent_id(&partitions, m[2].parse().unwrap()),
35                        id: PartitionId {
36                            part_type: match &m[3] {
37                                "standard" => PartitionType::Standard,
38                                "addon" => PartitionType::Addon,
39                                _ => PartitionType::Standard,
40                            },
41                            index: partitions.len(),
42                        },
43                        patch_level: m[4].parse().unwrap(),
44                        roots: vec![],
45                    });
46                }
47            } else if resource_path_regex.is_match(line) {
48                if let Some(m) = resource_path_regex.captures_iter(line).next() {
49                    if let Some(current_partition) = partitions.last_mut() {
50                        if let Ok(rid) =
51                            ResourceID::from_str(format!("{}.{}", &m[1], &m[2]).as_str())
52                        {
53                            current_partition.roots.push(rid);
54                        }
55                    } else {
56                        return Err(PackageDefinitionError::UnexpectedFormat("ResourceID defined before partition, are you using the correct game version?".to_string()));
57                    }
58                }
59            }
60        }
61        Ok(partitions)
62    }
63}
64
65fn find_parent_id(partitions: &[PartitionInfo], parent_name: String) -> Option<PartitionId> {
66    partitions
67        .iter()
68        .find(|&partition| partition.name.as_ref().is_some_and(|s| s == &parent_name))
69        .map(|parent_partition| parent_partition.id.clone())
70}