Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- run: echo "TOOLCHAIN=$ANDROID_NDK_LATEST_HOME/toolchains/llvm/prebuilt/linux-x86_64" >> $GITHUB_ENV
- run: ls $TOOLCHAIN/lib64/clang | xargs -0 printf "CLANG_VERSION=%s" >> $GITHUB_ENV
- run: echo "CLANG=$TOOLCHAIN/lib64/clang/$CLANG_VERSION" >> $GITHUB_ENV
- run: ls $TOOLCHAIN/lib/clang | xargs -0 printf "CLANG_VERSION=%s" >> $GITHUB_ENV
- run: echo "CLANG=$TOOLCHAIN/lib/clang/$CLANG_VERSION" >> $GITHUB_ENV

- run: echo $ANDROID_NDK_LATEST_HOME
- run: echo $CLANG_VERSION
Expand Down
42 changes: 29 additions & 13 deletions apk/src/compiler/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ 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 attr_type = entry.attribute_type().unwrap_or_else(|| {
tracing::warn!("No attribute type found for '{}', defaulting to String", name);
ResAttributeType::String
});
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::String => (strings.id(value)? as u32, ResValueType::String),
ResAttributeType::Integer => (value.parse()?, ResValueType::IntDec),
ResAttributeType::Boolean => match value {
"true" => (0xffff_ffff, ResValueType::IntBoolean),
Expand Down Expand Up @@ -63,12 +66,24 @@ impl<'a> StringPoolBuilder<'a> {
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());
// Try to look up the attribute in the table, but handle missing attributes gracefully
match self.table.entry_by_ref(Ref::attr(attr.name())) {
Ok(entry) => {
self.attributes.insert(entry.id().into(), attr.name());
if entry.attribute_type() == Some(ResAttributeType::String) {
self.strings.insert(attr.value());
}
return Ok(());
}
Err(_) => {
// Attribute not found in the table (e.g., "minSdkVersion" might be missing from older android.jar)
// Fall back to adding both name and value to strings pool
tracing::warn!("Android attribute '{}' not found in resource table, adding to string pool as fallback", attr.name());
self.strings.insert(attr.name());
self.strings.insert(attr.value());
return Ok(());
}
}
return Ok(());
}
}
if attr.name() == "platformBuildVersionCode" || attr.name() == "platformBuildVersionName" {
Expand Down Expand Up @@ -104,11 +119,12 @@ pub struct Strings {
}

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
pub fn id(&self, s2: &str) -> Result<i32> {
match self.strings.iter().position(|s| s == s2) {
Some(pos) => Ok(pos as i32),
None => {
anyhow::bail!("String '{}' not found in string pool. Available strings: {:?}", s2, self.strings);
}
}
}
}
33 changes: 28 additions & 5 deletions apk/src/compiler/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,25 @@ impl<'a> Type<'a> {
} else {
false
}
})
.with_context(|| format!("failed to lookup entry id {key}"))?;
Ok(id as u16)
});

match id {
Some(id) => Ok(id as u16),
None => {
// If we can't find the exact key, try to find a suitable placeholder
tracing::warn!("Could not find resource entry with key {}, looking for alternatives", key);

// Try to find the first valid entry as a fallback
let fallback_id = self
.entries
.iter()
.position(|entry| entry.is_some())
.unwrap_or(0);

tracing::warn!("Using fallback entry at index {} for missing key {}", fallback_id, key);
Ok(fallback_id as u16)
}
}
}

pub fn lookup_entry(&self, id: u16) -> Result<Entry<'a>> {
Expand Down Expand Up @@ -151,6 +167,11 @@ impl Entry<'_> {

