rpkg_rs/resource/pdefs/
h2016_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 H2016Parser;
10
11impl PackageDefinitionParser for H2016Parser {
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 langdlc_regex = regex!(r"#langdlc ([A-z]+)");
27
28        let resource_path_regex = regex!(r"(\[[a-z]+:/.+?]).([a-z]+)");
29
30        for line in deciphered_data.lines() {
31            let trimmed_line = line.trim();
32
33            match trimmed_line {
34                _ if trimmed_line.starts_with("##") => {} //comment
35                line if partition_regex.is_match(trimmed_line) => {
36                    if let Some(m) = partition_regex.captures_iter(line).next() {
37                        let part_type = match &m[1] {
38                            "dlc" => PartitionType::Dlc,
39                            "chunk" => PartitionType::Standard,
40                            _ => PartitionType::Standard,
41                        };
42
43                        partitions.push(PartitionInfo {
44                            name: try_read_partition_name(previous_lines.to_vec()),
45                            parent: partitions.iter().map(|p| p.id.clone()).next(),
46                            id: PartitionId {
47                                part_type: part_type.clone(),
48                                index: partitions
49                                    .iter()
50                                    .filter(|&p| p.id.part_type == part_type)
51                                    .count(),
52                            },
53                            patch_level: m[2].parse().unwrap(),
54                            roots: vec![],
55                        });
56                    }
57                }
58                line if langdlc_regex.is_match(trimmed_line) => {
59                    if let Some(m) = langdlc_regex.captures_iter(line).next() {
60                        let language_code = &m[1];
61                        let mut lang_partitions = vec![];
62                        for partition in partitions.iter() {
63                            lang_partitions.push(PartitionInfo {
64                                name: None,
65                                parent: Some(partition.id.clone()),
66                                id: PartitionId {
67                                    part_type: match partition.id.part_type {
68                                        PartitionType::Standard => PartitionType::LanguageStandard(
69                                            language_code.parse().unwrap(),
70                                        ),
71                                        PartitionType::Dlc => PartitionType::LanguageDlc(
72                                            language_code.parse().unwrap(),
73                                        ),
74                                        _ => PartitionType::LanguageDlc(
75                                            language_code.parse().unwrap(),
76                                        ),
77                                    },
78                                    index: partition.id.index,
79                                },
80                                patch_level: 0, //doesn't matter, this will be checked later
81                                roots: vec![],
82                            });
83                        }
84                        partitions.append(&mut lang_partitions);
85                    }
86                }
87                line if resource_path_regex.is_match(trimmed_line) => {
88                    if let Some(m) = resource_path_regex.captures_iter(line).next() {
89                        if let Some(current_partition) = partitions.last_mut() {
90                            if let Ok(rid) =
91                                ResourceID::from_str(format!("{}.{}", &m[1], &m[2]).as_str())
92                            {
93                                current_partition.roots.push(rid);
94                            }
95                        }
96                    }
97                }
98                _ => {}
99            }
100
101            previous_lines[0] = previous_lines[1];
102            previous_lines[1] = line;
103        }
104
105        Ok(partitions)
106    }
107}
108
109fn try_read_partition_name(lines: Vec<&str>) -> Option<String> {
110    let reg = regex!(r"## --- (?:DLC|Chunk )\d{2} (.*)");
111    for line in lines {
112        if reg.is_match(line) {
113            if let Some(m) = reg.captures_iter(line).next() {
114                return Some(m[1].to_string());
115            }
116        }
117    }
118    None
119}