rpkg_rs/resource/pdefs/
hm2_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 HM2Parser;
10
11impl PackageDefinitionParser for HM2Parser {
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::new();
22        let mut previous_lines: [&str; 2] = ["", ""];
23
24        let partition_regex = regex!(r"@([A-z]+) patchlevel=([0-9]+)");
25
26        let resource_path_regex = regex!(r"(\[[a-z]+:/.+?]).([a-z]+)");
27
28        for line in deciphered_data.lines() {
29            let trimmed_line = line.trim();
30
31            match trimmed_line {
32                _ if trimmed_line.starts_with("//") => {} //comment
33                line if partition_regex.is_match(trimmed_line) => {
34                    if let Some(m) = partition_regex.captures_iter(line).next() {
35                        let part_type = if &m[1] == "chunk" {
36                            PartitionType::Standard
37                        } else {
38                            PartitionType::Dlc
39                        };
40
41                        partitions.push(PartitionInfo {
42                            name: try_read_partition_name(previous_lines.to_vec()),
43                            parent: partitions.iter().map(|p| p.id.clone()).next(),
44                            id: PartitionId {
45                                part_type: part_type.clone(),
46                                index: partitions
47                                    .iter()
48                                    .filter(|&p| p.id.part_type == part_type)
49                                    .count(),
50                            },
51                            patch_level: m[2].parse().unwrap(),
52                            roots: vec![],
53                        });
54                    }
55                }
56                line if resource_path_regex.is_match(trimmed_line) => {
57                    if let Some(m) = resource_path_regex.captures_iter(line).next() {
58                        if let Some(current_partition) = partitions.last_mut() {
59                            if let Ok(rid) =
60                                ResourceID::from_str(format!("{}.{}", &m[1], &m[2]).as_str())
61                            {
62                                current_partition.roots.push(rid);
63                            }
64                        }
65                    };
66                }
67                _ => {}
68            }
69
70            previous_lines[0] = previous_lines[1];
71            previous_lines[1] = line;
72        }
73
74        Ok(partitions)
75    }
76}
77
78fn try_read_partition_name(lines: Vec<&str>) -> Option<String> {
79    let reg = regex!(r"// --- (?:DLC|Chunk) \d{2} (.*)");
80    for line in lines {
81        if reg.is_match(line) {
82            if let Some(m) = reg.captures_iter(line).next() {
83                return Some(m[1].to_string());
84            }
85        }
86    }
87    None
88}