apk 0.4.0

Library for creating and signing apks.
Documentation
use crate::compiler::table::{Ref, Table};
use crate::res::{ResAttributeType, ResValue, ResValueType};
use anyhow::{Context, Result};
use roxmltree::Attribute;
use std::collections::{BTreeMap, BTreeSet};

pub fn compile_attr(table: &Table, name: &str, value: &str, strings: &Strings) -> Result<ResValue> {
    let entry = table.entry_by_ref(Ref::attr(name))?;
    let attr_type = entry.attribute_type().unwrap();
    let (data, data_type) = match attr_type {
        ResAttributeType::Reference => {
            let id = table.entry_by_ref(Ref::parse(value)?)?.id();
            (u32::from(id), ResValueType::Reference)
        }
        ResAttributeType::String => (strings.id(value) as u32, ResValueType::String),
        ResAttributeType::Integer => (value.parse()?, ResValueType::IntDec),
        ResAttributeType::Boolean => match value {
            "true" => (0xffff_ffff, ResValueType::IntBoolean),
            "false" => (0x0000_0000, ResValueType::IntBoolean),
            _ => anyhow::bail!("expected boolean"),
        },
        ResAttributeType::Enum => {
            let id = table.entry_by_ref(Ref::id(value))?.id();
            let value = entry.lookup_value(id).unwrap();
            (value.data, ResValueType::from_u8(value.data_type).unwrap())
        }
        ResAttributeType::Flags => {
            let mut data = 0;
            let mut data_type = ResValueType::Null;
            for flag in value.split('|') {
                let id = table.entry_by_ref(Ref::id(flag))?.id();
                let value = entry.lookup_value(id).unwrap();
                data |= value.data;
                data_type = ResValueType::from_u8(value.data_type).unwrap();
            }
            (data, data_type)
        }
        _ => anyhow::bail!("unsupported attribute type"),
    };
    Ok(ResValue {
        size: 8,
        res0: 0,
        data_type: data_type as u8,
        data,
    })
}

pub struct StringPoolBuilder<'a> {
    table: &'a Table,
    attributes: BTreeMap<u32, &'a str>,
    strings: BTreeSet<&'a str>,
}

impl<'a> StringPoolBuilder<'a> {
    pub fn new(table: &'a Table) -> Self {
        Self {
            table,
            attributes: Default::default(),
            strings: Default::default(),
        }
    }

    pub fn add_attribute(&mut self, attr: Attribute<'a, 'a>) -> Result<()> {
        if let Some(ns) = attr.namespace() {
            if ns == "http://schemas.android.com/apk/res/android" {
                let entry = self.table.entry_by_ref(Ref::attr(attr.name()))?;
                self.attributes.insert(entry.id().into(), attr.name());
                if entry.attribute_type() == Some(ResAttributeType::String) {
                    self.strings.insert(attr.value());
                }
                return Ok(());
            }
        }
        if attr.name() == "platformBuildVersionCode" || attr.name() == "platformBuildVersionName" {
            self.strings.insert(attr.name());
        } else {
            self.strings.insert(attr.name());
            self.strings.insert(attr.value());
        }
        Ok(())
    }

    pub fn add_string(&mut self, s: &'a str) {
        self.strings.insert(s);
    }

    pub fn build(self) -> Strings {
        let mut strings = Vec::with_capacity(self.attributes.len() + self.strings.len());
        let mut map = Vec::with_capacity(self.attributes.len());
        for (id, name) in self.attributes {
            strings.push(name.to_string());
            map.push(id);
        }
        for string in self.strings {
            strings.push(string.to_string());
        }
        Strings { strings, map }
    }
}

pub struct Strings {
    pub strings: Vec<String>,
    pub map: Vec<u32>,
}

impl Strings {
    pub fn id(&self, s2: &str) -> i32 {
        self.strings
            .iter()
            .position(|s| s == s2)
            .with_context(|| format!("all strings added to the string pool: {}", s2))
            .unwrap() as i32
    }
}