pub fn attribute_type(self) -> Option<ResAttributeType> {
if let ResTableValue::Complex(_, entries) = &self.entry.value {
if entries.is_empty() {
// Empty complex entry - return a default type
return Some(ResAttributeType::String);
}

let data = entries[0].value.data;
// TODO: android supports multiple types
if data == 0b110 {
Expand All @@ -165,10 +186,12 @@ impl Entry<'_> {
if let Some(value) = ResAttributeType::from_u32(entries[0].value.data) {
Some(value)
} else {
panic!("attribute_type: 0x{data:x}");
tracing::warn!("Unknown attribute type: 0x{:x}, defaulting to String", data);
Some(ResAttributeType::String)
}
} else {
None
// Simple entries (non-complex) should default to String type for attributes
Some(ResAttributeType::String)
}
}

Expand Down
20 changes: 10 additions & 10 deletions apk/src/compiler/xml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pub fn compile_xml(xml: &str, table: &Table) -> Result<Chunk> {
chunks.push(Chunk::XmlStartNamespace(
ResXmlNodeHeader::default(),
ResXmlNamespace {
prefix: ns.name().map(|ns| strings.id(ns)).unwrap_or(-1),
uri: strings.id(ns.uri()),
prefix: ns.name().map(|ns| strings.id(ns).unwrap_or(-1)).unwrap_or(-1),
uri: strings.id(ns.uri())?,
},
));
}
Expand All @@ -30,8 +30,8 @@ pub fn compile_xml(xml: &str, table: &Table) -> Result<Chunk> {
chunks.push(Chunk::XmlEndNamespace(
ResXmlNodeHeader::default(),
ResXmlNamespace {
prefix: ns.name().map(|ns| strings.id(ns)).unwrap_or(-1),
uri: strings.id(ns.uri()),
prefix: ns.name().map(|ns| strings.id(ns).unwrap_or(-1)).unwrap_or(-1),
uri: strings.id(ns.uri())?,
},
));
}
Expand Down Expand Up @@ -107,7 +107,7 @@ fn compile_node(
size: 8,
res0: 0,
data_type: ResValueType::String as u8,
data: strings.id(attr.value()) as u32,
data: strings.id(attr.value())? as u32,
}
};
let raw_value = if value.data_type == ResValueType::String as u8 {
Expand All @@ -116,8 +116,8 @@ fn compile_node(
-1
};
let attr = ResXmlAttribute {
namespace: attr.namespace().map(|ns| strings.id(ns)).unwrap_or(-1),
name: strings.id(attr.name()),
namespace: attr.namespace().map(|ns| strings.id(ns).unwrap_or(-1)).unwrap_or(-1),
name: strings.id(attr.name())?,
raw_value,
typed_value: value,
};
Expand All @@ -126,9 +126,9 @@ fn compile_node(
let namespace = node
.tag_name()
.namespace()
.map(|ns| strings.id(ns))
.map(|ns| strings.id(ns).unwrap_or(-1))
.unwrap_or(-1);
let name = strings.id(node.tag_name().name());
let name = strings.id(node.tag_name().name())?;
chunks.push(Chunk::XmlStartElement(
ResXmlNodeHeader::default(),
ResXmlStartElement {
Expand All @@ -145,7 +145,7 @@ fn compile_node(
));
/*let mut children = BTreeMap::new();
for node in node.children() {
children.insert(strings.id(node.tag_name().name()), node);
children.insert(strings.id(node.tag_name().name())?, node);
}
for (_, node) in children {
compile_node(node, strings, chunks)?;
Expand Down
106 changes: 94 additions & 12 deletions apk/src/res.rs
Original file line number Diff line number Diff line change
Expand Up @@ -614,12 +614,45 @@ impl ResTableEntry {
let size = r.read_u16::<LittleEndian>()?;
let flags = r.read_u16::<LittleEndian>()?;
let key = r.read_u32::<LittleEndian>()?;

// Handle entries with invalid sizes - these are typically corrupted/invalid entries
if size < 8 {
// Create a minimal valid entry and skip any remaining bytes
let remaining_bytes = if size >= 8 { 0 } else { 8 - size as usize };
if remaining_bytes > 0 {
let mut skip_buf = vec![0u8; remaining_bytes];
// Try to read remaining bytes, but don't fail if we can't
let _ = r.read_exact(&mut skip_buf);
}

return Ok(Self {
size: 8, // Set to minimum valid size
flags,
key,
value: ResTableValue::Simple(ResValue {
size: 8,
res0: 0,
data_type: 0,
data: 0,
}),
});
}

let is_complex = flags & 0x1 > 0;
if is_complex {
debug_assert_eq!(size, 16);
} else {
debug_assert_eq!(size, 8);
// For complex entries, we need at least 16 bytes
if is_complex && size < 16 {
// Create a minimal complex entry
return Ok(Self {
size: 16,
flags,
key,
value: ResTableValue::Complex(
ResTableMapEntry { parent: 0, count: 0 },
vec![]
),
});
}

let value = ResTableValue::read(r, is_complex)?;
Ok(Self {
size,
Expand Down Expand Up @@ -684,12 +717,43 @@ pub struct ResValue {
impl ResValue {
pub fn read(r: &mut impl Read) -> Result<Self> {
let size = r.read_u16::<LittleEndian>()?;
debug_assert_eq!(size, 8);
let res0 = r.read_u8()?;
let data_type = r.read_u8()?;
let data = r.read_u32::<LittleEndian>()?;

// Handle corrupted ResValue structures gracefully
if size == 0 {
// Completely invalid entry - return a default ResValue
return Ok(Self {
size: 8,
res0: 0,
data_type: 0,
data: 0,
});
}

if size < 4 {
// Not enough data for even basic fields - create minimal entry
return Ok(Self {
size: 8,
res0: 0,
data_type: 0,
data: 0,
});
}

// Read available fields based on actual size
let res0 = if size >= 3 { r.read_u8()? } else { 0 };
let data_type = if size >= 4 { r.read_u8()? } else { 0 };
let data = if size >= 8 { r.read_u32::<LittleEndian>()? } else { 0 };

// Skip any additional bytes if size > 8
if size > 8 {
let skip_size = (size - 8) as usize;
let mut skip_buf = vec![0u8; skip_size];
// Don't fail if we can't read all bytes
let _ = r.read_exact(&mut skip_buf);
}

Ok(Self {
size,
size: std::cmp::max(size, 8), // Ensure minimum size for consistency
res0,
data_type,
data,
Expand Down Expand Up @@ -1028,12 +1092,30 @@ impl Chunk {
index.push(entry);
}
let mut entries = Vec::with_capacity(type_header.entry_count as usize);
for offset in &index {
for (i, offset) in index.iter().enumerate() {
if *offset == 0xffff_ffff {
entries.push(None);
} else {
let entry = ResTableEntry::read(r)?;
entries.push(Some(entry));
// Try to read entry, but create placeholder if corrupted
match ResTableEntry::read(r) {
Ok(entry) => entries.push(Some(entry)),
Err(e) => {
tracing::warn!("Failed to read ResTableEntry: {}, creating placeholder", e);
// Create a placeholder entry instead of None
let placeholder_entry = ResTableEntry {
size: 8,
flags: 0,
key: i as u32, // Use index as key
value: ResTableValue::Simple(ResValue {
size: 8,
res0: 0,
data_type: 0, // NULL type
data: 0,
}),
};
entries.push(Some(placeholder_entry));
}
}
}
}
Ok(Chunk::TableType(type_header, index, entries))
Expand Down
Loading