rpkg_rs/resource/pdefs/
h2016_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 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("##") => {} 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, 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}