1use std::cell::RefCell;
2use std::collections::HashMap;
3
4use log::warn;
5use winnow::combinator::repeat;
6use winnow::prelude::*;
7
8use crate::errors::ARCSError;
9use crate::structs::{
10 ResTableConfig, ResTableEntry, ResTableHeader, ResTablePackage, ResourceValueType, StringPool,
11};
12
13#[derive(Debug)]
18pub struct ARSC {
19 global_string_pool: StringPool,
20 packages: HashMap<u8, ResTablePackage>,
21
22 reference_names: RefCell<HashMap<u32, String>>,
24}
25
26impl ARSC {
27 pub fn new(input: &mut &[u8]) -> Result<ARSC, ARCSError> {
29 if input.len() < 12 {
30 return Err(ARCSError::TooSmallError);
31 }
32
33 let header = ResTableHeader::parse(input).map_err(|_| ARCSError::HeaderError)?;
34
35 if header.package_count < 1 {
36 warn!(
37 "expected at least one resource package, but got {}",
38 header.package_count
39 );
40 }
41
42 let global_string_pool =
43 StringPool::parse(input).map_err(|_| ARCSError::StringPoolError)?;
44
45 let table_packages: Vec<ResTablePackage> =
46 repeat(header.package_count as usize, ResTablePackage::parse)
47 .parse_next(input)
48 .map_err(|_| ARCSError::ResourceTableError)?;
49
50 let packages = match table_packages.len() {
52 0 => HashMap::new(),
53 1 => {
54 let pkg = table_packages
55 .into_iter()
56 .next()
57 .expect("is rust broken? one element must be");
58 HashMap::from([((pkg.header.id & 0xff) as u8, pkg)])
59 }
60 _ => {
61 let mut packages = HashMap::with_capacity(table_packages.len());
62 for pkg in table_packages {
63 let id = (pkg.header.id & 0xff) as u8;
64 if packages.contains_key(&id) {
65 warn!(
66 "malformed resource packages, duplicate package id - 0x{:02x}, skipped",
67 id
68 );
69 continue;
70 }
71
72 packages.insert(id, pkg);
73 }
74 packages
75 }
76 };
77
78 Ok(ARSC {
79 global_string_pool,
80 packages,
81 reference_names: RefCell::new(HashMap::with_capacity(32)),
83 })
84 }
85
86 pub fn get_resource_value(&self, id: u32) -> Option<String> {
90 let config = ResTableConfig::default();
92
93 let (package_id, type_id, entry_id) = self.split_resource_id(id);
94
95 let entry = self
96 .packages
97 .get(&package_id)?
98 .find_entry(&config, type_id, entry_id)?;
99
100 match entry {
101 ResTableEntry::Default(e) => match e.value.data_type {
102 ResourceValueType::Reference => {
103 if e.value.data == id {
105 return None;
106 }
107
108 self.get_resource_value(e.value.data)
109 }
110 _ => Some(e.value.to_string(&self.global_string_pool, Some(self))),
111 },
112 ResTableEntry::NoEntry => None,
114 e => {
115 warn!("for now don't how to handle this: {:#?}", e);
116 None
117 }
118 }
119 }
120
121 pub fn get_resource_value_by_name(&self, name: &str) -> Option<String> {
123 let (&id, _) = self
124 .reference_names
125 .borrow()
126 .iter()
127 .find(|(_, v)| v == &name)?;
128
129 self.get_resource_value(id)
130 }
131
132 pub fn get_resource_name(&self, id: u32) -> Option<String> {
136 if let Some(name) = self.reference_names.borrow().get(&id) {
138 return Some(name.clone());
139 }
140
141 let (package_id, type_id, entry_id) = self.split_resource_id(id);
143
144 let package = self.packages.get(&package_id)?;
146
147 let config = ResTableConfig::default();
150
151 let entry = package.find_entry(&config, type_id, entry_id)?;
153
154 let name = package.get_entry_full_name(entry, type_id)?;
156
157 self.reference_names.borrow_mut().insert(id, name.clone());
159
160 Some(name)
161 }
162
163 #[inline(always)]
165 fn split_resource_id(&self, id: u32) -> (u8, u8, u16) {
166 (
167 (id >> 24) as u8,
168 ((id >> 16) & 0xff) as u8,
169 (id & 0xffff) as u16,
170 )
171 }
172}