mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
Add amlogic image extractor, sparse: add support for FILL chunk.
This commit is contained in:
@@ -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>`.
|
If an output folder is not provided, extracted files will be saved in folder `_<INPUT_TARGET>`.
|
||||||
|
|
||||||
# Supported formats
|
# 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
|
## Android OTA payload.bin
|
||||||
**Used in:** Android devices, smartphones, TVs
|
**Used in:** Android devices, smartphones, TVs
|
||||||
**Notes:** Some compression methods may not be supported.
|
**Notes:** Some compression methods may not be supported.
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub mod funai_upg;
|
|||||||
pub mod pana_dvd;
|
pub mod pana_dvd;
|
||||||
pub mod android_ota_payload;
|
pub mod android_ota_payload;
|
||||||
pub mod bdl;
|
pub mod bdl;
|
||||||
|
pub mod amlogic;
|
||||||
|
|
||||||
pub mod pup;
|
pub mod pup;
|
||||||
|
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
@@ -116,6 +116,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
println!("PFL UPG file detected!");
|
println!("PFL UPG file detected!");
|
||||||
formats::pfl_upg::extract_pfl_upg(&file, &output_path)?;
|
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) {
|
else if formats::pana_dvd::is_pana_dvd_file(&file) {
|
||||||
println!("PANA_DVD file detected!");
|
println!("PANA_DVD file detected!");
|
||||||
formats::pana_dvd::extract_pana_dvd(&file, &output_path)?;
|
formats::pana_dvd::extract_pana_dvd(&file, &output_path)?;
|
||||||
|
|||||||
@@ -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)
|
if chunk_header.chunk_type == 0xCAC1 { //"raw" type chunk (actual data)
|
||||||
out_file.write_all(&chunk_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)
|
} 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 skip_size = file_header.block_size as u64 * chunk_header.chunk_size as u64;
|
||||||
let current_pos = out_file.stream_position()?;
|
let current_pos = out_file.stream_position()?;
|
||||||
|
|||||||
Reference in New Issue
Block a user