Add amlogic image extractor, sparse: add support for FILL chunk.

This commit is contained in:
theubusu
2026-02-01 20:11:49 +01:00
parent 48f8868d71
commit 13482802ba
5 changed files with 129 additions and 1 deletions
+5
View File
@@ -14,6 +14,11 @@ Build from source, by downloading the code or cloning the respository and runnin
If an output folder is not provided, extracted files will be saved in folder `_<INPUT_TARGET>`.
# Supported formats
## Amlogic burning image
**Used in:** Android TVs and Boxes
**Notes:** V1 format is not supported because of the lack of sample file.
**Thanks to:** https://github.com/7Ji/ampack
## Android OTA payload.bin
**Used in:** Android devices, smartphones, TVs
**Notes:** Some compression methods may not be supported.
+1
View File
@@ -14,6 +14,7 @@ pub mod funai_upg;
pub mod pana_dvd;
pub mod android_ota_payload;
pub mod bdl;
pub mod amlogic;
pub mod pup;
+108
View File
@@ -0,0 +1,108 @@
use std::fs::File;
use std::path::{Path};
use std::fs::{self, OpenOptions};
use std::io::{Write, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
use crate::utils::common;
use crate::utils::sparse::{unsparse_to_file};
#[derive(BinRead)]
struct ImageHeader {
_crc32: u32,
version: u32,
#[br(count = 4)] _magic_bytes: Vec<u8>, //56 19 B5 27
image_size: u64,
item_align_size: u32,
item_count: u32,
#[br(count = 36)] _reserved: Vec<u8>,
}
#[derive(BinRead)]
struct ItemEntry {
_item_id: u32,
file_type: u32,
_current_offset_in_item: u64,
offset_in_image: u64,
item_size: u64,
#[br(count = 256)] item_type_bytes: Vec<u8>,
#[br(count = 256)] name_bytes: Vec<u8>,
_verify: u32,
_is_backup_item: u16,
_backup_item_id: u16,
#[br(count = 24)] _reserved: Vec<u8>,
}
impl ItemEntry {
fn item_type(&self) -> String {
common::string_from_bytes(&self.item_type_bytes)
}
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
fn is_sparse(&self) -> bool {
self.file_type == 254
}
}
pub fn is_amlogic_file(file: &File) -> bool {
let header = common::read_file(&file, 8, 4).expect("Failed to read from file.");
if header == b"\x56\x19\xB5\x27" {
true
} else {
false
}
}
pub fn extract_amlogic(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
file.seek(SeekFrom::Start(0))?;
let header: ImageHeader = file.read_le()?;
println!("File info -\nImage size: {}\nItem align size: {}\nItem count: {}\nFormat version: {}",
header.image_size, header.item_align_size, header.item_count, header.version);
if header.version != 2 {
println!("\nSorry, this format version is not yet supported!");
return Ok(());
}
let mut items: Vec<ItemEntry> = Vec::new();
for _i in 0..header.item_count {
let item: ItemEntry = file.read_le()?;
items.push(item);
}
for (i, item) in items.iter().enumerate() {
println!("\n({}/{}) - {}, Type: {}, Offset: {}, Size: {} {}",
i+1, header.item_count, item.name(), item.item_type(), item.offset_in_image, item.item_size, if item.is_sparse() {"[SPARSE]"} else {""});
if item.item_type() == "VERIFY" { //verify item is SHA1 of partition item
let sum_bytes = common::read_file(&file, item.offset_in_image, item.item_size as usize)?;
let sum = common::string_from_bytes(&sum_bytes);
println!("- Checksum for {}: {}", item.name(), sum);
} else {
let data = common::read_file(&file, item.offset_in_image, item.item_size as usize)?;
let extension = if item.item_type() == "PARTITION" {"img"} else {&item.item_type()};
let output_path = Path::new(&output_folder).join(format!("{}.{}", item.name(), extension));
fs::create_dir_all(&output_folder)?;
if item.is_sparse() {
println!("- Unsparsing...");
unsparse_to_file(&data, output_path)?;
println!("-- Saved file!");
continue
} else {
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("- Saved file!");
}
}
}
println!("\nExtraction finished!");
Ok(())
}
+5 -1
View File
@@ -115,7 +115,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
else if formats::pfl_upg::is_pfl_upg_file(&file) {
println!("PFL UPG file detected!");
formats::pfl_upg::extract_pfl_upg(&file, &output_path)?;
}
}
else if formats::amlogic::is_amlogic_file(&file) {
println!("Amlogic image file detected!");
formats::amlogic::extract_amlogic(&file, &output_path)?;
}
else if formats::pana_dvd::is_pana_dvd_file(&file) {
println!("PANA_DVD file detected!");
formats::pana_dvd::extract_pana_dvd(&file, &output_path)?;
+10
View File
@@ -41,6 +41,16 @@ pub fn unsparse_to_file(data: &[u8], file_path: PathBuf) -> Result<(), Box<dyn s
if chunk_header.chunk_type == 0xCAC1 { //"raw" type chunk (actual data)
out_file.write_all(&chunk_data)?;
} else if chunk_header.chunk_type == 0xCAC2 { // "fill" type chunk (fill size with a value)
if chunk_data.len() != 4 {
return Err("Inavlid lenght of FILL chunk!".into());
}
let fill_size = (chunk_header.chunk_size * file_header.block_size) / 4;
let fill_data = chunk_data.repeat(fill_size as usize);
out_file.write_all(&fill_data)?;
} else if chunk_header.chunk_type == 0xCAC3 { // "dont care" type chunk (skip over)
let skip_size = file_header.block_size as u64 * chunk_header.chunk_size as u64;
let current_pos = out_file.stream_position()?;