Add Android OTA payload extractor

This commit is contained in:
theubusu
2025-12-12 22:14:01 +01:00
parent f993a3df70
commit 7d26859c6f
10 changed files with 759 additions and 29 deletions
Generated
+144 -1
View File
@@ -69,6 +69,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "array-init" name = "array-init"
version = "2.1.0" version = "2.1.0"
@@ -147,6 +153,21 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 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]] [[package]]
name = "cbc" name = "cbc"
version = "0.1.2" version = "0.1.2"
@@ -163,6 +184,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver",
"libc",
"shlex", "shlex",
] ]
@@ -373,6 +396,18 @@ dependencies = [
"wasi", "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]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@@ -401,6 +436,25 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 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]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@@ -410,12 +464,38 @@ dependencies = [
"spin", "spin",
] ]
[[package]]
name = "libbz2-rs-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.175" version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 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]] [[package]]
name = "libm" name = "libm"
version = "0.2.15" version = "0.2.15"
@@ -582,6 +662,12 @@ dependencies = [
"spki", "spki",
] ]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.21" version = "0.2.21"
@@ -600,6 +686,38 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "quote" name = "quote"
version = "1.0.40" version = "1.0.40"
@@ -609,6 +727,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@@ -635,7 +759,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.16",
] ]
[[package]] [[package]]
@@ -787,15 +911,19 @@ version = "0.1.0"
dependencies = [ dependencies = [
"aes", "aes",
"binrw", "binrw",
"bzip2",
"cbc", "cbc",
"clap", "clap",
"ecb", "ecb",
"flate2", "flate2",
"hex", "hex",
"liblzma",
"lz4", "lz4",
"lzma-rs", "lzma-rs",
"md5", "md5",
"minilzo-rs", "minilzo-rs",
"prost",
"prost-types",
"rsa", "rsa",
"sha1", "sha1",
"simd-adler32", "simd-adler32",
@@ -820,6 +948,15 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 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]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.1.3" version = "0.1.3"
@@ -900,6 +1037,12 @@ version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]] [[package]]
name = "xattr" name = "xattr"
version = "1.6.1" version = "1.6.1"
+4
View File
@@ -19,3 +19,7 @@ tar = "0.4.44"
binrw = "0.15" binrw = "0.15"
simd-adler32 = "*" simd-adler32 = "*"
minilzo-rs = "0.6.1" minilzo-rs = "0.6.1"
prost = "0.14.1"
prost-types = "0.14.1"
bzip2 = "0.6.1"
liblzma = "0.4.5"
+26
View File
@@ -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 |
+1
View File
@@ -12,6 +12,7 @@ pub mod sony_bdp;
pub mod rvp; pub mod rvp;
pub mod funai_upg; pub mod funai_upg;
pub mod pana_dvd; pub mod pana_dvd;
pub mod android_ota_payload;
pub mod pup; pub mod pup;
+89
View File
@@ -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<u8>, //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<dyn std::error::Error>> {
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(())
}
+4
View File
@@ -70,6 +70,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Novatek TIMG file detected!"); println!("Novatek TIMG file detected!");
formats::nvt_timg::extract_nvt_timg(&file, &output_path)?; 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) { else if formats::novatek::is_novatek_file(&file) {
println!("Novatek file detected!"); println!("Novatek file detected!");
formats::novatek::extract_novatek(&file, &output_path)?; formats::novatek::extract_novatek(&file, &output_path)?;
+1
View File
@@ -5,3 +5,4 @@ pub mod lzss;
pub mod lzop; pub mod lzop;
pub mod sparse; pub mod sparse;
pub mod compression; pub mod compression;
pub mod android_ota_update_metadata;
+471
View File
@@ -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<u64>,
#[prost(uint64, optional, tag = "2")]
pub num_blocks: ::core::option::Option<u64>,
}
/// 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<signatures::Signature>,
//}
/// 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<u32>,
#[prost(bytes = "vec", optional, tag = "2")]
pub data: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
/// 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<u32>,
}
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PartitionInfo {
#[prost(uint64, optional, tag = "1")]
pub size: ::core::option::Option<u64>,
#[prost(bytes = "vec", optional, tag = "2")]
pub hash: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
}
#[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<u64>,
/// The length of the data in the delta file
#[prost(uint64, optional, tag = "3")]
pub data_length: ::core::option::Option<u64>,
/// 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<Extent>,
/// 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<u64>,
#[prost(message, repeated, tag = "6")]
pub dst_extents: ::prost::alloc::vec::Vec<Extent>,
/// 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<u64>,
/// 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<u8>>,
/// 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<u8>>,
}
/// 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<Self> {
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<i32>,
#[prost(message, optional, tag = "2")]
pub src_extent: ::core::option::Option<Extent>,
#[prost(message, optional, tag = "3")]
pub dst_extent: ::core::option::Option<Extent>,
/// 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<u32>,
}
/// 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<Self> {
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<bool>,
/// 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<signatures::Signature>,
#[prost(message, optional, tag = "6")]
pub old_partition_info: ::core::option::Option<PartitionInfo>,
#[prost(message, optional, tag = "7")]
pub new_partition_info: ::core::option::Option<PartitionInfo>,
/// 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<InstallOperation>,
/// Whether a failure in the postinstall step for this partition should be
/// ignored.
#[prost(bool, optional, tag = "9")]
pub postinstall_optional: ::core::option::Option<bool>,
/// 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<Extent>,
/// The extent to store verity hash tree.
#[prost(message, optional, tag = "11")]
pub hash_tree_extent: ::core::option::Option<Extent>,
/// 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<u8>>,
/// The extent for data covered by FEC.
#[prost(message, optional, tag = "14")]
pub fec_data_extent: ::core::option::Option<Extent>,
/// The extent to store FEC.
#[prost(message, optional, tag = "15")]
pub fec_extent: ::core::option::Option<Extent>,
/// The number of FEC roots.
#[prost(uint32, optional, tag = "16", default = "2")]
pub fec_roots: ::core::option::Option<u32>,
/// 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<CowMergeOperation>,
/// 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<u64>,
}
#[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<u64>,
/// 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<bool>,
#[prost(bool, optional, tag = "2")]
pub batch_writes: ::core::option::Option<bool>,
}
/// 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<DynamicPartitionGroup>,
/// 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<bool>,
/// 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<bool>,
/// 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<u32>,
/// A collection of knobs to tune Virtual AB Compression
#[prost(message, optional, tag = "6")]
pub vabc_feature_set: ::core::option::Option<VabcFeatureSet>,
}
/// 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<i64>,
#[prost(bool, optional, tag = "3")]
pub is_compressed: ::core::option::Option<bool>,
#[prost(int64, optional, tag = "4")]
pub decompressed_size: ::core::option::Option<i64>,
}
/// 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<ApexInfo>,
//}
#[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<u32>,
/// 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<u64>,
#[prost(uint64, optional, tag = "5")]
pub signatures_size: ::core::option::Option<u64>,
/// 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<u32>,
/// 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<PartitionUpdate>,
/// 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<i64>,
/// Metadata related to all dynamic partitions.
#[prost(message, optional, tag = "15")]
pub dynamic_partition_metadata: ::core::option::Option<DynamicPartitionMetadata>,
}
+16
View File
@@ -4,6 +4,8 @@ use flate2::read::ZlibDecoder;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use lzma_rs::lzma_decompress; use lzma_rs::lzma_decompress;
use lz4::block::decompress; use lz4::block::decompress;
use bzip2::read::BzDecoder;
use liblzma::read::XzDecoder;
pub fn decompress_zlib(data: &[u8]) -> io::Result<Vec<u8>> { pub fn decompress_zlib(data: &[u8]) -> io::Result<Vec<u8>> {
let mut decoder = ZlibDecoder::new(data); let mut decoder = ZlibDecoder::new(data);
@@ -34,3 +36,17 @@ pub fn decompress_lz4(compressed_data: &[u8], original_size: i32) -> Result<Vec<
Err(e) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)), Err(e) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)),
} }
} }
pub fn decompress_bzip(compressed: &[u8]) -> std::io::Result<Vec<u8>> {
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<Vec<u8>> {
let mut decoder = XzDecoder::new(compressed);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed)?;
Ok(decompressed)
}
-25
View File
@@ -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 |