279 lines
8.2 KiB
Rust
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);
|
|
}
|
|
}
|