rpkg_rs/resource/pdefs/
hm3_parser.rs1use 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 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 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}