From 7d26859c6f8710db242277f6c6ca40e64b1f8b18 Mon Sep 17 00:00:00 2001 From: theubusu <80545678+theubusu@users.noreply.github.com> Date: Fri, 12 Dec 2025 22:14:01 +0100 Subject: [PATCH] Add Android OTA payload extractor --- Cargo.lock | 145 ++++++- Cargo.toml | 6 +- formats.md | 26 ++ src/formats.rs | 1 + src/formats/android_ota_payload.rs | 89 +++++ src/main.rs | 6 +- src/utils.rs | 3 +- src/utils/android_ota_update_metadata.rs | 471 +++++++++++++++++++++++ src/utils/compression.rs | 16 + unixtract.md | 25 -- 10 files changed, 759 insertions(+), 29 deletions(-) create mode 100644 formats.md create mode 100644 src/formats/android_ota_payload.rs create mode 100644 src/utils/android_ota_update_metadata.rs delete mode 100644 unixtract.md diff --git a/Cargo.lock b/Cargo.lock index f75dd2b..66d867d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "array-init" version = "2.1.0" @@ -147,6 +153,21 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + [[package]] name = "cbc" version = "0.1.2" @@ -163,6 +184,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -373,6 +396,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "heck" version = "0.5.0" @@ -401,6 +436,25 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -410,12 +464,38 @@ dependencies = [ "spin", ] +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "liblzma" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73c36d08cad03a3fbe2c4e7bb3a9e84c57e4ee4135ed0b065cade3d98480c648" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "libm" version = "0.2.15" @@ -582,6 +662,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -600,6 +686,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.40" @@ -609,6 +727,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -635,7 +759,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", ] [[package]] @@ -787,15 +911,19 @@ version = "0.1.0" dependencies = [ "aes", "binrw", + "bzip2", "cbc", "clap", "ecb", "flate2", "hex", + "liblzma", "lz4", "lzma-rs", "md5", "minilzo-rs", + "prost", + "prost-types", "rsa", "sha1", "simd-adler32", @@ -820,6 +948,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "windows-link" version = "0.1.3" @@ -900,6 +1037,12 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "xattr" version = "1.6.1" diff --git a/Cargo.toml b/Cargo.toml index 82c907f..a707359 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,8 @@ ecb = "0.1.2" tar = "0.4.44" binrw = "0.15" simd-adler32 = "*" -minilzo-rs = "0.6.1" \ No newline at end of file +minilzo-rs = "0.6.1" +prost = "0.14.1" +prost-types = "0.14.1" +bzip2 = "0.6.1" +liblzma = "0.4.5" \ No newline at end of file diff --git a/formats.md b/formats.md new file mode 100644 index 0000000..225f646 --- /dev/null +++ b/formats.md @@ -0,0 +1,26 @@ +| Format name | Used by | Notes | Thanks to | +| ----------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | +| Android OTA payload.bin | Some Android MediaTek TVs | Some compression methods are not supported | https://android.googlesource.com/platform/system/update_engine/+/HEAD/update_metadata.proto | +| EPK v1 | LG TVs before ~2010 | All files should be supported. | https://github.com/openlgtv/epk2extract | +| EPK v2 | LG TVs since ~2010 | **Depends on keys** - see keys.rs | https://github.com/openlgtv/epk2extract | +| EPK v3 | LG webOS TVs | **Depends on keys** - see keys.rs | https://github.com/openlgtv/epk2extract | +| Funai UPG | Some Funai TVs | Only supports unencrypted/unobfuscated files | - | +| INVINCIBLE_IMAGE | LG Broadcom-based Blu-Ray players | Only version 3 is supported (2011+) | - | +| MSD v1.0 | Samsung TVs 2013-2015 | **Depends on keys** - see keys.rs | https://github.com/bugficks/msddecrypt | +| MSD v1.1 | Samsung TVs 2016+ | **Depends on keys** - see keys.rs | https://github.com/bugficks/msddecrypt | +| Mstar upgrade bin | Many MStar-based TVs (Hisense, Toshiba..) | Most files should be supported | - | +| Mediatek BDP | Many Mediatek-based Blu-Ray players (LG, Samsung, Philips, Panasonic...) | Some older files may not be supported | - | +| Mediatek PKG | Many Mediatek-based TVs (Hisense, Sony, Panasonic, Philips...) | Newer files with larger header are not supported. **Depends on keys** - see keys.rs | https://github.com/openlgtv/epk2extract | +| Mediatek upgrade_loader | Older Mediatek-based TVs | All files should be supported providing they are not encrypted | - | +| Novatek PKG (NFWB) | Some Novatek-based TVs (Philips, LG..) | All files should be supported | https://github.com/openlgtv/epk2extract | +| Novatek TIMG | Later Novatek Based TVs (Philips TitanOS/Hisense) | All files should be supported | - | +| Panasonic Blu-Ray (PANA_DVD) | Panasonic Blu-Ray Players/Recorders | **Depends on keys** - see keys.rs | - | +| Philips UPG | Philips pre-TPVision TVS 2008-2013 | **Depends on keys** - see keys.rs | https://github.com/frederic/pflupg-tool | +| PUP | Sony PlayStation 4/ PlayStation 5 | File has to be decrypted | https://github.com/Zer0xFF/ps4-pup-unpacker | +| Roku | Roku TVs/players | Most files should work, but encrypted images contained within will not be decrypted. | - | +| RUF | Samsung Broadcom-based Blu-Ray players | **Depends on keys** - see keys.rs | - | +| RVP/MVP | Sharp Blu-Ray players/recorders | Only supports older files (XOR encrypted) | - | +| Samsung | Old Samsung TV firmwares pre 2013 | **Depends on keys** - see keys.rs | https://github.com/george-hopkins/samygo-patcher | +| SDDL.SEC | Panasonic TVs 2011+ | All files 2011+ are supported | https://github.com/theubusu/sddl_dec | +| SLP | Samsung Tizen-based NX series cameras | All files should be supported | - | +| Sony BDP | Sony Blu-Ray players | Only platforms up to MSB18 are supported | http://malcolmstagg.com/bdp/s390-firmware.html | \ No newline at end of file diff --git a/src/formats.rs b/src/formats.rs index 0400b6e..4ad7201 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -12,6 +12,7 @@ pub mod sony_bdp; pub mod rvp; pub mod funai_upg; pub mod pana_dvd; +pub mod android_ota_payload; pub mod pup; diff --git a/src/formats/android_ota_payload.rs b/src/formats/android_ota_payload.rs new file mode 100644 index 0000000..567c610 --- /dev/null +++ b/src/formats/android_ota_payload.rs @@ -0,0 +1,89 @@ +use std::fs::{self, File, OpenOptions}; +use std::path::{Path}; +use std::io::{Write}; +use binrw::{BinRead, BinReaderExt}; +use prost::Message; + +use crate::utils::common; +use crate::utils::android_ota_update_metadata::{DeltaArchiveManifest, install_operation}; +use crate::utils::compression::{decompress_bzip, decompress_xz}; + +#[derive(BinRead)] +struct Header { + #[br(count = 4)] _magic_bytes: Vec, //CrAU + file_format_version: u64, + manifest_size: u64, + metadata_signature_size: u32, +} + +pub fn is_android_ota_payload_file(file: &File) -> bool { + let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); + if header == b"CrAU" { + true + } else { + false + } +} + +pub fn extract_android_ota_payload(mut file: &File, output_folder: &str) -> Result<(), Box> { + let header: Header = file.read_be()?; + println!("File info:\nFormat version: {}\nManifest size: {}", header.file_format_version, header.manifest_size); + + if header.file_format_version != 2 { + println!("\nSorry, this version of the file is not supported!"); + return Ok(()) + } + + let base_offset = 24 /* size of header */ + header.manifest_size + header.metadata_signature_size as u64; + + let read_manifest = common::read_exact(&mut file, header.manifest_size as usize)?; + let manifest = DeltaArchiveManifest::decode(&*read_manifest)?; + + for (i, partition) in manifest.partitions.into_iter().enumerate() { + let operation_count = partition.operations.len(); + println!("\n#{} - {}, Size: {}, Operations: {}", + i + 1, partition.partition_name, partition.new_partition_info.unwrap().size.unwrap(), operation_count); + + for (i, operation) in partition.operations.into_iter().enumerate() { + let operation_name_str = match install_operation::Type::try_from(operation.r#type) { + Ok(t) => t.as_str_name(), + Err(_) => "UNKNOWN", + }; + + let offset = base_offset + operation.data_offset.unwrap(); + let size = operation.data_length.unwrap(); + + //because the amount of operations can reach up to the thousands, i think its best to update the current line + //to not clog up the terminal and so you know what the program is actually doing + print!("\r- ({}/{}) - {}({}), Offset: {}, Size: {}", + i + 1, operation_count, operation_name_str, operation.r#type, offset, size); + std::io::stdout().flush()?; + + let data = common::read_file(&file, offset, size as usize)?; + + let out_data; + if operation.r#type == 0 { //REPLACE - just write the stored data + out_data = data; + } + else if operation.r#type == 1 { //REPLACE_BZ - decompress with bzip and write + out_data = decompress_bzip(&data)?; + } + else if operation.r#type == 8 { //REPLACE_XZ - decompress with xz and write + out_data = decompress_xz(&data)?; + } else { + println!("-- Unsupported operation!"); + break + } + + fs::create_dir_all(&output_folder)?; + let output_path = Path::new(&output_folder).join(format!("{}.bin", partition.partition_name)); + let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?; + out_file.write_all(&out_data)?; + } + println!("\n-- Saved!"); + } + + println!("\nExtraction finished!"); + + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5cfa10e..1728f74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,7 +69,11 @@ fn main() -> Result<(), Box> { else if formats::nvt_timg::is_nvt_timg_file(&file) { println!("Novatek TIMG file detected!"); formats::nvt_timg::extract_nvt_timg(&file, &output_path)?; - } + } + if formats::android_ota_payload::is_android_ota_payload_file(&file) { + println!("Android OTA payload file detected!"); + formats::android_ota_payload::extract_android_ota_payload(&file, &output_path)?; + } else if formats::novatek::is_novatek_file(&file) { println!("Novatek file detected!"); formats::novatek::extract_novatek(&file, &output_path)?; diff --git a/src/utils.rs b/src/utils.rs index 6e45565..af23844 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,4 +4,5 @@ pub mod pana_dvd_crypto; pub mod lzss; pub mod lzop; pub mod sparse; -pub mod compression; \ No newline at end of file +pub mod compression; +pub mod android_ota_update_metadata; \ No newline at end of file diff --git a/src/utils/android_ota_update_metadata.rs b/src/utils/android_ota_update_metadata.rs new file mode 100644 index 0000000..9326add --- /dev/null +++ b/src/utils/android_ota_update_metadata.rs @@ -0,0 +1,471 @@ +// This file is @generated by prost-build. +/// Data is packed into blocks on disk, always starting from the beginning +/// of the block. If a file's data is too large for one block, it overflows +/// into another block, which may or may not be the following block on the +/// physical partition. An ordered list of extents is another +/// representation of an ordered list of blocks. For example, a file stored +/// in blocks 9, 10, 11, 2, 18, 12 (in that order) would be stored in +/// extents { {9, 3}, {2, 1}, {18, 1}, {12, 1} } (in that order). +/// In general, files are stored sequentially on disk, so it's more efficient +/// to use extents to encode the block lists (this is effectively +/// run-length encoding). +/// A sentinel value (kuint64max) as the start block denotes a sparse-hole +/// in a file whose block-length is specified by num_blocks. +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct Extent { + #[prost(uint64, optional, tag = "1")] + pub start_block: ::core::option::Option, + #[prost(uint64, optional, tag = "2")] + pub num_blocks: ::core::option::Option, +} +/// Signatures: Updates may be signed by the OS vendor. The client verifies +/// an update's signature by hashing the entire download. The section of the +/// download that contains the signature is at the end of the file, so when +/// signing a file, only the part up to the signature part is signed. +/// Then, the client looks inside the download's Signatures message for a +/// Signature message that it knows how to handle. Generally, a client will +/// only know how to handle one type of signature, but an update may contain +/// many signatures to support many different types of client. Then client +/// selects a Signature message and uses that, along with a known public key, +/// to verify the download. The public key is expected to be part of the +/// client. +// -- Added by me , struct Signatures is unused -- +//#[derive(Clone, PartialEq, ::prost::Message)] +//pub struct Signatures { +// #[prost(message, repeated, tag = "1")] +// pub signatures: ::prost::alloc::vec::Vec, +//} +/// Nested message and enum types in `Signatures`. +pub mod signatures { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Signature { + #[deprecated] + #[prost(uint32, optional, tag = "1")] + pub version: ::core::option::Option, + #[prost(bytes = "vec", optional, tag = "2")] + pub data: ::core::option::Option<::prost::alloc::vec::Vec>, + /// The DER encoded signature size of EC keys is nondeterministic for + /// different input of sha256 hash. However, we need the size of the + /// serialized signatures protobuf string to be fixed before signing; + /// because this size is part of the content to be signed. Therefore, we + /// always pad the signature data to the maximum possible signature size of + /// a given key. And the payload verifier will truncate the signature to + /// its correct size based on the value of |unpadded_signature_size|. + #[prost(fixed32, optional, tag = "3")] + pub unpadded_signature_size: ::core::option::Option, + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PartitionInfo { + #[prost(uint64, optional, tag = "1")] + pub size: ::core::option::Option, + #[prost(bytes = "vec", optional, tag = "2")] + pub hash: ::core::option::Option<::prost::alloc::vec::Vec>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InstallOperation { + #[prost(enumeration = "install_operation::Type", required, tag = "1")] + pub r#type: i32, + /// Only minor version 6 or newer support 64 bits |data_offset| and + /// |data_length|, older client will read them as uint32. + /// The offset into the delta file (after the protobuf) + /// where the data (if any) is stored + #[prost(uint64, optional, tag = "2")] + pub data_offset: ::core::option::Option, + /// The length of the data in the delta file + #[prost(uint64, optional, tag = "3")] + pub data_length: ::core::option::Option, + /// Ordered list of extents that are read from (if any) and written to. + #[prost(message, repeated, tag = "4")] + pub src_extents: ::prost::alloc::vec::Vec, + /// Byte length of src, equal to the number of blocks in src_extents * + /// block_size. It is used for BSDIFF and SOURCE_BSDIFF, because we need to + /// pass that external program the number of bytes to read from the blocks we + /// pass it. This is not used in any other operation. + #[prost(uint64, optional, tag = "5")] + pub src_length: ::core::option::Option, + #[prost(message, repeated, tag = "6")] + pub dst_extents: ::prost::alloc::vec::Vec, + /// Byte length of dst, equal to the number of blocks in dst_extents * + /// block_size. Used for BSDIFF and SOURCE_BSDIFF, but not in any other + /// operation. + #[prost(uint64, optional, tag = "7")] + pub dst_length: ::core::option::Option, + /// Optional SHA 256 hash of the blob associated with this operation. + /// This is used as a primary validation for http-based downloads and + /// as a defense-in-depth validation for https-based downloads. If + /// the operation doesn't refer to any blob, this field will have + /// zero bytes. + #[prost(bytes = "vec", optional, tag = "8")] + pub data_sha256_hash: ::core::option::Option<::prost::alloc::vec::Vec>, + /// Indicates the SHA 256 hash of the source data referenced in src_extents at + /// the time of applying the operation. If present, the update_engine daemon + /// MUST read and verify the source data before applying the operation. + #[prost(bytes = "vec", optional, tag = "9")] + pub src_sha256_hash: ::core::option::Option<::prost::alloc::vec::Vec>, +} +/// Nested message and enum types in `InstallOperation`. +pub mod install_operation { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Type { + /// Replace destination extents w/ attached data. + Replace = 0, + /// Replace destination extents w/ attached bzipped data. + ReplaceBz = 1, + /// Move source extents to target extents. + Move = 2, + /// The data is a bsdiff binary diff. + Bsdiff = 3, + /// On minor version 2 or newer, these operations are supported: + /// + /// Copy from source to target partition + SourceCopy = 4, + /// Like BSDIFF, but read from source partition + SourceBsdiff = 5, + /// On minor version 3 or newer and on major version 2 or newer, these + /// operations are supported: + /// + /// Replace destination extents w/ attached xz data. + ReplaceXz = 8, + /// On minor version 4 or newer, these operations are supported: + /// + /// Write zeros in the destination. + Zero = 6, + /// Discard the destination blocks, reading as undefined. + Discard = 7, + /// Like SOURCE_BSDIFF, but compressed with brotli. + BrotliBsdiff = 10, + /// On minor version 5 or newer, these operations are supported: + /// + /// The data is in puffdiff format. + Puffdiff = 9, + /// On minor version 8 or newer, these operations are supported: + Zucchini = 11, + /// On minor version 9 or newer, these operations are supported: + Lz4diffBsdiff = 12, + Lz4diffPuffdiff = 13, + /// This is not part of the original spec, but is currently in use for some + /// types of payloads. + /// + /// Replace destination extents w/ attached zstd data. + ReplaceZstd = 14, + } + impl Type { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::Replace => "REPLACE", + Self::ReplaceBz => "REPLACE_BZ", + Self::Move => "MOVE", + Self::Bsdiff => "BSDIFF", + Self::SourceCopy => "SOURCE_COPY", + Self::SourceBsdiff => "SOURCE_BSDIFF", + Self::ReplaceXz => "REPLACE_XZ", + Self::Zero => "ZERO", + Self::Discard => "DISCARD", + Self::BrotliBsdiff => "BROTLI_BSDIFF", + Self::Puffdiff => "PUFFDIFF", + Self::Zucchini => "ZUCCHINI", + Self::Lz4diffBsdiff => "LZ4DIFF_BSDIFF", + Self::Lz4diffPuffdiff => "LZ4DIFF_PUFFDIFF", + Self::ReplaceZstd => "REPLACE_ZSTD", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "REPLACE" => Some(Self::Replace), + "REPLACE_BZ" => Some(Self::ReplaceBz), + "MOVE" => Some(Self::Move), + "BSDIFF" => Some(Self::Bsdiff), + "SOURCE_COPY" => Some(Self::SourceCopy), + "SOURCE_BSDIFF" => Some(Self::SourceBsdiff), + "REPLACE_XZ" => Some(Self::ReplaceXz), + "ZERO" => Some(Self::Zero), + "DISCARD" => Some(Self::Discard), + "BROTLI_BSDIFF" => Some(Self::BrotliBsdiff), + "PUFFDIFF" => Some(Self::Puffdiff), + "ZUCCHINI" => Some(Self::Zucchini), + "LZ4DIFF_BSDIFF" => Some(Self::Lz4diffBsdiff), + "LZ4DIFF_PUFFDIFF" => Some(Self::Lz4diffPuffdiff), + "REPLACE_ZSTD" => Some(Self::ReplaceZstd), + _ => None, + } + } + } +} +/// Hints to VAB snapshot to skip writing some blocks if these blocks are +/// identical to the ones on the source image. The src & dst extents for each +/// CowMergeOperation should be contiguous, and they're a subset of an OTA +/// InstallOperation. +/// During merge time, we need to follow the pre-computed sequence to avoid +/// read after write, similar to the inplace update schema. +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct CowMergeOperation { + #[prost(enumeration = "cow_merge_operation::Type", optional, tag = "1")] + pub r#type: ::core::option::Option, + #[prost(message, optional, tag = "2")] + pub src_extent: ::core::option::Option, + #[prost(message, optional, tag = "3")] + pub dst_extent: ::core::option::Option, + /// For COW_XOR, source location might be unaligned, so this field is in range + /// [0, block_size), representing how much should the src_extent shift toward + /// larger block number. If this field is non-zero, then src_extent will + /// include 1 extra block in the end, as the merge op actually references the + /// first |src_offset| bytes of that extra block. For example, if |dst_extent| + /// is \[10, 15\], |src_offset| is 500, then src_extent might look like \[25, 31\]. + /// Note that |src_extent| contains 1 extra block than the |dst_extent|. + #[prost(uint32, optional, tag = "4")] + pub src_offset: ::core::option::Option, +} +/// Nested message and enum types in `CowMergeOperation`. +pub mod cow_merge_operation { + #[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + ::prost::Enumeration + )] + #[repr(i32)] + pub enum Type { + /// identical blocks + CowCopy = 0, + /// used when src/dst blocks are highly similar + CowXor = 1, + /// Raw replace operation + CowReplace = 2, + } + impl Type { + /// String value of the enum field names used in the ProtoBuf definition. + /// + /// The values are not transformed in any way and thus are considered stable + /// (if the ProtoBuf definition does not change) and safe for programmatic use. + pub fn as_str_name(&self) -> &'static str { + match self { + Self::CowCopy => "COW_COPY", + Self::CowXor => "COW_XOR", + Self::CowReplace => "COW_REPLACE", + } + } + /// Creates an enum from field names used in the ProtoBuf definition. + pub fn from_str_name(value: &str) -> ::core::option::Option { + match value { + "COW_COPY" => Some(Self::CowCopy), + "COW_XOR" => Some(Self::CowXor), + "COW_REPLACE" => Some(Self::CowReplace), + _ => None, + } + } + } +} +/// Describes the update to apply to a single partition. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PartitionUpdate { + /// A platform-specific name to identify the partition set being updated. For + /// example, in Chrome OS this could be "ROOT" or "KERNEL". + #[prost(string, required, tag = "1")] + pub partition_name: ::prost::alloc::string::String, + /// Whether this partition carries a filesystem with post-install program that + /// must be run to finalize the update process. See also |postinstall_path| and + /// |filesystem_type|. + #[prost(bool, optional, tag = "2")] + pub run_postinstall: ::core::option::Option, + /// The path of the executable program to run during the post-install step, + /// relative to the root of this filesystem. If not set, the default "postinst" + /// will be used. This setting is only used when |run_postinstall| is set and + /// true. + #[prost(string, optional, tag = "3")] + pub postinstall_path: ::core::option::Option<::prost::alloc::string::String>, + /// The filesystem type as passed to the mount(2) syscall when mounting the new + /// filesystem to run the post-install program. If not set, a fixed list of + /// filesystems will be attempted. This setting is only used if + /// |run_postinstall| is set and true. + #[prost(string, optional, tag = "4")] + pub filesystem_type: ::core::option::Option<::prost::alloc::string::String>, + /// If present, a list of signatures of the new_partition_info.hash signed with + /// different keys. If the update_engine daemon requires vendor-signed images + /// and has its public key installed, one of the signatures should be valid + /// for /postinstall to run. + #[prost(message, repeated, tag = "5")] + pub new_partition_signature: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "6")] + pub old_partition_info: ::core::option::Option, + #[prost(message, optional, tag = "7")] + pub new_partition_info: ::core::option::Option, + /// The list of operations to be performed to apply this PartitionUpdate. The + /// associated operation blobs (in operations\[i\].data_offset, data_length) + /// should be stored contiguously and in the same order. + #[prost(message, repeated, tag = "8")] + pub operations: ::prost::alloc::vec::Vec, + /// Whether a failure in the postinstall step for this partition should be + /// ignored. + #[prost(bool, optional, tag = "9")] + pub postinstall_optional: ::core::option::Option, + /// On minor version 6 or newer, these fields are supported: + /// The extent for data covered by verity hash tree. + #[prost(message, optional, tag = "10")] + pub hash_tree_data_extent: ::core::option::Option, + /// The extent to store verity hash tree. + #[prost(message, optional, tag = "11")] + pub hash_tree_extent: ::core::option::Option, + /// The hash algorithm used in verity hash tree. + #[prost(string, optional, tag = "12")] + pub hash_tree_algorithm: ::core::option::Option<::prost::alloc::string::String>, + /// The salt used for verity hash tree. + #[prost(bytes = "vec", optional, tag = "13")] + pub hash_tree_salt: ::core::option::Option<::prost::alloc::vec::Vec>, + /// The extent for data covered by FEC. + #[prost(message, optional, tag = "14")] + pub fec_data_extent: ::core::option::Option, + /// The extent to store FEC. + #[prost(message, optional, tag = "15")] + pub fec_extent: ::core::option::Option, + /// The number of FEC roots. + #[prost(uint32, optional, tag = "16", default = "2")] + pub fec_roots: ::core::option::Option, + /// Per-partition version used for downgrade detection, added + /// as an effort to support partial updates. For most partitions, + /// this is the build timestamp. + #[prost(string, optional, tag = "17")] + pub version: ::core::option::Option<::prost::alloc::string::String>, + /// A sorted list of CowMergeOperation. When writing cow, we can choose to + /// skip writing the raw bytes for these extents. During snapshot merge, the + /// bytes will read from the source partitions instead. + #[prost(message, repeated, tag = "18")] + pub merge_operations: ::prost::alloc::vec::Vec, + /// Estimated size for COW image. This is used by libsnapshot + /// as a hint. If set to 0, libsnapshot should use alternative + /// methods for estimating size. + #[prost(uint64, optional, tag = "19")] + pub estimate_cow_size: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DynamicPartitionGroup { + /// Name of the group. + #[prost(string, required, tag = "1")] + pub name: ::prost::alloc::string::String, + /// Maximum size of the group. The sum of sizes of all partitions in the group + /// must not exceed the maximum size of the group. + #[prost(uint64, optional, tag = "2")] + pub size: ::core::option::Option, + /// A list of partitions that belong to the group. + #[prost(string, repeated, tag = "3")] + pub partition_names: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, Copy, PartialEq, ::prost::Message)] +pub struct VabcFeatureSet { + #[prost(bool, optional, tag = "1")] + pub threaded: ::core::option::Option, + #[prost(bool, optional, tag = "2")] + pub batch_writes: ::core::option::Option, +} +/// Metadata related to all dynamic partitions. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DynamicPartitionMetadata { + /// All updatable groups present in |partitions| of this DeltaArchiveManifest. + /// - If an updatable group is on the device but not in the manifest, it is + /// not updated. Hence, the group will not be resized, and partitions cannot + /// be added to or removed from the group. + /// - If an updatable group is in the manifest but not on the device, the group + /// is added to the device. + #[prost(message, repeated, tag = "1")] + pub groups: ::prost::alloc::vec::Vec, + /// Whether dynamic partitions have snapshots during the update. If this is + /// set to true, the update_engine daemon creates snapshots for all dynamic + /// partitions if possible. If this is unset, the update_engine daemon MUST + /// NOT create snapshots for dynamic partitions. + #[prost(bool, optional, tag = "2")] + pub snapshot_enabled: ::core::option::Option, + /// If this is set to false, update_engine should not use VABC regardless. If + /// this is set to true, update_engine may choose to use VABC if device + /// supports it, but not guaranteed. + /// VABC stands for Virtual AB Compression + #[prost(bool, optional, tag = "3")] + pub vabc_enabled: ::core::option::Option, + /// The compression algorithm used by VABC. Available ones are "gz", "brotli". + /// See system/core/fs_mgr/libsnapshot/cow_writer.cpp for available options, + /// as this parameter is ultimated forwarded to libsnapshot's CowWriter + #[prost(string, optional, tag = "4")] + pub vabc_compression_param: ::core::option::Option<::prost::alloc::string::String>, + /// COW version used by VABC. The represents the major version in the COW + /// header + #[prost(uint32, optional, tag = "5")] + pub cow_version: ::core::option::Option, + /// A collection of knobs to tune Virtual AB Compression + #[prost(message, optional, tag = "6")] + pub vabc_feature_set: ::core::option::Option, +} +/// Definition has been duplicated from +/// $ANDROID_BUILD_TOP/build/tools/releasetools/ota_metadata.proto. Keep in sync. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ApexInfo { + #[prost(string, optional, tag = "1")] + pub package_name: ::core::option::Option<::prost::alloc::string::String>, + #[prost(int64, optional, tag = "2")] + pub version: ::core::option::Option, + #[prost(bool, optional, tag = "3")] + pub is_compressed: ::core::option::Option, + #[prost(int64, optional, tag = "4")] + pub decompressed_size: ::core::option::Option, +} +/// Definition has been duplicated from +/// $ANDROID_BUILD_TOP/build/tools/releasetools/ota_metadata.proto. Keep in sync. +// -- Added by me , struct ApexMetadata is unused -- +//#[derive(Clone, PartialEq, ::prost::Message)] +//pub struct ApexMetadata { +// #[prost(message, repeated, tag = "1")] +// pub apex_info: ::prost::alloc::vec::Vec, +//} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DeltaArchiveManifest { + /// (At time of writing) usually 4096 + #[prost(uint32, optional, tag = "3", default = "4096")] + pub block_size: ::core::option::Option, + /// If signatures are present, the offset into the blobs, generally + /// tacked onto the end of the file, and the length. We use an offset + /// rather than a bool to allow for more flexibility in future file formats. + /// If either is absent, it means signatures aren't supported in this + /// file. + #[prost(uint64, optional, tag = "4")] + pub signatures_offset: ::core::option::Option, + #[prost(uint64, optional, tag = "5")] + pub signatures_size: ::core::option::Option, + /// The minor version, also referred as "delta version", of the payload. + /// Minor version 0 is full payload, everything else is delta payload. + #[prost(uint32, optional, tag = "12", default = "0")] + pub minor_version: ::core::option::Option, + /// Only present in major version >= 2. List of partitions that will be + /// updated, in the order they will be updated. This field replaces the + /// |install_operations|, |kernel_install_operations| and the + /// |{old,new}_{kernel,rootfs}_info| fields used in major version = 1. This + /// array can have more than two partitions if needed, and they are identified + /// by the partition name. + #[prost(message, repeated, tag = "13")] + pub partitions: ::prost::alloc::vec::Vec, + /// The maximum timestamp of the OS allowed to apply this payload. + /// Can be used to prevent downgrading the OS. + #[prost(int64, optional, tag = "14")] + pub max_timestamp: ::core::option::Option, + /// Metadata related to all dynamic partitions. + #[prost(message, optional, tag = "15")] + pub dynamic_partition_metadata: ::core::option::Option, +} diff --git a/src/utils/compression.rs b/src/utils/compression.rs index 16c2014..f63df17 100644 --- a/src/utils/compression.rs +++ b/src/utils/compression.rs @@ -4,6 +4,8 @@ use flate2::read::ZlibDecoder; use flate2::read::GzDecoder; use lzma_rs::lzma_decompress; use lz4::block::decompress; +use bzip2::read::BzDecoder; +use liblzma::read::XzDecoder; pub fn decompress_zlib(data: &[u8]) -> io::Result> { let mut decoder = ZlibDecoder::new(data); @@ -33,4 +35,18 @@ pub fn decompress_lz4(compressed_data: &[u8], original_size: i32) -> Result Ok(decompressed), Err(e) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)), } +} + +pub fn decompress_bzip(compressed: &[u8]) -> std::io::Result> { + let mut decoder = BzDecoder::new(compressed); + let mut decompressed = Vec::new(); + decoder.read_to_end(&mut decompressed)?; + Ok(decompressed) +} + +pub fn decompress_xz(compressed: &[u8]) -> std::io::Result> { + let mut decoder = XzDecoder::new(compressed); + let mut decompressed = Vec::new(); + decoder.read_to_end(&mut decompressed)?; + Ok(decompressed) } \ No newline at end of file diff --git a/unixtract.md b/unixtract.md deleted file mode 100644 index 3c48ce7..0000000 --- a/unixtract.md +++ /dev/null @@ -1,25 +0,0 @@ -| Format name | Used by | Notes | Thanks to | -| ----------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------- | -| EPK v1 | LG TVs before ~2010 | All files should be supported (expect epkv1(new)) | https://github.com/openlgtv/epk2extract | -| EPK v2 | LG TVs since ~2010 | Most files are supported (**depends on keys**) | https://github.com/openlgtv/epk2extract | -| EPK v3 | LG webOS TVs | Only some files are supported (**depends on keys**) | https://github.com/openlgtv/epk2extract | -| Funai UPG | Some Funai TVs | Only supports unencrypted/unobfuscated files | - | -| INVINCIBLE_IMAGE | LG Broadcom-based Blu-Ray players | Only version 3 is supported (2011+) | - | -| MSD v1.0 | Samsung TVs 2013-2015 | Only some files are supported (**depends on keys**) | https://github.com/bugficks/msddecrypt | -| MSD v1.1 | Samsung TVs 2016+ | Only some files are supported (**depends on keys**) | https://github.com/bugficks/msddecrypt | -| Mstar upgrade bin | Many MStar-based TVs (Hisense, Toshiba..) | Most files should be supported | - | -| Mediatek BDP | Many Mediatek-based Blu-Ray players (LG, Samsung, Philips, Panasonic...) | Some older files may not be supported | - | -| Mediatek PKG | Many Mediatek-based TVs (Hisense, Sony, Panasonic, Philips...) | Only files with 144 byte header and using 4xVendor magic as enc key are supported | https://github.com/openlgtv/epk2extract | -| Mediatek upgrade_loader | Older Mediatek-based TVs | All files should be supported providing they are not encrypted | - | -| Novatek pkg | Some Novatek-based TVs (Philips, LG..) | All files should be supported | https://github.com/openlgtv/epk2extract | -| Novatek TIMG | Later Novatek Based TVs (Philips TitanOS/Hisense) | All files should be supported | - | -| Panasonic Blu-Ray (PANA_DVD) | Panasonic Blu-Ray Players/Recorders | Only some files are supported (**depends on keys**) | - | -| Philips UPG | Philips pre-TPVision TVS 2008-2013 | Only some files are supported (**depends on keys**) | https://github.com/frederic/pflupg-tool | -| PUP | Sony PlayStation 4/ PlayStation 5 | File has to be decrypted | https://github.com/Zer0xFF/ps4-pup-unpacker | -| Roku | Roku TVs/players | Most files should work, but encrypted images contained within will not be decrypted. | - | -| RUF | Samsung Broadcom-based Blu-Ray players | Only BD-P1600 is supported (**depends on keys**) | - | -| RVP/MVP | Sharp Blu-Ray players/recorders | Only supports older files (XOR encrypted) | - | -| Samsung | Old Samsung TV firmwares pre 2013 | Only some files are supported (**depends on keys**) | https://github.com/george-hopkins/samygo-patcher | -| SDDL.SEC | Panasonic TVs 2011+ | All files 2011+ are supported | https://github.com/theubusu/sddl_dec | -| SLP | Samsung Tizen-based NX series cameras | All files should be supported | - | -| Sony BDP | Sony Blu-Ray players | Only platforms up to MSB18 are supported | http://malcolmstagg.com/bdp/s390-firmware.html | \ No newline at end of file