use std::collections::HashMap;
use crate::{
defs::{ResChunk, ResTableRef},
res_value::{ARGB4, ARGB8, RGB4, RGB8, ResValue},
string_pool::StringPoolHandler,
table::{
Packages, ResTable, ResTableConfig, ResTableEntryFlags, ResTableType, ResTableTypeFlags,
ResTableTypeSpec,
},
traits::Mergeable,
};
#[derive(Debug, Clone)]
pub enum ResourceValue {
Undefined,
Empty,
Float(f32),
Int(i32),
Ref(ResTableRef),
Attr(ResTableRef),
String(String),
Dimension(u32),
Fraction(u32),
DynRef(ResTableRef),
DynAttr(ResTableRef),
Bool(bool),
RGB4(RGB4),
ARGB4(ARGB4),
RGB8(RGB8),
ARGB8(ARGB8),
}
impl ResourceValue {
pub fn into_res_value(self, strings: &mut StringPoolHandler) -> ResValue {
ResValue {
data: match self {
ResourceValue::Undefined => crate::res_value::ResValueType::Null(
crate::res_value::ResTypeNullType::Undefined,
),
ResourceValue::Empty => {
crate::res_value::ResValueType::Null(crate::res_value::ResTypeNullType::Empty)
}
ResourceValue::Float(f) => crate::res_value::ResValueType::Float(f),
ResourceValue::Int(i) => crate::res_value::ResValueType::IntDec(i as u32),
ResourceValue::Ref(res_table_ref) => {
crate::res_value::ResValueType::Reference(res_table_ref)
}
ResourceValue::Attr(res_table_ref) => {
crate::res_value::ResValueType::Attribute(res_table_ref)
}
ResourceValue::String(s) => {
crate::res_value::ResValueType::String(strings.allocate(s.into()))
}
ResourceValue::Dimension(d) => crate::res_value::ResValueType::Dimension(d),
ResourceValue::Fraction(f) => crate::res_value::ResValueType::Fraction(f),
ResourceValue::DynRef(res_table_ref) => {
crate::res_value::ResValueType::DynamicReference(res_table_ref)
}
ResourceValue::DynAttr(res_table_ref) => {
crate::res_value::ResValueType::DynamicAttribute(res_table_ref)
}
ResourceValue::Bool(b) => crate::res_value::ResValueType::IntBoolean(b.into()),
ResourceValue::RGB4(rgb4) => crate::res_value::ResValueType::IntColorRGB4(rgb4),
ResourceValue::ARGB4(argb4) => crate::res_value::ResValueType::IntColorARGB4(argb4),
ResourceValue::RGB8(rgb8) => crate::res_value::ResValueType::IntColorRGB8(rgb8),
ResourceValue::ARGB8(argb8) => crate::res_value::ResValueType::IntColorARGB8(argb8),
},
}
}
pub fn from_res_value(val: ResValue, strings: &StringPoolHandler) -> Self {
match val.data {
crate::res_value::ResValueType::Null(null) => match null {
crate::res_value::ResTypeNullType::Undefined => Self::Undefined,
crate::res_value::ResTypeNullType::Empty => Self::Empty,
},
crate::res_value::ResValueType::Reference(r) => Self::Ref(r),
crate::res_value::ResValueType::Attribute(a) => Self::Attr(a),
crate::res_value::ResValueType::String(s) => strings
.resolve(s)
.map(|v| Self::String(v.to_string()))
.unwrap_or_else(|| Self::Empty),
crate::res_value::ResValueType::Float(f) => Self::Float(f),
crate::res_value::ResValueType::Dimension(d) => Self::Dimension(d),
crate::res_value::ResValueType::Fraction(f) => Self::Fraction(f),
crate::res_value::ResValueType::DynamicReference(dr) => Self::DynRef(dr),
crate::res_value::ResValueType::DynamicAttribute(da) => Self::DynAttr(da),
crate::res_value::ResValueType::IntDec(i) => Self::Int(i as i32),
crate::res_value::ResValueType::IntHex(i) => Self::Int(i as i32),
crate::res_value::ResValueType::IntBoolean(b) => Self::Bool(b.into()),
crate::res_value::ResValueType::IntColorARGB8(argb8) => Self::ARGB8(argb8),
crate::res_value::ResValueType::IntColorRGB8(rgb8) => Self::RGB8(rgb8),
crate::res_value::ResValueType::IntColorARGB4(argb4) => Self::ARGB4(argb4),
crate::res_value::ResValueType::IntColorRGB4(rgb4) => Self::RGB4(rgb4),
}
}
}
#[derive(Debug, Clone)]
pub enum ResourceEntryData {
KeyPair {
key: String,
value: ResourceValue,
},
Map {
key: String,
parent: ResTableRef,
value: HashMap<ResTableRef, ResourceValue>,
},
Compact {
value: u32,
},
}
impl ResourceEntryData {
pub fn set_string(&mut self, name: String) {
match self {
ResourceEntryData::KeyPair { key: _, value } => *value = ResourceValue::String(name),
ResourceEntryData::Map {
key,
parent: _,
value: _,
} => {
*self = ResourceEntryData::KeyPair {
key: key.to_string(),
value: ResourceValue::String(name),
}
}
ResourceEntryData::Compact { value: _ } => todo!(),
}
}
}
#[derive(Debug, Clone)]
pub struct ResourceEntry {
pub flags: ResTableEntryFlags,
pub data: ResourceEntryData,
}
impl ResourceEntry {
pub fn set_string(&mut self, name: String) {
self.data.set_string(name);
}
}
impl Mergeable for ResourceEntry {
type Returns = ();
fn merge(&mut self, other: Self) -> Self::Returns {
*self = other; }
}
#[derive(Debug, Clone)]
pub struct ResourceType {
pub name: String,
pub entries: HashMap<u16, EntryHandle>,
}
impl Mergeable for ResourceType {
type Returns = ();
fn merge(&mut self, other: Self) -> Self::Returns {
for (id, oent) in other.entries {
let sent = self.entries.get_mut(&id);
if let Some(sent) = sent {
sent.merge(oent);
} else {
self.entries.insert(id, oent);
}
}
}
}
impl ResourceType {
pub fn resolve(&self, reference: ResTableRef) -> Option<&EntryHandle> {
self.entries.get(&reference.entry_index)
}
pub fn resolve_mut(&mut self, reference: ResTableRef) -> Option<&mut EntryHandle> {
self.entries.get_mut(&reference.entry_index)
}
pub fn find_entry<'a>(&'a self, key: &str) -> Option<(u16, &'a ResourceEntry)> {
self.entries.iter().find_map(|e| {
let Some(ent) = e.1.entries.iter().next() else {
return None;
}; match &ent.1.data {
ResourceEntryData::KeyPair { key: k, value: _ } => {
if k == key {
Some((*e.0, ent.1))
} else {
None
}
}
ResourceEntryData::Map {
key: k,
parent: _,
value: _,
} => {
if k == key {
Some((*e.0, ent.1))
} else {
None
}
}
ResourceEntryData::Compact { value: _ } => todo!(),
}
})
}
}
#[derive(Debug, Clone)]
pub struct ResourcePackage {
pub name: String,
pub types: HashMap<u8, ResourceType>,
}
impl Mergeable for ResourcePackage {
type Returns = ();
fn merge(&mut self, other: Self) -> Self::Returns {
for (id, otyp) in other.types {
let styp = self.types.get_mut(&id);
if let Some(styp) = styp {
styp.merge(otyp);
} else {
self.types.insert(id, otyp);
}
}
}
}
impl ResourcePackage {
pub fn resolve(&self, reference: ResTableRef) -> Option<&EntryHandle> {
self.types.get(&reference.type_index)?.resolve(reference)
}
pub fn resolve_mut(&mut self, reference: ResTableRef) -> Option<&mut EntryHandle> {
self.types
.get_mut(&reference.type_index)?
.resolve_mut(reference)
}
pub fn find_type<'a>(&'a self, name: &str) -> Option<(u8, &'a ResourceType)> {
self.types.iter().find_map(|t| {
if t.1.name == name {
Some((*t.0, t.1))
} else {
None
}
})
}
}
#[derive(Debug, Clone)]
pub struct ResourceTable {
pub packages: HashMap<u32, ResourcePackage>,
}
impl From<ResourceTable> for ResTable {
fn from(value: ResourceTable) -> Self {
let mut table_sp = StringPoolHandler::new_empty();
let mut new_pkgs = Vec::with_capacity(value.packages.len());
for (id, pkg) in value.packages {
let mut sp_type = StringPoolHandler::new_empty();
let mut sp_key = StringPoolHandler::new_empty();
let mut new_chunks: Vec<ResChunk> = Vec::new();
for (typ_id, typ) in pkg.types {
sp_type.write(typ.name.clone(), (typ_id - 1).into());
let (masks, data) = typ.group_by_config_and_mask();
let spec = ResTableTypeSpec {
id: typ_id,
types_count: data.len() as u16,
config_masks: masks,
};
new_chunks.push(ResChunk {
data: crate::defs::ResTypeValue::TableSpec(spec),
});
for (config, cfg_entry) in data {
let mut new_entries = Vec::new();
for (entry_id, entry) in cfg_entry {
new_entries.push((
entry_id as usize,
Some(crate::table::ResTableEntry {
flags: entry.flags,
data: match entry.data {
ResourceEntryData::KeyPair { key, value } => {
crate::table::ResTableEntryValue::ResValue(
crate::table::ResTableResValueEntry {
key: sp_key.allocate(key.into()),
data: value.into_res_value(&mut table_sp),
},
)
}
ResourceEntryData::Map { key, parent, value } => {
let mut new_map = Vec::with_capacity(value.len());
for (map_key, valu) in value {
new_map.push(crate::table::ResTableMap {
name: map_key,
value: valu.into_res_value(&mut table_sp),
})
}
crate::table::ResTableEntryValue::Map(
crate::table::ResTableMapEntry {
key: sp_key.allocate(key.into()),
parent,
map: new_map,
},
)
}
ResourceEntryData::Compact { value } => {
crate::table::ResTableEntryValue::Compact(value)
}
},
}),
));
}
let flags = ResTableTypeFlags::default();
let new_typ = ResTableType {
id: typ_id,
entries: new_entries,
flags,
config: config,
};
new_chunks.push(ResChunk {
data: crate::defs::ResTypeValue::TableType(new_typ),
});
}
}
new_pkgs.push(crate::table::ResTablePackage {
id,
name: pkg.name,
last_public_type: 0,
last_public_key: 0,
type_id_offset: 0,
string_pool_type: sp_type,
string_pool_key: sp_key,
chunks: new_chunks,
})
}
Self {
string_pool: table_sp,
packages: Packages::new(new_pkgs),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct EntryHandle {
pub entries: HashMap<(u32, ResTableConfig), ResourceEntry>,
}
impl Mergeable for EntryHandle {
type Returns = ();
fn merge(&mut self, other: Self) -> Self::Returns {
for (id, oent) in other.entries {
let sent = self.entries.get_mut(&id);
if let Some(sent) = sent {
sent.merge(oent);
} else {
self.entries.insert(id, oent);
}
}
}
}
impl EntryHandle {
pub fn first_mut(&mut self) -> Option<&mut ResourceEntry> {
self.entries.values_mut().next()
}
}
impl ResourceTable {
pub fn resolve(&self, reference: ResTableRef) -> Option<&EntryHandle> {
self.packages
.get(&(reference.package_index as u32))?
.resolve(reference)
}
pub fn resolve_mut(&mut self, reference: ResTableRef) -> Option<&mut EntryHandle> {
self.packages
.get_mut(&(reference.package_index as u32))?
.resolve_mut(reference)
}
pub fn find_package<'a>(&'a self, name: &str) -> Option<(u32, &'a ResourcePackage)> {
self.packages.iter().find_map(|p| {
if p.1.name == name {
Some((*p.0, p.1))
} else {
None
}
})
}
}
impl Mergeable for ResourceTable {
type Returns = ();
fn merge(&mut self, other: Self) -> Self::Returns {
for (id, opkg) in other.packages {
let spkg = self.packages.get_mut(&id);
if let Some(spkg) = spkg {
spkg.merge(opkg);
} else {
self.packages.insert(id, opkg);
}
}
}
}
impl ResourceType {
pub fn group_by_config_and_mask(
self,
) -> (Vec<u32>, HashMap<ResTableConfig, Vec<(u16, ResourceEntry)>>) {
let mut outer_map = HashMap::new();
let mut config_masks = Vec::new();
for (id, entries) in self.entries {
for ((mask, cfg), entry) in entries.entries {
let outer_ent: &mut Vec<(u16, ResourceEntry)> = outer_map.entry(cfg).or_default();
outer_ent.push((id, entry));
if id as usize >= config_masks.len() {
config_masks.resize(id as usize + 1, 0);
}
*config_masks.get_mut(id as usize).unwrap() = mask;
}
}
(config_masks, outer_map)
}
}
impl From<ResTable> for ResourceTable {
fn from(value: ResTable) -> Self {
let pkgs = value.packages.into_packages();
let mut new_pkgs = HashMap::with_capacity(pkgs.len());
for pkg in pkgs {
let mut new_types: HashMap<u8, ResourceType> = HashMap::new();
let mut current_spec = None;
for chunk in pkg.chunks {
match chunk.data {
crate::defs::ResTypeValue::TableSpec(spec) => {
current_spec = Some(spec);
}
crate::defs::ResTypeValue::TableType(typ) => {
let mut new_entries = HashMap::with_capacity(typ.entries.len());
let mut spec = current_spec
.clone()
.unwrap_or_else(|| ResTableTypeSpec::default());
for (entry_id, entry) in typ.entries {
let Some(entry) = entry else { continue };
if spec.id != typ.id {
spec = ResTableTypeSpec::default();
}
let data = match entry.data {
crate::table::ResTableEntryValue::ResValue(res_val) => {
ResourceEntryData::KeyPair {
key: res_val
.key
.resolve(&pkg.string_pool_key)
.unwrap_or_else(|| "")
.to_string(),
value: ResourceValue::from_res_value(
res_val.data,
&value.string_pool,
),
}
}
crate::table::ResTableEntryValue::Map(map) => {
let mut map_vals = HashMap::with_capacity(map.map.len());
for val in map.map {
map_vals.insert(
val.name,
ResourceValue::from_res_value(
val.value,
&value.string_pool,
),
);
}
ResourceEntryData::Map {
key: map
.key
.resolve(&pkg.string_pool_key)
.unwrap_or_else(|| "")
.to_string(),
parent: map.parent,
value: map_vals,
}
}
crate::table::ResTableEntryValue::Compact(v) => {
ResourceEntryData::Compact { value: v }
}
};
new_entries.insert(
entry_id as u16,
ResourceEntry {
flags: entry.flags,
data,
},
);
}
let Some(name) = pkg.string_pool_type.resolve((typ.id - 1).into()) else {
continue;
};
let existing_type = new_types.get_mut(&typ.id);
if let Some(e_type) = existing_type {
for (e_id, entry) in new_entries {
let mask =
*spec.config_masks.get(e_id as usize).unwrap_or_else(|| &0);
let existing = e_type.entries.entry(e_id).or_default();
existing.entries.insert((mask, typ.config), entry);
}
} else {
new_types.insert(
typ.id,
ResourceType {
name: name.to_string(),
entries: new_entries
.into_iter()
.map(|(k, v)| {
(
k,
EntryHandle {
entries: HashMap::from_iter([(
(
*spec
.config_masks
.get(k as usize)
.unwrap_or_else(|| &0),
typ.config,
),
v,
)]),
},
)
})
.collect(),
},
);
}
}
_ => todo!(),
}
}
new_pkgs.insert(
pkg.id,
ResourcePackage {
name: pkg.name,
types: new_types,
},
);
}
Self { packages: new_pkgs }
}
}