Files
APK/axml.rs
T
2026-07-12 19:37:19 +00:00

279 lines
8.2 KiB
Rust

const RES_XML_TYPE: u16 = 0x0003;
const RES_STRING_POOL_TYPE: u16 = 0x0001;
const RES_XML_RESOURCE_MAP_TYPE: u16 = 0x0180;
const RES_XML_START_NAMESPACE_TYPE: u16 = 0x0100;
const RES_XML_END_NAMESPACE_TYPE: u16 = 0x0101;
const RES_XML_START_ELEMENT_TYPE: u16 = 0x0102;
const RES_XML_END_ELEMENT_TYPE: u16 = 0x0103;
const NO_COMMENT: u32 = 0;
const ATTR_RES_IDS: &[(&str, u32)] = &[
("versionCode", 0x0101021b),
("versionName", 0x0101021c),
("minSdkVersion", 0x0101020c),
("targetSdkVersion", 0x01010270),
("label", 0x01010001),
("name", 0x01010003),
("exported", 0x01010010),
("hasCode", 0x0101000c),
("configChanges", 0x0101001f),
];
pub const TYPE_STRING: u8 = 0x03;
pub const TYPE_INT_DEC: u8 = 0x10;
pub const TYPE_INT_BOOL: u8 = 0x12;
struct StringPool {
strings: Vec<String>,
index: std::collections::HashMap<String, u32>,
}
impl StringPool {
fn new() -> Self {
StringPool {
strings: Vec::new(),
index: std::collections::HashMap::new(),
}
}
fn intern(&mut self, s: &str) -> u32 {
if let Some(&i) = self.index.get(s) {
return i;
}
let i = self.strings.len() as u32;
self.strings.push(s.to_string());
self.index.insert(s.to_string(), i);
i
}
fn encode(&self) -> Vec<u8> {
const UTF8_FLAG: u32 = 1 << 8;
let mut offsets = Vec::new();
let mut string_data = Vec::new();
for s in &self.strings {
offsets.push(string_data.len() as u32);
let utf16_units = s.encode_utf16().count();
let bytes = s.as_bytes();
push_utf8_len(&mut string_data, utf16_units);
push_utf8_len(&mut string_data, bytes.len());
string_data.extend_from_slice(bytes);
string_data.push(0);
}
while string_data.len() % 4 != 0 {
string_data.push(0);
}
let string_count = self.strings.len() as u32;
let header_size = 28u32;
let offsets_size = string_count * 4;
let strings_start = header_size + offsets_size;
let chunk_size = strings_start + string_data.len() as u32;
let mut out = Vec::new();
push_u16(&mut out, RES_STRING_POOL_TYPE);
push_u16(&mut out, 28);
push_u32(&mut out, chunk_size);
push_u32(&mut out, string_count);
push_u32(&mut out, 0);
push_u32(&mut out, UTF8_FLAG);
push_u32(&mut out, strings_start);
push_u32(&mut out, 0);
for o in offsets {
push_u32(&mut out, o);
}
out.extend_from_slice(&string_data);
out
}
}
struct Attr {
ns: Option<u32>,
name: u32,
raw_value: i32,
typ: u8,
data: u32,
}
pub struct AxmlBuilder {
pool: StringPool,
body: Vec<u8>,
android_ns_uri: u32,
android_ns_prefix: u32,
line: u32,
}
impl AxmlBuilder {
pub fn new() -> Self {
AxmlBuilder {
pool: StringPool::new(),
body: Vec::new(),
android_ns_uri: 0xFFFF_FFFF,
android_ns_prefix: 0xFFFF_FFFF,
line: 1,
}
}
fn ns_uri(&mut self) -> u32 {
if self.android_ns_uri == 0xFFFF_FFFF {
self.android_ns_uri = self.pool.intern("http://schemas.android.com/apk/res/android");
}
self.android_ns_uri
}
pub fn intern(&mut self, s: &str) -> u32 {
self.pool.intern(s)
}
pub const EMIT_NAMESPACE_CHUNKS: bool = false;
pub fn start(&mut self) {
if !Self::EMIT_NAMESPACE_CHUNKS {
return;
}
let mut c = Vec::new();
push_u16(&mut c, RES_XML_START_NAMESPACE_TYPE);
push_u16(&mut c, 16);
push_u32(&mut c, 24);
push_u32(&mut c, self.line);
push_u32(&mut c, NO_COMMENT);
push_u32(&mut c, self.android_ns_prefix);
push_u32(&mut c, self.android_ns_uri);
self.body.extend_from_slice(&c);
}
pub fn end(&mut self) {
if !Self::EMIT_NAMESPACE_CHUNKS {
return;
}
let mut c = Vec::new();
push_u16(&mut c, RES_XML_END_NAMESPACE_TYPE);
push_u16(&mut c, 16);
push_u32(&mut c, 24);
push_u32(&mut c, self.line);
push_u32(&mut c, 0xFFFF_FFFF);
push_u32(&mut c, self.android_ns_prefix);
push_u32(&mut c, self.android_ns_uri);
self.body.extend_from_slice(&c);
}
pub fn open(&mut self, tag: u32, attrs: &[(u32, u8, u32, i32)]) {
let with_ns: Vec<(u32, u8, u32, i32, bool)> =
attrs.iter().map(|&(n, t, d, r)| (n, t, d, r, true)).collect();
self.open_ns(tag, &with_ns);
}
pub fn open_ns(&mut self, tag: u32, attrs: &[(u32, u8, u32, i32, bool)]) {
let uri = if attrs.iter().any(|a| a.4) { Some(self.ns_uri()) } else { None };
let attr_structs: Vec<Attr> = attrs
.iter()
.map(|&(name, typ, data, raw, android_ns)| Attr {
ns: if android_ns { uri } else { None },
name,
raw_value: raw,
typ,
data,
})
.collect();
let mut c = Vec::new();
let attr_count = attr_structs.len() as u32;
let chunk_size = 16 + 20 + attr_count * 20;
push_u16(&mut c, RES_XML_START_ELEMENT_TYPE);
push_u16(&mut c, 16);
push_u32(&mut c, chunk_size);
push_u32(&mut c, self.line);
push_u32(&mut c, NO_COMMENT);
push_u32(&mut c, 0);
push_u32(&mut c, tag);
push_u16(&mut c, 20);
push_u16(&mut c, 20);
push_u16(&mut c, attr_count as u16);
push_u16(&mut c, 0);
push_u16(&mut c, 0);
push_u16(&mut c, 0);
for a in &attr_structs {
push_u32(&mut c, a.ns.unwrap_or(0xFFFF_FFFF));
push_u32(&mut c, a.name);
push_u32(&mut c, a.raw_value as u32);
push_u16(&mut c, 8);
c.push(0);
c.push(a.typ);
push_u32(&mut c, a.data);
}
self.body.extend_from_slice(&c);
}
pub fn close(&mut self, tag: u32) {
let mut c = Vec::new();
push_u16(&mut c, RES_XML_END_ELEMENT_TYPE);
push_u16(&mut c, 16);
push_u32(&mut c, 24);
push_u32(&mut c, self.line);
push_u32(&mut c, NO_COMMENT);
push_u32(&mut c, 0);
push_u32(&mut c, tag);
self.body.extend_from_slice(&c);
}
pub fn res_id_for(name: &str) -> Option<u32> {
ATTR_RES_IDS.iter().find(|(n, _)| *n == name).map(|(_, id)| *id)
}
pub fn finish(self, attr_name_to_resid: &[(u32, u32)]) -> Vec<u8> {
let pool_chunk = self.pool.encode();
let max_attr_idx = attr_name_to_resid
.iter()
.map(|&(str_idx, _)| str_idx as usize)
.max()
.unwrap_or(0);
let mut resmap = vec![0u32; max_attr_idx + 1];
for &(str_idx, resid) in attr_name_to_resid {
if (str_idx as usize) < resmap.len() {
resmap[str_idx as usize] = resid;
}
}
let mut resmap_chunk = Vec::new();
let resmap_size = 8 + resmap.len() as u32 * 4;
push_u16(&mut resmap_chunk, RES_XML_RESOURCE_MAP_TYPE);
push_u16(&mut resmap_chunk, 8);
push_u32(&mut resmap_chunk, resmap_size);
for r in &resmap {
push_u32(&mut resmap_chunk, *r);
}
let inner_len = pool_chunk.len() + resmap_chunk.len() + self.body.len();
let total = 8 + inner_len;
let mut out = Vec::new();
push_u16(&mut out, RES_XML_TYPE);
push_u16(&mut out, 8);
push_u32(&mut out, total as u32);
out.extend_from_slice(&pool_chunk);
out.extend_from_slice(&resmap_chunk);
out.extend_from_slice(&self.body);
out
}
}
pub mod val {
#[allow(unused_imports)]
pub use super::{TYPE_INT_BOOL, TYPE_INT_DEC, TYPE_STRING};
}
fn push_u16(v: &mut Vec<u8>, x: u16) {
v.extend_from_slice(&x.to_le_bytes());
}
fn push_u32(v: &mut Vec<u8>, x: u32) {
v.extend_from_slice(&x.to_le_bytes());
}
fn push_utf8_len(v: &mut Vec<u8>, len: usize) {
if len < 0x80 {
v.push(len as u8);
} else {
v.push(((len >> 8) & 0x7f) as u8 | 0x80);
v.push((len & 0xff) as u8);
}
}