RESTRUCTURING 1

- change formats to have their own folders with mod + include files + anything they want
- change format list to be defined in formats.rs instead of in each format
- move utils to their respective formats
- some minor code changes in some formats
This commit is contained in:
theubusu
2026-02-17 17:28:59 +01:00
parent 9fe39280cd
commit aa2249f024
69 changed files with 1678 additions and 1629 deletions
+39
View File
@@ -0,0 +1,39 @@
use crate::utils::common;
use binrw::{BinRead};
#[derive(BinRead)]
pub struct ImageHeader {
_crc32: u32,
pub version: u32,
_magic_bytes: [u8; 4], //56 19 B5 27
pub image_size: u64,
pub item_align_size: u32,
pub item_count: u32,
_reserved: [u8; 36],
}
#[derive(BinRead)]
pub struct ItemEntry {
_item_id: u32,
pub file_type: u32,
_current_offset_in_item: u64,
pub offset_in_image: u64,
pub item_size: u64,
item_type_bytes: [u8; 256],
name_bytes: [u8; 256],
_verify: u32,
_is_backup_item: u16,
_backup_item_id: u16,
_reserved: [u8; 24],
}
impl ItemEntry {
pub fn item_type(&self) -> String {
common::string_from_bytes(&self.item_type_bytes)
}
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
pub fn is_sparse(&self) -> bool {
self.file_type == 254
}
}
@@ -1,53 +1,15 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "amlogic", detector_func: is_amlogic_file, extractor_func: extract_amlogic }
}
use crate::AppContext;
use std::path::{Path};
use std::path::Path;
use std::fs::{self, OpenOptions};
use std::io::{Write, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
use binrw::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
}
}
use crate::utils::sparse::unsparse_to_file;
use include::*;
pub fn is_amlogic_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -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>,
}
@@ -0,0 +1,9 @@
use binrw::{BinRead};
#[derive(BinRead)]
pub struct Header {
_magic_bytes: [u8; 4], //CrAU
pub file_format_version: u64,
pub manifest_size: u64,
pub metadata_signature_size: u32,
}
@@ -1,26 +1,18 @@
mod include;
mod android_ota_update_metadata;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "android_ota_payload", detector_func: is_android_ota_payload_file, extractor_func: extract_android_ota_payload }
}
use crate::AppContext;
use std::fs::{self, OpenOptions};
use std::path::{Path};
use std::io::{Write};
use binrw::{BinRead, BinReaderExt};
use std::path::Path;
use std::io::Write;
use binrw::BinReaderExt;
use prost::Message;
use crate::utils::common;
use crate::utils::android_ota_update_metadata::{DeltaArchiveManifest, install_operation};
use 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,
}
use include::*;
pub fn is_android_ota_payload_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+76
View File
@@ -0,0 +1,76 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(BinRead)]
pub struct BdlHeader {
_magic_bytes: [u8; 4], //ibdl
_file_version: [u8; 8],
_unk1: u32,
pub pkg_count: u32,
_unk2: [u8; 12],
date_bytes: [u8; 256],
manufacturer_bytes: [u8; 256],
model_bytes: [u8; 256],
_unk3: [u8; 9],
version_bytes: [u8; 256],
info_bytes: [u8; 1280],
}
impl BdlHeader {
pub fn date(&self) -> String {
common::string_from_bytes(&self.date_bytes)
}
pub fn manufacturer(&self) -> String {
common::string_from_bytes(&self.manufacturer_bytes)
}
pub fn model(&self) -> String {
common::string_from_bytes(&self.model_bytes)
}
pub fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
pub fn info(&self) -> String {
common::string_from_bytes(&self.info_bytes)
}
}
#[derive(BinRead)]
pub struct PkgListEntry {
pub offset: u64,
pub size: u64,
}
#[derive(BinRead)]
pub struct PkgHeader {
_magic_bytes: [u8; 4], //ipkg
_unk1: [u8; 12],
pub entry_count: u32,
_unk2: [u8; 12],
version_bytes: [u8; 256],
manufacturer_bytes: [u8; 256],
name_bytes: [u8; 256],
_unk3: [u8; 285],
}
impl PkgHeader {
pub fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
pub fn manufacturer(&self) -> String {
common::string_from_bytes(&self.manufacturer_bytes)
}
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
pub struct PkgEntry {
name_bytes: [u8; 256],
pub offset: u64,
pub size: u64,
_crc32: u32,
}
impl PkgEntry {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
+5 -80
View File
@@ -1,89 +1,14 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "bdl", detector_func: is_bdl_file, extractor_func: extract_bdl }
}
use crate::AppContext;
use std::path::{Path};
use std::path::Path;
use std::fs::{self, OpenOptions};
use std::io::{Write, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
#[derive(BinRead)]
struct BdlHeader {
#[br(count = 4)] _magic_bytes: Vec<u8>, //ibdl
#[br(count = 8)] _file_version: Vec<u8>,
_unk1: u32,
pkg_count: u32,
#[br(count = 12)] _unk2: Vec<u8>,
#[br(count = 256)] date_bytes: Vec<u8>,
#[br(count = 256)] manufacturer_bytes: Vec<u8>,
#[br(count = 256)] model_bytes: Vec<u8>,
#[br(count = 9)] _unk3: Vec<u8>,
#[br(count = 256)] version_bytes: Vec<u8>,
#[br(count = 1280)] info_bytes: Vec<u8>,
}
impl BdlHeader {
fn date(&self) -> String {
common::string_from_bytes(&self.date_bytes)
}
fn manufacturer(&self) -> String {
common::string_from_bytes(&self.manufacturer_bytes)
}
fn model(&self) -> String {
common::string_from_bytes(&self.model_bytes)
}
fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
fn info(&self) -> String {
common::string_from_bytes(&self.info_bytes)
}
}
#[derive(BinRead)]
struct PkgListEntry {
offset: u64,
size: u64,
}
#[derive(BinRead)]
struct PkgHeader {
#[br(count = 4)] _magic_bytes: Vec<u8>, //ipkg
#[br(count = 12)] _unk1: Vec<u8>,
entry_count: u32,
#[br(count = 12)] _unk2: Vec<u8>,
#[br(count = 256)] version_bytes: Vec<u8>,
#[br(count = 256)] manufacturer_bytes: Vec<u8>,
#[br(count = 256)] name_bytes: Vec<u8>,
#[br(count = 285)] _unk3: Vec<u8>,
}
impl PkgHeader {
fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
fn manufacturer(&self) -> String {
common::string_from_bytes(&self.manufacturer_bytes)
}
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
struct PkgEntry {
#[br(count = 256)] name_bytes: Vec<u8>,
offset: u64,
size: u64,
_crc32: u32,
}
impl PkgEntry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
use include::*;
pub fn is_bdl_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -1,8 +1,5 @@
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "epk", detector_func: is_epk_file, extractor_func: extract_epk }
}
use crate::AppContext;
use crate::utils::common;
use crate::formats;
+31
View File
@@ -0,0 +1,31 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(BinRead)]
pub struct CommonHeader {
_magic_bytes: [u8; 4],
pub file_size: u32,
pub pak_count: u32,
}
#[derive(BinRead)]
pub struct PakHeader {
pak_name_bytes: [u8; 4],
pub image_size: u32,
platform_id_bytes: [u8; 64],
_reserved: [u8; 56],
}
impl PakHeader {
pub fn pak_name(&self) -> String {
common::string_from_bytes(&self.pak_name_bytes)
}
pub fn platform_id(&self) -> String {
common::string_from_bytes(&self.platform_id_bytes)
}
}
#[derive(BinRead)]
pub struct Pak {
pub offset : u32,
pub size : u32,
}
@@ -1,44 +1,14 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "epk1", detector_func: is_epk1_file, extractor_func: extract_epk1 }
}
use crate::AppContext;
use std::path::{Path};
use std::path::Path;
use std::fs::{self, OpenOptions};
use std::io::{Write, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
#[derive(BinRead)]
struct CommonHeader {
#[br(count = 4)] _magic_bytes: Vec<u8>,
file_size: u32,
pak_count: u32,
}
#[derive(BinRead)]
struct PakHeader {
#[br(count = 4)] pak_name_bytes: Vec<u8>,
image_size: u32,
#[br(count = 64)] platform_id_bytes: Vec<u8>,
#[br(count = 56)] _reserved: Vec<u8>,
}
impl PakHeader {
fn pak_name(&self) -> String {
common::string_from_bytes(&self.pak_name_bytes)
}
fn platform_id(&self) -> String {
common::string_from_bytes(&self.platform_id_bytes)
}
}
#[derive(BinRead)]
struct Pak {
offset : u32,
size : u32,
}
use include::*;
pub fn is_epk1_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+59
View File
@@ -0,0 +1,59 @@
use crate::utils::common;
use binrw::BinRead;
pub static SIGNATURE_SIZE: u32 = 128;
#[derive(BinRead)]
pub struct Header {
_magic_bytes: [u8; 4], //epak
pub file_size: u32,
pub pak_count: u32,
_epk2_magic: [u8; 4], //EPK2
pub version: [u8; 4],
ota_id_bytes: [u8; 32],
}
impl Header {
pub fn ota_id(&self) -> String {
common::string_from_bytes(&self.ota_id_bytes)
}
}
#[derive(BinRead)]
pub struct PakEntry {
pub offset: u32,
pub size: u32,
name_bytes: [u8; 4],
_version: [u8; 4],
pub segment_size: u32,
}
impl PakEntry {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
pub struct PakHeader {
_pak_name_bytes: [u8; 4],
pub image_size: u32,
platform_id_bytes: [u8; 64],
_sw_version: u32,
_sw_date: u32,
_build_type: u32,
pub segment_count: u32,
pub segment_size: u32,
pub segment_index: u32,
_pak_magic_bytes: [u8; 4], //MPAK
_reserved: [u8; 24],
_segment_crc32: u32,
}
impl PakHeader {
pub fn platform_id(&self) -> String {
common::string_from_bytes(&self.platform_id_bytes)
}
}
pub struct Pak {
pub offset: u32,
pub _size: u32,
pub name: String,
}
@@ -1,75 +1,16 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "epk2", detector_func: is_epk2_file, extractor_func: extract_epk2 }
}
use crate::AppContext;
use std::fs::{self, OpenOptions};
use std::path::{Path};
use std::path::Path;
use std::io::{Write, Seek, SeekFrom, Cursor};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::keys;
use crate::formats::epk::{decrypt_aes_ecb_auto, find_key};
static SIGNATURE_SIZE: u32 = 128;
#[derive(BinRead)]
struct Header {
#[br(count = 4)] _magic_bytes: Vec<u8>, //epak
file_size: u32,
pak_count: u32,
#[br(count = 4)] _epk2_magic: Vec<u8>, //EPK2
#[br(count = 4)] version: Vec<u8>,
#[br(count = 32)] ota_id_bytes: Vec<u8>,
}
impl Header {
fn ota_id(&self) -> String {
common::string_from_bytes(&self.ota_id_bytes)
}
}
#[derive(BinRead)]
struct PakEntry {
offset: u32,
size: u32,
#[br(count = 4)] name_bytes: Vec<u8>,
#[br(count = 4)] _version: Vec<u8>,
segment_size: u32,
}
impl PakEntry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
struct PakHeader {
#[br(count = 4)] _pak_name_bytes: Vec<u8>,
image_size: u32,
#[br(count = 64)] platform_id_bytes: Vec<u8>,
_sw_version: u32,
_sw_date: u32,
_build_type: u32,
segment_count: u32,
segment_size: u32,
segment_index: u32,
#[br(count = 4)] _pak_magic_bytes: Vec<u8>, //MPAK
#[br(count = 24)] _reserved: Vec<u8>,
_segment_crc32: u32,
}
impl PakHeader {
fn platform_id(&self) -> String {
common::string_from_bytes(&self.platform_id_bytes)
}
}
struct Pak {
offset: u32,
_size: u32,
name: String,
}
use include::*;
pub fn is_epk2_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -86,8 +27,6 @@ pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
file.seek(SeekFrom::Start(0))?;
let _header_signature = common::read_exact(&mut file, SIGNATURE_SIZE as usize)?;
let _header_signature = common::read_exact(&mut file, SIGNATURE_SIZE as usize)?;
let stored_header = common::read_exact(&mut file, 1584)?; //max header size
let header;
+47
View File
@@ -0,0 +1,47 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(BinRead)]
pub struct EpkHeader {
_epk_magic: [u8; 4], //epak
pub file_size: u32,
pub pak_count: u32,
_epk2_magic: [u8; 4], //EPK2
pub version: [u8; 4],
ota_id_bytes: [u8; 32],
}
impl EpkHeader {
pub fn ota_id(&self) -> String {
common::string_from_bytes(&self.ota_id_bytes)
}
}
#[derive(BinRead)]
pub struct PakHeader {
pak_name_bytes: [u8; 4],
pub image_size: u32,
platform_id_bytes: [u8; 64],
_sw_version: u32,
_sw_date: u32,
_build_type: u32,
pub segment_count: u32,
pub segment_size: u32,
pub segment_index: u32,
_pak_magic_bytes: [u8; 4], //MPAK
_reserved: [u8; 24],
_segment_crc32: u32,
}
impl PakHeader {
pub fn pak_name(&self) -> String {
common::string_from_bytes(&self.pak_name_bytes)
}
pub fn platform_id(&self) -> String {
common::string_from_bytes(&self.platform_id_bytes)
}
}
#[derive(BinRead)]
pub struct Pak {
pub offset : u32,
pub size : u32,
}
@@ -1,60 +1,14 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "epk2b", detector_func: is_epk2b_file, extractor_func: extract_epk2b }
}
use crate::AppContext;
use std::path::{Path};
use std::fs::{self, OpenOptions};
use std::io::{Write, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
#[derive(BinRead)]
struct EpkHeader {
#[br(count = 4)] _epk_magic: Vec<u8>, //epak
file_size: u32,
pak_count: u32,
#[br(count = 4)] _epk2_magic: Vec<u8>, //EPK2
#[br(count = 4)] version: Vec<u8>,
#[br(count = 32)] ota_id_bytes: Vec<u8>,
}
impl EpkHeader {
fn ota_id(&self) -> String {
common::string_from_bytes(&self.ota_id_bytes)
}
}
#[derive(BinRead)]
struct PakHeader {
#[br(count = 4)] pak_name_bytes: Vec<u8>,
image_size: u32,
#[br(count = 64)] platform_id_bytes: Vec<u8>,
_sw_version: u32,
_sw_date: u32,
_build_type: u32,
segment_count: u32,
segment_size: u32,
segment_index: u32,
#[br(count = 4)] _pak_magic_bytes: Vec<u8>, //MPAK
#[br(count = 24)] _reserved: Vec<u8>,
_segment_crc32: u32,
}
impl PakHeader {
fn pak_name(&self) -> String {
common::string_from_bytes(&self.pak_name_bytes)
}
fn platform_id(&self) -> String {
common::string_from_bytes(&self.platform_id_bytes)
}
}
#[derive(BinRead)]
struct Pak {
offset : u32,
size : u32,
}
use include::*;
pub fn is_epk2b_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+64
View File
@@ -0,0 +1,64 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(BinRead)]
pub struct Header {
_magic_bytes: [u8; 4], //EPK3
pub version: [u8; 4],
ota_id_bytes: [u8; 32],
pub package_info_size: u32,
_bchunked: u32,
}
impl Header {
pub fn ota_id(&self) -> String {
common::string_from_bytes(&self.ota_id_bytes)
}
}
#[derive(BinRead)]
pub struct HeaderNewEx {
_pak_info_magic: [u8; 4],
encrypt_type_bytes: [u8; 6],
update_type_bytes: [u8; 6],
pub update_platform_version: f32,
pub compatible_minimum_version: f32,
pub need_to_check_compatible_version: i32,
}
impl HeaderNewEx {
pub fn encrypt_type(&self) -> String {
common::string_from_bytes(&self.encrypt_type_bytes)
}
pub fn update_type(&self) -> String {
common::string_from_bytes(&self.update_type_bytes)
}
}
#[derive(BinRead)]
pub struct PkgInfoHeader {
pub package_info_list_size: u32,
pub package_info_count: u32,
}
#[derive(BinRead)]
pub struct PkgInfoEntry {
_package_type: u32,
_package_info_size: u32,
package_name_bytes: [u8; 128],
_package_version_bytes: [u8; 96],
_package_architecture_bytes: [u8; 32],
_checksum: [u8; 32],
pub package_size: u32,
_dipk: u32,
//segment info
_is_segmented: u32,
pub segment_index: u32,
pub segment_count: u32,
pub segment_size: u32,
//
_unk: u32,
}
impl PkgInfoEntry {
pub fn package_name(&self) -> String {
common::string_from_bytes(&self.package_name_bytes)
}
}
@@ -1,80 +1,16 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "epk3", detector_func: is_epk3_file, extractor_func: extract_epk3 }
}
use crate::AppContext;
use std::fs::{self, OpenOptions};
use std::path::{Path};
use std::path::Path;
use std::io::{Write, Seek, SeekFrom, Cursor};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::keys;
use crate::formats::epk::{decrypt_aes_ecb_auto, find_key};
#[derive(BinRead)]
struct Header {
#[br(count = 4)] _magic_bytes: Vec<u8>, //EPK3
#[br(count = 4)] version: Vec<u8>,
#[br(count = 32)] ota_id_bytes: Vec<u8>,
package_info_size: u32,
_bchunked: u32,
}
impl Header {
fn ota_id(&self) -> String {
common::string_from_bytes(&self.ota_id_bytes)
}
}
#[derive(BinRead)]
struct HeaderNewEx {
#[br(count = 4)] _pak_info_magic: Vec<u8>,
#[br(count = 6)] encrypt_type_bytes: Vec<u8>,
#[br(count = 6)] update_type_bytes: Vec<u8>,
update_platform_version: f32,
compatible_minimum_version: f32,
need_to_check_compatible_version: i32,
}
impl HeaderNewEx {
fn encrypt_type(&self) -> String {
common::string_from_bytes(&self.encrypt_type_bytes)
}
fn update_type(&self) -> String {
common::string_from_bytes(&self.update_type_bytes)
}
}
#[derive(BinRead)]
struct PkgInfoHeader {
package_info_list_size: u32,
package_info_count: u32,
}
#[derive(BinRead)]
struct PkgInfoEntry {
_package_type: u32,
_package_info_size: u32,
#[br(count = 128)] package_name_bytes: Vec<u8>,
#[br(count = 96)] _package_version_bytes: Vec<u8>,
#[br(count = 32)] _package_architecture_bytes: Vec<u8>,
#[br(count = 32)] _checksum: Vec<u8>,
package_size: u32,
_dipk: u32,
//segment info
_is_segmented: u32,
segment_index: u32,
segment_count: u32,
segment_size: u32,
//
_unk: u32,
}
impl PkgInfoEntry {
fn package_name(&self) -> String {
common::string_from_bytes(&self.package_name_bytes)
}
}
use include::*;
pub fn is_epk3_file(_app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
Ok(None)
+15
View File
@@ -0,0 +1,15 @@
use binrw::BinRead;
#[derive(BinRead)]
pub struct Header {
_magic_bytes: [u8; 6],
pub entry_count: u16,
pub file_size: u32,
}
#[derive(BinRead)]
pub struct Entry {
pub entry_type: u16,
pub entry_size: u32,
_unk: u16,
}
@@ -1,29 +1,14 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "funai_upg", detector_func: is_funai_upg_file, extractor_func: extract_funai_upg }
}
use crate::AppContext;
use std::path::Path;
use std::fs::{self, OpenOptions};
use std::io::{Write};
use binrw::{BinRead, BinReaderExt};
use std::io::Write;
use binrw::BinReaderExt;
use crate::utils::common;
#[derive(BinRead)]
struct Header {
#[br(count = 6)] _magic_bytes: Vec<u8>,
entry_count: u16,
file_size: u32,
}
#[derive(BinRead)]
struct Entry {
entry_type: u16,
entry_size: u32,
_unk: u16,
}
use include::*;
pub fn is_funai_upg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+56
View File
@@ -0,0 +1,56 @@
use crate::utils::common;
use binrw::BinRead;
//v3 key + iv
pub static V3_KEY: [u8; 16] = [0x32, 0xe5, 0x26, 0x1e, 0x22, 0x67, 0x5e, 0x93, 0x20, 0xcf, 0x35, 0x91, 0x7c, 0x63, 0x7a, 0x36];
pub static V3_IV: [u8; 16] = [0xe3, 0x9f, 0x36, 0x39, 0x56, 0x9a, 0x6b, 0x8d, 0x3f, 0x2e, 0xc9, 0x44, 0xd9, 0xbc, 0xec, 0x43];
#[derive(BinRead)]
pub struct Header {
_magic_bytes: [u8; 16],
pub file_version: [u8; 4],
_unk1: u32,
ver1_bytes: [u8; 16],
ver2_bytes: [u8; 16],
_unk2: u16,
_type: u8,
pub keep_size: u32,
_unk3: u8,
pub data_start_offset: u32,
pub data_size: u32,
_data_size_2: u32,
pub skip_size: u32,
_unk4: u16,
_encryption_method: u8, // 0x01 - AES128, 0x02 - AES256
_hash_type: u8, // 0x01 - MD5, 0x02 - SHA1
ver3_bytes: [u8; 16],
ver4_bytes: [u8; 16],
_unk6: [u8; 11],
pub payload_count: u8,
}
impl Header {
pub fn ver1(&self) -> String {
common::string_from_bytes(&self.ver1_bytes).replace('\n', "")
}
pub fn ver2(&self) -> String {
common::string_from_bytes(&self.ver2_bytes).replace('\n', "")
}
pub fn ver3(&self) -> String {
common::string_from_bytes(&self.ver3_bytes).replace('\n', "")
}
pub fn ver4(&self) -> String {
common::string_from_bytes(&self.ver4_bytes).replace('\n', "")
}
}
#[derive(BinRead)]
pub struct Entry {
name_bytes: [u8; 16],
pub start_offset: u32,
pub size: u32,
}
impl Entry {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
@@ -1,66 +1,15 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "invincible_image", detector_func: is_invincible_image_file, extractor_func: extract_invincible_image }
}
use crate::AppContext;
use std::path::{Path};
use std::fs::{self, OpenOptions};
use std::io::{Write, Read, Seek, SeekFrom, Cursor};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
use crate::utils::common;
#[derive(BinRead)]
struct Header {
#[br(count = 16)] _magic_bytes: Vec<u8>,
#[br(count = 4)] file_version: Vec<u8>,
_unk1: u32,
#[br(count = 16)] ver1_bytes: Vec<u8>,
#[br(count = 16)] ver2_bytes: Vec<u8>,
_unk2: u16,
_type: u8,
keep_size: u32,
_unk3: u8,
data_start_offset: u32,
data_size: u32,
_data_size_2: u32,
skip_size: u32,
_unk4: u16,
_encryption_method: u8, // 0x01 - AES128, 0x02 - AES256
_hash_type: u8, // 0x01 - MD5, 0x02 - SHA1
#[br(count = 16)] ver3_bytes: Vec<u8>,
#[br(count = 16)] ver4_bytes: Vec<u8>,
#[br(count = 11)] _unk6: Vec<u8>,
payload_count: u8,
}
impl Header {
fn ver1(&self) -> String {
common::string_from_bytes(&self.ver1_bytes).replace('\n', "")
}
fn ver2(&self) -> String {
common::string_from_bytes(&self.ver2_bytes).replace('\n', "")
}
fn ver3(&self) -> String {
common::string_from_bytes(&self.ver3_bytes).replace('\n', "")
}
fn ver4(&self) -> String {
common::string_from_bytes(&self.ver4_bytes).replace('\n', "")
}
}
#[derive(BinRead)]
struct Entry {
#[br(count = 16)] name_bytes: Vec<u8>,
start_offset: u32,
size: u32,
}
impl Entry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
use include::*;
pub fn is_invincible_image_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -108,12 +57,8 @@ pub fn extract_invincible_image(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Res
file.seek(SeekFrom::Current(header.skip_size.into()))?;
}
//v3 key + iv
let key: [u8; 16] = [0x32, 0xe5, 0x26, 0x1e, 0x22, 0x67, 0x5e, 0x93, 0x20, 0xcf, 0x35, 0x91, 0x7c, 0x63, 0x7a, 0x36];
let iv: [u8; 16] = [0xe3, 0x9f, 0x36, 0x39, 0x56, 0x9a, 0x6b, 0x8d, 0x3f, 0x2e, 0xc9, 0x44, 0xd9, 0xbc, 0xec, 0x43];
println!("\nDecrypting data...");
let decrypted_data = decrypt_aes128_cbc_nopad(&encrypted_data, &key, &iv)?;
let decrypted_data = decrypt_aes128_cbc_nopad(&encrypted_data, &V3_KEY, &V3_IV)?;
let mut data_reader = Cursor::new(decrypted_data);
@@ -1,3 +1,8 @@
//MSD OUITH parsers
pub mod msd_ouith_parser_old;
pub mod msd_ouith_parser_tizen_1_8;
pub mod msd_ouith_parser_tizen_1_9;
// COMMON MSD FUNCTIONS
use aes::Aes128;
use cbc::{Decryptor, cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}};
+269
View File
@@ -0,0 +1,269 @@
//MAIN CODE: https://github.com/theubusu/msd_OUITH_parser
use std::io::{Seek, SeekFrom, Cursor};
use binrw::{BinRead, BinReaderExt};
use crate::utils::common;
// whether to print the tree
static CONFIG_PRINT_TREE: bool = false;
#[derive(BinRead)]
struct ChunkHeader {
size: u32,
value: u32,
}
#[derive(BinRead)]
struct DescriptorHeader {
tag: u16,
size: u32,
}
#[derive(BinRead)]
//OUSWFileVersionDesc, OUPartitionVersionDesc, OUCMACDataDesc
struct CommonDestinationInfo {
name_len: u8,
#[br(count = name_len)] name_bytes: Vec<u8>,
version: u16,
}
impl CommonDestinationInfo {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
struct OUAESEncryptionDesc {
mode: u8,
key_size: u32,
salt_size: u32,
}
#[derive(BinRead)]
struct OURSAValidationDesc {
mode: u8,
_unknown: u32,
signature_size: u32,
}
#[derive(BinRead)]
struct OUSecureHashValidationDesc {
mode: u8,
hash_size: u16,
#[br(count = hash_size)] hash: Vec<u8>,
}
#[derive(BinRead)]
struct OUGroupDesc {
group_id: u32,
field_2: u8,
field_3: u8,
}
#[derive(BinRead)]
pub struct OUSWImageVersionExDesc {
pub name_len: u8,
#[br(count = name_len)] pub name_bytes: Vec<u8>,
pub major_ver: u16,
pub minor_ver: u16,
pub date_year: u8,
pub date_month: u8,
pub date_day: u8,
}
impl OUSWImageVersionExDesc {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
//this is a struct that will communicate all values important when parsing the MSD file.
pub struct MSDItem {
pub item_id: u32,
pub item_type: u16, //File, Partition, CMACData
pub all_size: u32, //same size as in msd header
pub name: String,
pub heading_size: u32, //has the signature and salt
pub data_size: u32,
pub aes_encryption: bool,
//pub crc32_hash: Option<u32>,
//pub secure_hash: Option<Vec<u8>>,
}
pub fn parse_ouith_blob(blob: &[u8]) -> Result<(Vec<MSDItem>, Option<OUSWImageVersionExDesc>), Box<dyn std::error::Error>> {
let mut reader = Cursor::new(blob);
let mut items: Vec<MSDItem> = Vec::new();
let mut info: Option<OUSWImageVersionExDesc> = None;
let _signature = common::read_exact(&mut reader, 128)?; //signature included at the beginning of blob in MSD file
let mut chunk_n = 0;
while reader.stream_position()? < blob.len() as u64 {
chunk_n += 1;
let chunk: ChunkHeader = reader.read_be()?;
if CONFIG_PRINT_TREE { println!("\nChunk {} - Size: {}, Value: {}", chunk_n, chunk.size, chunk.value); };
let chunk_end = reader.stream_position()? + chunk.size as u64;
//parse top level descriptor. it can only be ID 1(OUUpgradeItemDesc) or 2(OUGroupDesc)
let top_descriptor: DescriptorHeader = reader.read_be()?;
if top_descriptor.tag == 0x01 {
if CONFIG_PRINT_TREE { println!("OUUpgradeItemDesc(0x01) - Size: {}", top_descriptor.size); };
let item_id: u32 = reader.read_be()?;
if CONFIG_PRINT_TREE { println!(" Item ID: {}", item_id); };
//REQUIRED are items in order: OUDestinationDesc(0x03), OUDataProcessingDesc(0x07), OUGroupInfoDesc(0x13). OPTIONAL items: OUDependenciesDesc(0x04), OUDataPostProcessingDesc(0x08)
//In MSD files, no others seem to be used than the required ones. We will ignore all data after required descriptors.
let destination_descriptor: DescriptorHeader = reader.read_be()?;
if destination_descriptor.tag != 0x03 {return Err(format!("Unexpected descriptor type in OUUpgradeItemDesc, Expected: 0x03, Got: 0x{:02x}!", destination_descriptor.tag).into())}
if CONFIG_PRINT_TREE { println!(" OUDestinationDesc(0x03) - Size: {}", destination_descriptor.size); };
let out_size: u32 = reader.read_be()?;
if CONFIG_PRINT_TREE { println!(" Out data size: {}", out_size); };
//OUDestinationDesc needs one of OUSWFileVersionDesc(0x0B), OUPartitionVersionDesc(0x0A), OUCMACDataDesc(0x11). Their structure is the same. so we can store the type
let type_descriptor: DescriptorHeader = reader.read_be()?;
if ![0x0B, 0xA, 0x11].contains(&type_descriptor.tag) {return Err(format!("Unexpected descriptor type in OUDestinationDesc, Expected: one of 0x0B, 0x0A, 0x11, Got: 0x{:02x}!", type_descriptor.tag).into())}
if CONFIG_PRINT_TREE { println!(" Type descriptor(0x{:02x}) - Size: {}", type_descriptor.tag, type_descriptor.size); };
let destination_info: CommonDestinationInfo = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Name lenght: {}", destination_info.name_len);
println!(" Name: {}", destination_info.name());
println!(" Version: {}", destination_info.version);
};
//OUDataProcessingDesc can have OUXOREncryptionDesc(0x0D), OUAESEncryptionDesc(0x0E), OUCompressionDesc(0x0F), OUSecureHashValidationDesc(0x18), OURSAValidationDesc(0x10), OUDataCopyDesc(0x16), OUKeepCurrentDataDesc(0x1E), OUCRC32ValidationDesc(0x12)
let data_processing_descriptor: DescriptorHeader = reader.read_be()?;
if data_processing_descriptor.tag != 0x07 {return Err(format!("Unexpected descriptor type in OUUpgradeItemDesc, Expected: 0x07, Got: 0x{:02x}!", data_processing_descriptor.tag).into())}
if CONFIG_PRINT_TREE { println!(" OUDataProcessingDesc(0x07) - Size: {}", data_processing_descriptor.size); };
let heading_size: u32 = reader.read_be()?;
if CONFIG_PRINT_TREE { println!(" Heading size: {}", heading_size); };
let data_size: u32 = reader.read_be()?;
if CONFIG_PRINT_TREE { println!(" Data size: {}", data_size); };
let mut aes_encryption = false;
//let mut crc32_hash: Option<u32> = None;
//let mut secure_hash: Option<Vec<u8>> = None;
let epos = reader.stream_position()? + (data_processing_descriptor.size - 8) as u64;
while reader.stream_position()? < epos {
let descriptor: DescriptorHeader = reader.read_be()?;
if ![0x0D, 0x0E, 0x0F, 0x18, 0x10, 0x16, 0x1E, 0x12].contains(&descriptor.tag) {return Err(format!("Unexpected descriptor type in OUDataProcessingDesc, Expected: one of 0x0D, 0x0E, 0x0F, 0x18, 0x10, 0x16, 0x1E, 0x12, Got: 0x{:02x}!", descriptor.tag).into())}
if descriptor.tag == 0x0E {
//OUAESEncryptionDesc
if CONFIG_PRINT_TREE { println!(" OUAESEncryptionDesc(0x0E) - Size: {}", descriptor.size); };
let aes_encryption_desc: OUAESEncryptionDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Mode: {}", aes_encryption_desc.mode);
println!(" Key size: {}", aes_encryption_desc.key_size);
println!(" Salt size: {}", aes_encryption_desc.salt_size);
};
aes_encryption = true;
}
else if descriptor.tag == 0x10 {
//OURSAValidationDesc
if CONFIG_PRINT_TREE { println!(" OURSAValidationDesc(0x10) - Size: {}", descriptor.size); };
let rsa_validation_desc: OURSAValidationDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Mode: {}", rsa_validation_desc.mode);
println!(" Field 2: {}", rsa_validation_desc._unknown);
println!(" Signature size: {}", rsa_validation_desc.signature_size);
};
}
else if descriptor.tag == 0x12 {
//OUCRC32ValidationDesc
if CONFIG_PRINT_TREE { println!(" OUCRC32ValidationDesc(0x12) - Size: {}", descriptor.size); };
let crc32: u32 = reader.read_be()?;
if CONFIG_PRINT_TREE { println!(" CRC32: {:02x}", crc32); };
//crc32_hash = Some(crc32);
}
else if descriptor.tag == 0x18 {
//OUSecureHashValidationDesc
if CONFIG_PRINT_TREE { println!(" OUSecureHashValidationDesc(0x18) - Size: {}", descriptor.size); };
let secure_hash_validation_desc: OUSecureHashValidationDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Mode: {}", secure_hash_validation_desc.mode);
println!(" Hash size: {}", secure_hash_validation_desc.hash_size);
println!(" Hash: {}", hex::encode(&secure_hash_validation_desc.hash));
};
//secure_hash = Some(secure_hash_validation_desc.hash);
}
else {
//type not implemented, ignore the data
if CONFIG_PRINT_TREE { println!(" Unimplemented descriptor(0x{:02x}) - Size: {}", descriptor.tag, descriptor.size); };
let _descriptor_data = common::read_exact(&mut reader, descriptor.size as usize);
}
}
//OUGroupInfoDesc
let group_info_descriptor: DescriptorHeader = reader.read_be()?;
if group_info_descriptor.tag != 0x13 {return Err(format!("Unexpected descriptor type in OUUpgradeItemDesc, Expected: 0x13, Got: 0x{:02x}!", group_info_descriptor.tag).into())}
if CONFIG_PRINT_TREE { println!(" OUGroupInfoDesc(0x13) - Size: {}", group_info_descriptor.size); };
let group_id: u32 = reader.read_be()?;
if CONFIG_PRINT_TREE { println!(" Group ID: {}", group_id); };
//create the msd item with all infos
let msd_item = MSDItem {
item_id: item_id,
item_type: type_descriptor.tag,
all_size: chunk.value,
name: destination_info.name(),
heading_size: heading_size,
data_size: data_size,
aes_encryption: aes_encryption,
//crc32_hash: crc32_hash,
//secure_hash: secure_hash,
};
items.push(msd_item);
//go directly to the end of the chunk to skip optional descriptors just in case
reader.seek(SeekFrom::Start(chunk_end))?;
}
else if top_descriptor.tag == 0x02 {
if CONFIG_PRINT_TREE { println!("OUGroupDesc(0x02) - Size: {}", top_descriptor.size); };
let group_desc: OUGroupDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Group ID: {}", group_desc.group_id);
println!(" Field 2: {}", group_desc.field_2);
println!(" Field 3: {}", group_desc.field_3);
};
//OUGroupDesc REQUIRES one of: OUSWImageVersionDesc(0x09), OUSWImageVersionExDesc(0x19), OUOptionalDataVersionDesc(0x14), OUFirmwareVersionDesc(0x15). OPTIONALLY it can also have OUDependenciesDesc
//MSD files seem to exclusively use OUSWImageVersionExDesc
let version_descriptor: DescriptorHeader = reader.read_be()?;
if ![0x09, 0x19, 0x14, 0x15].contains(&version_descriptor.tag) {return Err(format!("Unexpected descriptor type in OUGroupDesc, Expected: one of 0x09, 0x19, 0x14, 0x15, Got: 0x{:02x}!", version_descriptor.tag).into())}
if version_descriptor.tag == 0x19 {
if CONFIG_PRINT_TREE { println!(" OUSWImageVersionExDesc(0x12) - Size: {}", version_descriptor.size); };
let sw_image_version_ex_desc: OUSWImageVersionExDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Name lenght: {}", sw_image_version_ex_desc.name_len);
println!(" Name: {}", sw_image_version_ex_desc.name());
println!(" Major version: {}", sw_image_version_ex_desc.major_ver);
println!(" Minor version: {}", sw_image_version_ex_desc.minor_ver);
println!(" Date year: {}", sw_image_version_ex_desc.date_year);
println!(" Date month: {}", sw_image_version_ex_desc.date_month);
println!(" Date day: {}", sw_image_version_ex_desc.date_day);
};
info = Some(sw_image_version_ex_desc);
}
else {
//type not implemented, ignore the data
if CONFIG_PRINT_TREE { println!(" Unimplemented descriptor(0x{:02x}) - Size: {}", version_descriptor.tag, version_descriptor.size); };
let _descriptor_data = common::read_exact(&mut reader, version_descriptor.size as usize);
}
//go directly to the end of the chunk to skip optional descriptors just in case
reader.seek(SeekFrom::Start(chunk_end))?;
}
else {
return Err(format!("Unexpected top level descriptor type 0x{:02x}!", top_descriptor.tag).into());
}
}
Ok((items, info)) //finally, it will return a list of MSD items and info about image if it was collected.
}
@@ -0,0 +1,269 @@
//MAIN CODE: https://github.com/theubusu/msd_OUITH_parser
use std::io::{Cursor};
use binrw::{BinRead, BinReaderExt};
use crate::utils::common;
// whether to print the tree
static CONFIG_PRINT_TREE: bool = false;
#[derive(BinRead)]
struct DescriptorHeader {
_flag: u8,
size: u32, //size includes the next TAG value
tag: u32,
}
#[derive(BinRead)]
struct OUUpgradeItemDesc {
_flag: u8,
item_id: u32,
unk_flag: u8,
original_size: u32,
processed_size: u32,
unk: u16,
}
#[derive(BinRead)]
struct OUDataProcessingDesc {
_flag: u8,
subdesc_count: u32,
}
#[derive(BinRead)]
struct OUGroupInfoDesc {
_flag: u8,
group_id: u32,
}
#[derive(BinRead)]
struct OUCRC32ValidationDesc {
_flag: u8,
crc32: u32,
}
#[derive(BinRead)]
struct OURSAValidationDesc {
_flag: u8,
signature_size: u16,
public_key_id: u8,
#[br(count = signature_size)] signature: Vec<u8>,
}
#[derive(BinRead)]
struct OUAESEncryptionDesc {
_flag: u8,
private_key_id: u32,
salt_size: u8,
#[br(count = salt_size)] salt: Vec<u8>,
processed_size: u32,
}
#[derive(BinRead)]
struct OUGroupDesc {
_flag: u8,
group_id: u32,
unknown: u16,
}
#[derive(BinRead)]
pub struct OUSWImageVersionDesc {
pub _flag: u8,
pub name_len: u8,
#[br(count = name_len)] pub name_bytes: Vec<u8>,
pub major_ver: u16,
pub minor_ver: u16,
pub date_year: u16,
pub date_month: u8,
pub date_day: u8,
}
impl OUSWImageVersionDesc {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
struct OUPartitionVersionDesc {
_flag: u8,
name_len: u8,
#[br(count = name_len)] name_bytes: Vec<u8>,
version: u16,
}
impl OUPartitionVersionDesc {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
//this is a struct that will communicate all values important when parsing the MSD file.
pub struct MSDItem {
pub item_id: u32,
pub name: String,
//pub crc32_hash: Option<u32>,
pub aes_encryption: bool,
pub aes_salt: Option<Vec<u8>>,
}
pub fn parse_blob_1_8(blob: &[u8]) -> Result<(Vec<MSDItem>, Option<OUSWImageVersionDesc>), Box<dyn std::error::Error>> {
let mut reader = Cursor::new(blob);
let mut items: Vec<MSDItem> = Vec::new();
let mut info: Option<OUSWImageVersionDesc> = None;
let _signature = common::read_exact(&mut reader, 256)?; //signature included at the beginning of blob in MSD file
let magic = common::read_exact(&mut reader, 51)?;
if magic != b"Tizen Software Upgrade Tree Binary Format ver. 1.8\x00" {
return Err(format!("Invalid tree magic!").into())
}
let top_level_descriptor_count: u32 = reader.read_be()?; //BIG ENDIAN
if CONFIG_PRINT_TREE { println!("\nTop level descriptor count: {}", top_level_descriptor_count); };
for _i in 0..top_level_descriptor_count {
//parse top level descriptor. it can only be ID 1(OUUpgradeItemDesc) or 2(OUGroupDesc)
let top_descriptor: DescriptorHeader = reader.read_be()?;
if top_descriptor.tag == 0x01 {
if CONFIG_PRINT_TREE { println!("OUUpgradeItemDesc(0x01) - Size: {}", top_descriptor.size); };
let upgrade_item_desc: OUUpgradeItemDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Item ID: {}", upgrade_item_desc.item_id);
println!(" Unknown flag: {}", upgrade_item_desc.unk_flag);
println!(" Original size: {}", upgrade_item_desc.original_size);
println!(" Processed size: {}", upgrade_item_desc.processed_size);
println!(" Unknown: {}", upgrade_item_desc.unk);
};
let subdesc_count: u32 = reader.read_le()?; //LITTLE ENDIAN??
if CONFIG_PRINT_TREE { println!(" Subdescriptor count: {}", subdesc_count); };
let mut name: Option<String> = None;
//let mut crc32_hash: Option<u32> = None;
let mut aes_encryption = false;
let mut aes_salt: Option<Vec<u8>> = None;
for _i in 0..subdesc_count {
let sub_descriptor: DescriptorHeader = reader.read_be()?;
if sub_descriptor.tag == 0x0A {
if CONFIG_PRINT_TREE { println!(" OUPartitionVersionDesc(0x0A) - Size: {}", sub_descriptor.size); };
let partition_version_desc: OUPartitionVersionDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Name lenght: {}", partition_version_desc.name_len);
println!(" Name: {}", partition_version_desc.name());
println!(" Version: {}", partition_version_desc.version);
};
name = Some(partition_version_desc.name());
}
else if sub_descriptor.tag == 0x07 {
if CONFIG_PRINT_TREE { println!(" OUDataProcessingDesc(0x07) - Size: {}", sub_descriptor.size); };
let data_processing_desc: OUDataProcessingDesc = reader.read_le()?; //LITTLE ENDIAN??
if CONFIG_PRINT_TREE { println!(" Subdescriptor count: {}", data_processing_desc.subdesc_count); };
for _i in 0..data_processing_desc.subdesc_count {
let data_processing_sub_desc: DescriptorHeader = reader.read_be()?;
if data_processing_sub_desc.tag == 0x12 {
if CONFIG_PRINT_TREE { println!(" OUCRC32ValidationDesc(0x12) - Size: {}", data_processing_sub_desc.size); };
let crc32_validation_desc: OUCRC32ValidationDesc = reader.read_be()?;
if CONFIG_PRINT_TREE { println!(" CRC32: {:02x}", crc32_validation_desc.crc32); };
//crc32_hash = Some(crc32_validation_desc.crc32);
}
else if data_processing_sub_desc.tag == 0x10 {
if CONFIG_PRINT_TREE { println!(" OURSAValidationDesc(0x10) - Size: {}", data_processing_sub_desc.size); };
let rsa_validation_desc: OURSAValidationDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Signature size: {}", rsa_validation_desc.signature_size);
println!(" Public key ID: {}", rsa_validation_desc.public_key_id);
println!(" Signature: {}", hex::encode(&rsa_validation_desc.signature));
};
}
else if data_processing_sub_desc.tag == 0x0E {
if CONFIG_PRINT_TREE { println!(" OUAESEncryptionDesc(0x0E) - Size: {}", data_processing_sub_desc.size); };
let aes_encryption_desc: OUAESEncryptionDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Private key ID: {}", aes_encryption_desc.private_key_id);
println!(" Salt size: {}", aes_encryption_desc.salt_size);
println!(" Salt: {}", hex::encode(&aes_encryption_desc.salt));
println!(" Processed size: {}", aes_encryption_desc.processed_size);
};
aes_encryption = true;
aes_salt = Some(aes_encryption_desc.salt);
}
else {
if CONFIG_PRINT_TREE { println!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", data_processing_sub_desc.tag, data_processing_sub_desc.size); };
let _ = common::read_exact(&mut reader, data_processing_sub_desc.size as usize - 4)?;
}
}
}
else if sub_descriptor.tag == 0x13 {
if CONFIG_PRINT_TREE { println!(" OUGroupInfoDesc(0x13) - Size: {}", sub_descriptor.size); };
let group_info_desc: OUGroupInfoDesc = reader.read_be()?;
if CONFIG_PRINT_TREE { println!(" Group ID: {}", group_info_desc.group_id); };
}
else {
if CONFIG_PRINT_TREE { println!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
let _ = common::read_exact(&mut reader, sub_descriptor.size as usize - 4)?;
}
}
if let Some(name) = name {
//create the msd item with all infos
let msd_item = MSDItem {
item_id: upgrade_item_desc.item_id,
name: name,
//crc32_hash: crc32_hash,
aes_encryption: aes_encryption,
aes_salt: aes_salt,
};
items.push(msd_item);
} else {
//if no name found panic because it is required
return Err(format!("Could not retrieve required Name for item ID {}", upgrade_item_desc.item_id).into());
}
}
else if top_descriptor.tag == 0x02 {
if CONFIG_PRINT_TREE { println!("OUGroupDesc(0x02) - Size: {}", top_descriptor.size); };
let group_desc: OUGroupDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Group ID: {}", group_desc.group_id);
println!(" Unknown: {}", group_desc.unknown);
};
let subdesc_count: u32 = reader.read_le()?; //LITTLE ENDIAN??
if CONFIG_PRINT_TREE { println!(" Subdescriptor count: {}", subdesc_count); };
for _i in 0..subdesc_count {
let sub_descriptor: DescriptorHeader = reader.read_be()?;
if sub_descriptor.tag == 0x19 {
if CONFIG_PRINT_TREE { println!(" OUSWImageVersionDesc(0x19) - Size: {}", sub_descriptor.size); };
let sw_image_version_desc: OUSWImageVersionDesc = reader.read_be()?;
if CONFIG_PRINT_TREE {
println!(" Name lenght: {}", sw_image_version_desc.name_len);
println!(" Name: {}", sw_image_version_desc.name());
println!(" Major ver: {}", sw_image_version_desc.major_ver);
println!(" Minor ver: {}", sw_image_version_desc.minor_ver);
println!(" Year: {}", sw_image_version_desc.date_year);
println!(" Month: {}", sw_image_version_desc.date_month);
println!(" Day: {}", sw_image_version_desc.date_day);
};
info = Some(sw_image_version_desc);
} else {
if CONFIG_PRINT_TREE { println!(" Unimplemented Descriptor (0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
let _ = common::read_exact(&mut reader, sub_descriptor.size as usize - 4)?;
}
}
}
else {
return Err(format!("Unexpected top level descriptor type 0x{:02x}!", top_descriptor.tag).into());
}
}
Ok((items, info)) //finally, it will return a list of MSD items and info about image if it was collected.
}
@@ -0,0 +1,271 @@
//MAIN CODE: https://github.com/theubusu/msd_OUITH_parser
use std::io::{Cursor};
use binrw::{BinRead, BinReaderExt};
use crate::utils::common;
// whether to print the tree
static CONFIG_PRINT_TREE: bool = false;
#[derive(BinRead)]
struct DescriptorHeader {
_flag: u8,
size: u32, //size includes the next TAG value
tag: u32,
}
#[derive(BinRead)]
struct OUUpgradeItemDesc {
_flag: u8,
item_id: u32,
unk_flag: u8,
original_size: u64,
processed_size: u64,
unk: u16,
}
#[derive(BinRead)]
struct OUDataProcessingDesc {
_flag: u8,
subdesc_count: u32,
}
#[derive(BinRead)]
struct OUGroupInfoDesc {
_flag: u8,
group_id: u32,
}
#[derive(BinRead)]
struct OUCRC32ValidationDesc {
_flag: u8,
crc32: u32,
}
#[derive(BinRead)]
struct OURSAValidationDesc {
_flag: u8,
public_key_id: u32,
signature_size: u16,
#[br(count = signature_size)] signature: Vec<u8>,
}
#[derive(BinRead)]
struct OUAESEncryptionDesc {
_flag: u8,
salt_size: u8,
#[br(count = salt_size)] salt: Vec<u8>,
processed_size: u64,
}
#[derive(BinRead)]
struct OUGroupDesc {
_flag: u8,
group_id: u32,
}
#[derive(BinRead)]
pub struct OUSWImageVersionDesc {
pub _flag: u8,
pub name_len: u8,
#[br(count = name_len)] pub name_bytes: Vec<u8>,
pub major_ver: u16,
pub minor_ver: u16,
}
impl OUSWImageVersionDesc {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
struct OUPartitionVersionDesc {
_flag: u8,
name_len: u8,
#[br(count = name_len)] name_bytes: Vec<u8>,
}
impl OUPartitionVersionDesc {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
struct OUSecureDowngradeDesc {
_flag: u8,
image_generation_date: u32,
_unk1: u32,
_unk2: u32,
}
//this is a struct that will communicate all values important when parsing the MSD file.
pub struct MSDItem {
pub item_id: u32,
pub name: String,
//pub crc32_hash: Option<u32>,
pub aes_encryption: bool,
pub aes_salt: Option<Vec<u8>>,
}
pub fn parse_blob_1_9(blob: &[u8]) -> Result<(Vec<MSDItem>, Option<OUSWImageVersionDesc>), Box<dyn std::error::Error>> {
let mut reader = Cursor::new(blob);
let mut items: Vec<MSDItem> = Vec::new();
let mut info: Option<OUSWImageVersionDesc> = None;
let _public_key_id: u32 = reader.read_le()?;
let signature_size: u16 = reader.read_le()?;
let _signature = common::read_exact(&mut reader, signature_size as usize)?; //signature included at the beginning of blob in MSD file
let magic = common::read_exact(&mut reader, 51)?;
if magic != b"Tizen Software Upgrade Tree Binary Format ver. 1.9\x00" {
return Err(format!("Invalid tree magic!").into())
}
let top_level_descriptor_count: u32 = reader.read_le()?;
if CONFIG_PRINT_TREE { println!("\nTop level descriptor count: {}", top_level_descriptor_count); };
for _i in 0..top_level_descriptor_count {
//parse top level descriptor. it can only be ID 1(OUUpgradeItemDesc) or 2(OUGroupDesc) or 0x37(OUSecureDowngradeDesc)
let top_descriptor: DescriptorHeader = reader.read_le()?;
if top_descriptor.tag == 0x01 {
if CONFIG_PRINT_TREE { println!("OUUpgradeItemDesc(0x01) - Size: {}", top_descriptor.size); };
let upgrade_item_desc: OUUpgradeItemDesc = reader.read_le()?;
if CONFIG_PRINT_TREE {
println!(" Item ID: {}", upgrade_item_desc.item_id);
println!(" Unknown flag: {}", upgrade_item_desc.unk_flag);
println!(" Original size: {}", upgrade_item_desc.original_size);
println!(" Processed size: {}", upgrade_item_desc.processed_size);
println!(" Unknown: {}", upgrade_item_desc.unk);
};
let subdesc_count: u32 = reader.read_le()?;
if CONFIG_PRINT_TREE { println!(" Subdescriptor count: {}", subdesc_count); };
let mut name: Option<String> = None;
//let mut crc32_hash: Option<u32> = None;
let mut aes_encryption = false;
let mut aes_salt: Option<Vec<u8>> = None;
for _i in 0..subdesc_count {
let sub_descriptor: DescriptorHeader = reader.read_le()?;
if sub_descriptor.tag == 0x0A {
if CONFIG_PRINT_TREE { println!(" OUPartitionVersionDesc(0x0A) - Size: {}", sub_descriptor.size); };
let partition_version_desc: OUPartitionVersionDesc = reader.read_le()?;
if CONFIG_PRINT_TREE {
println!(" Name lenght: {}", partition_version_desc.name_len);
println!(" Name: {}", partition_version_desc.name());
};
name = Some(partition_version_desc.name());
}
else if sub_descriptor.tag == 0x07 {
if CONFIG_PRINT_TREE { println!(" OUDataProcessingDesc(0x07) - Size: {}", sub_descriptor.size); };
let data_processing_desc: OUDataProcessingDesc = reader.read_le()?;
if CONFIG_PRINT_TREE { println!(" Subdescriptor count: {}", data_processing_desc.subdesc_count); };
for _i in 0..data_processing_desc.subdesc_count {
let data_processing_sub_desc: DescriptorHeader = reader.read_le()?;
if data_processing_sub_desc.tag == 0x12 {
if CONFIG_PRINT_TREE { println!(" OUCRC32ValidationDesc(0x12) - Size: {}", data_processing_sub_desc.size); };
let crc32_validation_desc: OUCRC32ValidationDesc = reader.read_le()?;
if CONFIG_PRINT_TREE { println!(" CRC32: {:02x}", crc32_validation_desc.crc32); };
//crc32_hash = Some(crc32_validation_desc.crc32);
}
else if data_processing_sub_desc.tag == 0x10 {
if CONFIG_PRINT_TREE { println!(" OURSAValidationDesc(0x10) - Size: {}", data_processing_sub_desc.size); };
let rsa_validation_desc: OURSAValidationDesc = reader.read_le()?;
if CONFIG_PRINT_TREE {
println!(" Signature size: {}", rsa_validation_desc.signature_size);
println!(" Public key ID: {}", rsa_validation_desc.public_key_id);
println!(" Signature: {}", hex::encode(&rsa_validation_desc.signature));
};
}
else if data_processing_sub_desc.tag == 0x0E {
if CONFIG_PRINT_TREE { println!(" OUAESEncryptionDesc(0x0E) - Size: {}", data_processing_sub_desc.size); };
let aes_encryption_desc: OUAESEncryptionDesc = reader.read_le()?;
if CONFIG_PRINT_TREE {
println!(" Salt size: {}", aes_encryption_desc.salt_size);
println!(" Salt: {}", hex::encode(&aes_encryption_desc.salt));
println!(" Processed size: {}", aes_encryption_desc.processed_size);
};
aes_encryption = true;
aes_salt = Some(aes_encryption_desc.salt);
}
else {
if CONFIG_PRINT_TREE { println!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", data_processing_sub_desc.tag, data_processing_sub_desc.size); };
let _ = common::read_exact(&mut reader, data_processing_sub_desc.size as usize - 4)?;
}
}
}
else if sub_descriptor.tag == 0x13 {
if CONFIG_PRINT_TREE { println!(" OUGroupInfoDesc(0x13) - Size: {}", sub_descriptor.size); };
let group_info_desc: OUGroupInfoDesc = reader.read_le()?;
if CONFIG_PRINT_TREE { println!(" Group ID: {}", group_info_desc.group_id); };
}
else {
if CONFIG_PRINT_TREE { println!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
let _ = common::read_exact(&mut reader, sub_descriptor.size as usize - 4)?;
}
}
if let Some(name) = name {
//create the msd item with all infos
let msd_item = MSDItem {
item_id: upgrade_item_desc.item_id,
name: name,
//crc32_hash: crc32_hash,
aes_encryption: aes_encryption,
aes_salt: aes_salt,
};
items.push(msd_item);
} else {
//if no name found panic because it is required
return Err(format!("Could not retrieve required Name for item ID {}", upgrade_item_desc.item_id).into());
}
}
else if top_descriptor.tag == 0x02 {
if CONFIG_PRINT_TREE { println!("OUGroupDesc(0x02) - Size: {}", top_descriptor.size); };
let group_desc: OUGroupDesc = reader.read_le()?;
if CONFIG_PRINT_TREE { println!(" Group ID: {}", group_desc.group_id); };
let subdesc_count: u32 = reader.read_le()?; //LITTLE ENDIAN??
if CONFIG_PRINT_TREE { println!(" Subdescriptor count: {}", subdesc_count); };
for _i in 0..subdesc_count {
let sub_descriptor: DescriptorHeader = reader.read_le()?;
if sub_descriptor.tag == 0x19 {
if CONFIG_PRINT_TREE { println!(" OUSWImageVersionDesc(0x19) - Size: {}", sub_descriptor.size); };
let sw_image_version_desc: OUSWImageVersionDesc = reader.read_le()?;
if CONFIG_PRINT_TREE {
println!(" Name lenght: {}", sw_image_version_desc.name_len);
println!(" Name: {}", sw_image_version_desc.name());
println!(" Major ver: {}", sw_image_version_desc.major_ver);
println!(" Minor ver: {}", sw_image_version_desc.minor_ver);
};
info = Some(sw_image_version_desc);
} else {
if CONFIG_PRINT_TREE { println!(" Unimplemented Descriptor (0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
let _ = common::read_exact(&mut reader, sub_descriptor.size as usize - 4)?;
}
}
}
else if top_descriptor.tag == 0x37 {
if CONFIG_PRINT_TREE { println!("OUSecureDowngradeDesc(0x37) - Size: {}", top_descriptor.size); };
let secure_downgrade_desc: OUSecureDowngradeDesc = reader.read_le()?;
if CONFIG_PRINT_TREE { println!(" Image generation timestamp: {}", secure_downgrade_desc.image_generation_date); };
}
else {
return Err(format!("Unexpected top level descriptor type 0x{:02x}!", top_descriptor.tag).into());
}
}
Ok((items, info)) //finally, it will return a list of MSD items and info about image if it was collected.
}
+28
View File
@@ -0,0 +1,28 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(BinRead)]
pub struct FileHeader {
_magic_bytes: [u8; 6],
pub section_count: u32
}
#[derive(BinRead)]
pub struct SectionEntry {
pub index: u32,
pub offset: u32,
pub size: u32,
}
#[derive(BinRead)]
pub struct HeaderEntry {
pub offset: u32,
pub size: u32,
_name_length: u8,
#[br(count = _name_length)] name_bytes: Vec<u8>,
}
impl HeaderEntry {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
@@ -1,51 +1,18 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "msd10", detector_func: is_msd10_file, extractor_func: extract_msd10 }
}
use crate::AppContext;
use std::fs::{self, OpenOptions};
use std::path::{Path};
use std::path::Path;
use std::io::{Write, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::keys;
use crate::formats::msd::{decrypt_aes_salted_old, decrypt_aes_salted_tizen, decrypt_aes_tizen};
use crate::utils::msd_ouith_parser_old::{parse_ouith_blob};
use crate::utils::msd_ouith_parser_tizen_1_8::{parse_blob_1_8};
#[derive(BinRead)]
struct FileHeader {
#[br(count = 6)] _magic_bytes: Vec<u8>,
section_count: u32
}
#[derive(BinRead)]
struct SectionEntry {
index: u32,
offset: u32,
size: u32,
}
#[derive(BinRead)]
struct HeaderEntry {
offset: u32,
size: u32,
_name_length: u8,
#[br(count = _name_length)] name_bytes: Vec<u8>,
}
impl HeaderEntry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
struct Section {
index: u32,
offset: u32,
size: u32,
}
use crate::formats::msd::msd_ouith_parser_old::{parse_ouith_blob};
use crate::formats::msd::msd_ouith_parser_tizen_1_8::{parse_blob_1_8};
use include::*;
pub fn is_msd10_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -64,11 +31,11 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let header: FileHeader = file.read_le()?;
println!("\nNumber of sections: {}", header.section_count);
let mut sections: Vec<Section> = Vec::new();
let mut sections: Vec<SectionEntry> = Vec::new();
for _i in 0..header.section_count {
let section: SectionEntry = file.read_le()?;
println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
sections.push(Section {index: section.index, offset: section.offset, size: section.size});
sections.push(section);
}
let _zero_padding = common::read_exact(&mut file, 4)?;
+30
View File
@@ -0,0 +1,30 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(BinRead)]
pub struct FileHeader {
_magic_bytes: [u8; 6],
_header_checksum: u32, //CRC32 of data with the size of "_header_size"
_header_size: u64,
pub section_count: u32,
}
#[derive(BinRead)]
pub struct SectionEntry {
pub index: u32,
pub offset: u64,
pub size: u64,
}
#[derive(BinRead)]
pub struct HeaderEntry {
pub offset: u64,
pub size: u32,
_name_length: u8,
#[br(count = _name_length)] name_bytes: Vec<u8>,
}
impl HeaderEntry {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
@@ -1,52 +1,17 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "msd11", detector_func: is_msd11_file, extractor_func: extract_msd11 }
}
use crate::AppContext;
use std::fs::{self, OpenOptions};
use std::path::{Path};
use std::io::{Write};
use binrw::{BinRead, BinReaderExt};
use std::path::Path;
use std::io::Write;
use binrw::BinReaderExt;
use crate::utils::common;
use crate::keys;
use crate::formats::msd::{decrypt_aes_salted_tizen, decrypt_aes_tizen};
use crate::utils::msd_ouith_parser_tizen_1_9::{parse_blob_1_9};
#[derive(BinRead)]
struct FileHeader {
#[br(count = 6)] _magic_bytes: Vec<u8>,
_header_checksum: u32, //CRC32 of data with the size of "_header_size"
_header_size: u64,
section_count: u32,
}
#[derive(BinRead)]
struct SectionEntry {
index: u32,
offset: u64,
size: u64,
}
#[derive(BinRead)]
struct HeaderEntry {
offset: u64,
size: u32,
_name_length: u8,
#[br(count = _name_length)] name_bytes: Vec<u8>,
}
impl HeaderEntry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
struct Section {
index: u32,
offset: u64,
size: u64,
}
use crate::formats::msd::msd_ouith_parser_tizen_1_9::{parse_blob_1_9};
use include::*;
pub fn is_msd11_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -65,11 +30,11 @@ pub fn extract_msd11(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let header: FileHeader = file.read_le()?;
println!("\nNumber of sections: {}", header.section_count);
let mut sections: Vec<Section> = Vec::new();
let mut sections: Vec<SectionEntry> = Vec::new();
for _i in 0..header.section_count {
let section: SectionEntry = file.read_le()?;
println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
sections.push(Section {index: section.index, offset: section.offset, size: section.size});
sections.push(section);
}
let header_count: u32 = file.read_le()?;
+7
View File
@@ -0,0 +1,7 @@
pub fn parse_number(s: &str) -> Option<u64> {
if let Some(hex_str) = s.strip_prefix("0x") {
u64::from_str_radix(hex_str, 16).ok()
} else {
u64::from_str_radix(s, 16).ok()
}
}
@@ -1,17 +1,16 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "mstar", detector_func: is_mstar_file, extractor_func: extract_mstar }
}
use crate::AppContext;
use std::fs::{self, OpenOptions};
use std::path::{Path};
use std::io::{Write};
use std::path::Path;
use std::io::Write;
use crate::utils::common;
use crate::utils::compression::{decompress_lzma, decompress_lz4};
use crate::utils::lzop::{unlzop_to_file};
use crate::utils::sparse::{unsparse_to_file};
use include::*;
pub fn is_mstar_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -25,14 +24,6 @@ pub fn is_mstar_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<d
}
}
fn parse_number(s: &str) -> Option<u64> {
if let Some(hex_str) = s.strip_prefix("0x") {
u64::from_str_radix(hex_str, 16).ok()
} else {
u64::from_str_radix(s, 16).ok()
}
}
pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let file = app_ctx.file().ok_or("Extractor expected file")?;
+65
View File
@@ -0,0 +1,65 @@
use crate::utils::common;
use binrw::BinRead;
pub fn find_bytes(data: &[u8], pattern: &[u8]) -> Option<usize> {
data.windows(pattern.len()).position(|window| window == pattern)
}
pub static PITIT_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50];
pub static PITIT_END_MARKER: u32 = 0x69_54_49_50; //PITi - end marker of PITIT
#[derive(BinRead)]
pub struct PITITPITEntry {
pub nand_size: u32,
pub pit_offset: u32,
pub pit_size: u32,
_table_id: u32,
}
#[derive(BinRead)]
pub struct PITITBITEntry {
pub bit_offset: u32,
pub bit_size: u32,
_private_data_1: u32,
_private_data_2: u32,
}
#[derive(BinRead)]
pub struct PITHeader {
pub pit_magic: [u8; 8],
_version: u32,
pub first_entry_offset: u32, //"header len"
pub entry_size: u32, //"item lenght"
pub entry_count: u32, //"item num"
}
pub static PIT_MAGIC: [u8; 8] = [0xDC, 0xEA, 0x30, 0x85, 0xDC, 0xEA, 0x30, 0x85];
#[derive(BinRead)]
pub struct PITEntry {
name_bytes: [u8; 16],
pub partition_id: u32,
_part_info: u32,
pub offset_on_nand: u32,
pub size_on_nand: u32,
_enc_size: u32,
_no_enc_size: u32,
_reserve: [u8; 24],
}
impl PITEntry {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
pub struct BITEntry {
pub partition_id: u32,
pub offset: u32,
pub size: u32,
pub offset_in_target_part: u32,
_bin_info: u32, //"Bin info"
}
pub static BIT_MAGIC: [u8; 20] = [0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85];
pub static BIT_END_MARKER: u32 = 0x85_30_EF_EF; //EF EF 30 85 - end marker of BIT
@@ -1,82 +1,19 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "mtk_bdp", detector_func: is_mtk_bdp_file, extractor_func: extract_mtk_bdp }
}
use crate::AppContext;
use std::path::{Path};
use std::fs::{self, OpenOptions};
use std::io::{Seek, SeekFrom, Read, Write};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
pub struct MtkBdpContext {
pitit_offset: u64,
}
static PITIT_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50];
static PITIT_END_MARKER: u32 = 0x69_54_49_50; //PITi - end marker of PITIT
#[derive(BinRead)]
struct PITITPITEntry {
nand_size: u32,
pit_offset: u32,
pit_size: u32,
_table_id: u32,
}
#[derive(BinRead)]
struct PITITBITEntry {
bit_offset: u32,
bit_size: u32,
_private_data_1: u32,
_private_data_2: u32,
}
#[derive(BinRead)]
struct PITHeader {
#[br(count = 8)] pit_magic: Vec<u8>,
_version: u32,
first_entry_offset: u32, //"header len"
entry_size: u32, //"item lenght"
entry_count: u32, //"item num"
}
static PIT_MAGIC: [u8; 8] = [0xDC, 0xEA, 0x30, 0x85, 0xDC, 0xEA, 0x30, 0x85];
#[derive(BinRead)]
struct PITEntry {
#[br(count = 16)] name_bytes: Vec<u8>,
partition_id: u32,
_part_info: u32,
offset_on_nand: u32,
size_on_nand: u32,
_enc_size: u32,
_no_enc_size: u32,
#[br(count = 24)] _reserve: Vec<u8>,
}
impl PITEntry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
}
#[derive(BinRead)]
struct BITEntry {
partition_id: u32,
offset: u32,
size: u32,
offset_in_target_part: u32,
_bin_info: u32, //"Bin info"
}
static BIT_MAGIC: [u8; 20] = [0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85];
static BIT_END_MARKER: u32 = 0x85_30_EF_EF; //EF EF 30 85 - end marker of BIT
fn find_bytes(data: &[u8], pattern: &[u8]) -> Option<usize> {
data.windows(pattern.len()).position(|window| window == pattern)
}
pub fn is_mtk_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let mut file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
let file_size = file.metadata()?.len();
+326
View File
@@ -0,0 +1,326 @@
pub const CHARLEN: [(u32,u32); 288] = [
//key, pos
(0x00000002u32, 0x00000005u32),
(0x00000003u32, 0x00000005u32),
(0x0000000au32, 0x00000006u32),
(0x00000028u32, 0x00000007u32),
(0x00000029u32, 0x00000007u32),
(0x0000002au32, 0x00000007u32),
(0x0000002bu32, 0x00000007u32),
(0x0000002cu32, 0x00000007u32),
(0x0000002du32, 0x00000007u32),
(0x0000002eu32, 0x00000007u32),
(0x0000002fu32, 0x00000007u32),
(0x0000008au32, 0x00000008u32),
(0x0000008bu32, 0x00000008u32),
(0x0000008cu32, 0x00000008u32),
(0x0000008du32, 0x00000008u32),
(0x0000008eu32, 0x00000008u32),
(0x00000030u32, 0x00000007u32),
(0x0000008fu32, 0x00000008u32),
(0x00000090u32, 0x00000008u32),
(0x00000180u32, 0x00000009u32),
(0x00000091u32, 0x00000008u32),
(0x00000181u32, 0x00000009u32),
(0x00000182u32, 0x00000009u32),
(0x00000183u32, 0x00000009u32),
(0x00000031u32, 0x00000007u32),
(0x00000092u32, 0x00000008u32),
(0x00000093u32, 0x00000008u32),
(0x00000094u32, 0x00000008u32),
(0x0000000bu32, 0x00000006u32),
(0x00000184u32, 0x00000009u32),
(0x00000185u32, 0x00000009u32),
(0x00000186u32, 0x00000009u32),
(0x0000000cu32, 0x00000006u32),
(0x00000032u32, 0x00000007u32),
(0x00000033u32, 0x00000007u32),
(0x00000095u32, 0x00000008u32),
(0x00000096u32, 0x00000008u32),
(0x00000097u32, 0x00000008u32),
(0x00000187u32, 0x00000009u32),
(0x00000188u32, 0x00000009u32),
(0x00000034u32, 0x00000007u32),
(0x00000098u32, 0x00000008u32),
(0x00000099u32, 0x00000008u32),
(0x00000189u32, 0x00000009u32),
(0x0000009au32, 0x00000008u32),
(0x0000009bu32, 0x00000008u32),
(0x0000018au32, 0x00000009u32),
(0x0000018bu32, 0x00000009u32),
(0x00000035u32, 0x00000007u32),
(0x0000009cu32, 0x00000008u32),
(0x0000009du32, 0x00000008u32),
(0x0000018cu32, 0x00000009u32),
(0x0000018du32, 0x00000009u32),
(0x0000018eu32, 0x00000009u32),
(0x0000018fu32, 0x00000009u32),
(0x00000190u32, 0x00000009u32),
(0x0000009eu32, 0x00000008u32),
(0x00000191u32, 0x00000009u32),
(0x00000192u32, 0x00000009u32),
(0x00000193u32, 0x00000009u32),
(0x00000194u32, 0x00000009u32),
(0x00000195u32, 0x00000009u32),
(0x00000196u32, 0x00000009u32),
(0x00000197u32, 0x00000009u32),
(0x00000036u32, 0x00000007u32),
(0x0000009fu32, 0x00000008u32),
(0x00000037u32, 0x00000007u32),
(0x00000038u32, 0x00000007u32),
(0x00000198u32, 0x00000009u32),
(0x00000199u32, 0x00000009u32),
(0x000000a0u32, 0x00000008u32),
(0x0000019au32, 0x00000009u32),
(0x00000039u32, 0x00000007u32),
(0x0000003au32, 0x00000007u32),
(0x0000003bu32, 0x00000007u32),
(0x000000a1u32, 0x00000008u32),
(0x000000a2u32, 0x00000008u32),
(0x000000a3u32, 0x00000008u32),
(0x0000019bu32, 0x00000009u32),
(0x0000019cu32, 0x00000009u32),
(0x0000019du32, 0x00000009u32),
(0x0000019eu32, 0x00000009u32),
(0x0000019fu32, 0x00000009u32),
(0x000001a0u32, 0x00000009u32),
(0x000001a1u32, 0x00000009u32),
(0x000003b0u32, 0x0000000au32),
(0x000003b1u32, 0x0000000au32),
(0x000003b2u32, 0x0000000au32),
(0x000001a2u32, 0x00000009u32),
(0x000001a3u32, 0x00000009u32),
(0x000001a4u32, 0x00000009u32),
(0x000003b3u32, 0x0000000au32),
(0x000001a5u32, 0x00000009u32),
(0x000003b4u32, 0x0000000au32),
(0x000003b5u32, 0x0000000au32),
(0x000003b6u32, 0x0000000au32),
(0x0000003cu32, 0x00000007u32),
(0x000000a4u32, 0x00000008u32),
(0x000000a5u32, 0x00000008u32),
(0x000000a6u32, 0x00000008u32),
(0x000000a7u32, 0x00000008u32),
(0x000000a8u32, 0x00000008u32),
(0x000001a6u32, 0x00000009u32),
(0x000003b7u32, 0x0000000au32),
(0x0000000du32, 0x00000006u32),
(0x000000a9u32, 0x00000008u32),
(0x000001a7u32, 0x00000009u32),
(0x000001a8u32, 0x00000009u32),
(0x000001a9u32, 0x00000009u32),
(0x000001aau32, 0x00000009u32),
(0x000001abu32, 0x00000009u32),
(0x000001acu32, 0x00000009u32),
(0x0000003du32, 0x00000007u32),
(0x000001adu32, 0x00000009u32),
(0x000000aau32, 0x00000008u32),
(0x000001aeu32, 0x00000009u32),
(0x000001afu32, 0x00000009u32),
(0x000001b0u32, 0x00000009u32),
(0x000003b8u32, 0x0000000au32),
(0x000003b9u32, 0x0000000au32),
(0x0000003eu32, 0x00000007u32),
(0x000001b1u32, 0x00000009u32),
(0x000001b2u32, 0x00000009u32),
(0x000001b3u32, 0x00000009u32),
(0x000003bau32, 0x0000000au32),
(0x000003bbu32, 0x0000000au32),
(0x000003bcu32, 0x0000000au32),
(0x000003bdu32, 0x0000000au32),
(0x0000003fu32, 0x00000007u32),
(0x000000abu32, 0x00000008u32),
(0x000001b4u32, 0x00000009u32),
(0x000001b5u32, 0x00000009u32),
(0x000001b6u32, 0x00000009u32),
(0x000003beu32, 0x0000000au32),
(0x000003bfu32, 0x0000000au32),
(0x000003c0u32, 0x0000000au32),
(0x000000acu32, 0x00000008u32),
(0x000000adu32, 0x00000008u32),
(0x000001b7u32, 0x00000009u32),
(0x000001b8u32, 0x00000009u32),
(0x000001b9u32, 0x00000009u32),
(0x000003c1u32, 0x0000000au32),
(0x000003c2u32, 0x0000000au32),
(0x000003c3u32, 0x0000000au32),
(0x000000aeu32, 0x00000008u32),
(0x000000afu32, 0x00000008u32),
(0x000000b0u32, 0x00000008u32),
(0x000003c4u32, 0x0000000au32),
(0x000003c5u32, 0x0000000au32),
(0x000003c6u32, 0x0000000au32),
(0x000003c7u32, 0x0000000au32),
(0x000003c8u32, 0x0000000au32),
(0x00000040u32, 0x00000007u32),
(0x000000b1u32, 0x00000008u32),
(0x000001bau32, 0x00000009u32),
(0x000001bbu32, 0x00000009u32),
(0x000003c9u32, 0x0000000au32),
(0x000003cau32, 0x0000000au32),
(0x000003cbu32, 0x0000000au32),
(0x000003ccu32, 0x0000000au32),
(0x000000b2u32, 0x00000008u32),
(0x000000b3u32, 0x00000008u32),
(0x000003cdu32, 0x0000000au32),
(0x000003ceu32, 0x0000000au32),
(0x000003cfu32, 0x0000000au32),
(0x000003d0u32, 0x0000000au32),
(0x000003d1u32, 0x0000000au32),
(0x000007deu32, 0x0000000bu32),
(0x000000b4u32, 0x00000008u32),
(0x000001bcu32, 0x00000009u32),
(0x000001bdu32, 0x00000009u32),
(0x000001beu32, 0x00000009u32),
(0x000003d2u32, 0x0000000au32),
(0x000003d3u32, 0x0000000au32),
(0x000007dfu32, 0x0000000bu32),
(0x000007e0u32, 0x0000000bu32),
(0x000000b5u32, 0x00000008u32),
(0x000003d4u32, 0x0000000au32),
(0x000003d5u32, 0x0000000au32),
(0x000007e1u32, 0x0000000bu32),
(0x000003d6u32, 0x0000000au32),
(0x000000b6u32, 0x00000008u32),
(0x000003d7u32, 0x0000000au32),
(0x000007e2u32, 0x0000000bu32),
(0x000003d8u32, 0x0000000au32),
(0x000007e3u32, 0x0000000bu32),
(0x000007e4u32, 0x0000000bu32),
(0x000007e5u32, 0x0000000bu32),
(0x000003d9u32, 0x0000000au32),
(0x000000b7u32, 0x00000008u32),
(0x000007e6u32, 0x0000000bu32),
(0x000007e7u32, 0x0000000bu32),
(0x000000b8u32, 0x00000008u32),
(0x000001bfu32, 0x00000009u32),
(0x000003dau32, 0x0000000au32),
(0x000003dbu32, 0x0000000au32),
(0x000003dcu32, 0x0000000au32),
(0x000007e8u32, 0x0000000bu32),
(0x000007e9u32, 0x0000000bu32),
(0x000007eau32, 0x0000000bu32),
(0x000001c0u32, 0x00000009u32),
(0x000001c1u32, 0x00000009u32),
(0x000003ddu32, 0x0000000au32),
(0x000007ebu32, 0x0000000bu32),
(0x000003deu32, 0x0000000au32),
(0x000007ecu32, 0x0000000bu32),
(0x000007edu32, 0x0000000bu32),
(0x000007eeu32, 0x0000000bu32),
(0x0000000eu32, 0x00000006u32),
(0x0000000fu32, 0x00000006u32),
(0x000001c2u32, 0x00000009u32),
(0x000001c3u32, 0x00000009u32),
(0x000003dfu32, 0x0000000au32),
(0x000001c4u32, 0x00000009u32),
(0x000007efu32, 0x0000000bu32),
(0x000007f0u32, 0x0000000bu32),
(0x000001c5u32, 0x00000009u32),
(0x000001c6u32, 0x00000009u32),
(0x000003e0u32, 0x0000000au32),
(0x000003e1u32, 0x0000000au32),
(0x000001c7u32, 0x00000009u32),
(0x000003e2u32, 0x0000000au32),
(0x000007f1u32, 0x0000000bu32),
(0x000007f2u32, 0x0000000bu32),
(0x00000010u32, 0x00000006u32),
(0x000001c8u32, 0x00000009u32),
(0x000001c9u32, 0x00000009u32),
(0x000003e3u32, 0x0000000au32),
(0x000003e4u32, 0x0000000au32),
(0x000001cau32, 0x00000009u32),
(0x000001cbu32, 0x00000009u32),
(0x00000041u32, 0x00000007u32),
(0x000001ccu32, 0x00000009u32),
(0x000001cdu32, 0x00000009u32),
(0x000001ceu32, 0x00000009u32),
(0x000003e5u32, 0x0000000au32),
(0x000003e6u32, 0x0000000au32),
(0x000003e7u32, 0x0000000au32),
(0x000003e8u32, 0x0000000au32),
(0x000003e9u32, 0x0000000au32),
(0x00000042u32, 0x00000007u32),
(0x000000b9u32, 0x00000008u32),
(0x000007f3u32, 0x0000000bu32),
(0x000007f4u32, 0x0000000bu32),
(0x000003eau32, 0x0000000au32),
(0x000007f5u32, 0x0000000bu32),
(0x000001cfu32, 0x00000009u32),
(0x000001d0u32, 0x00000009u32),
(0x000000bau32, 0x00000008u32),
(0x000001d1u32, 0x00000009u32),
(0x000001d2u32, 0x00000009u32),
(0x000001d3u32, 0x00000009u32),
(0x000001d4u32, 0x00000009u32),
(0x000001d5u32, 0x00000009u32),
(0x000000bbu32, 0x00000008u32),
(0x00000043u32, 0x00000007u32),
(0x00000000u32, 0x00000004u32),
(0x00000004u32, 0x00000005u32),
(0x00000011u32, 0x00000006u32),
(0x00000012u32, 0x00000006u32),
(0x00000013u32, 0x00000006u32),
(0x00000044u32, 0x00000007u32),
(0x000000bcu32, 0x00000008u32),
(0x000000bdu32, 0x00000008u32),
(0x000000beu32, 0x00000008u32),
(0x000001d6u32, 0x00000009u32),
(0x000001d7u32, 0x00000009u32),
(0x000003ebu32, 0x0000000au32),
(0x000003ecu32, 0x0000000au32),
(0x000003edu32, 0x0000000au32),
(0x000003eeu32, 0x0000000au32),
(0x000007f6u32, 0x0000000bu32),
(0x000007f7u32, 0x0000000bu32),
(0x000007f8u32, 0x0000000bu32),
(0x000007f9u32, 0x0000000bu32),
(0x000007fau32, 0x0000000bu32),
(0x000007fbu32, 0x0000000bu32),
(0x00000ff8u32, 0x0000000cu32),
(0x00000ff9u32, 0x0000000cu32),
(0x00000ffau32, 0x0000000cu32),
(0x00000ffbu32, 0x0000000cu32),
(0x00001ffcu32, 0x0000000du32),
(0x00000ffcu32, 0x0000000cu32),
(0x00001ffdu32, 0x0000000du32),
(0x00000ffdu32, 0x0000000cu32),
(0x00001ffeu32, 0x0000000du32),
(0x00001fffu32, 0x0000000du32),
(0x000000bfu32, 0x00000008u32),
];
pub const POS: [(u32,u32); 32] = [
(0x00000000u32, 0x00000002u32),
(0x00000002u32, 0x00000003u32),
(0x00000006u32, 0x00000004u32),
(0x0000000eu32, 0x00000005u32),
(0x0000000fu32, 0x00000005u32),
(0x00000010u32, 0x00000005u32),
(0x00000011u32, 0x00000005u32),
(0x00000012u32, 0x00000005u32),
(0x00000013u32, 0x00000005u32),
(0x00000014u32, 0x00000005u32),
(0x0000002au32, 0x00000006u32),
(0x0000002bu32, 0x00000006u32),
(0x0000002cu32, 0x00000006u32),
(0x0000002du32, 0x00000006u32),
(0x0000002eu32, 0x00000006u32),
(0x0000002fu32, 0x00000006u32),
(0x00000030u32, 0x00000006u32),
(0x00000031u32, 0x00000006u32),
(0x00000032u32, 0x00000006u32),
(0x00000033u32, 0x00000006u32),
(0x00000034u32, 0x00000006u32),
(0x00000035u32, 0x00000006u32),
(0x00000036u32, 0x00000006u32),
(0x00000037u32, 0x00000006u32),
(0x00000038u32, 0x00000006u32),
(0x00000039u32, 0x00000006u32),
(0x0000003au32, 0x00000006u32),
(0x0000003bu32, 0x00000006u32),
(0x0000003cu32, 0x00000006u32),
(0x0000003du32, 0x00000006u32),
(0x0000003eu32, 0x00000006u32),
(0x0000003fu32, 0x00000006u32),
];
+63
View File
@@ -0,0 +1,63 @@
use crate::utils::common;
use binrw::BinRead;
pub static HEADER_KEY: [u8; 16] = [
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
];
pub static HEADER_IV: [u8; 16] = [0x00; 16];
pub static MTK_HEADER_MAGIC: &[u8; 8] = b"#DH@FiRm";
pub static MTK_RESERVED_MAGIC: &[u8; 16] = b"reserved mtk inc";
pub static MTK_META_MAGIC: &[u8; 4] = b"iMtK";
pub static MTK_META_PAD_MAGIC: &[u8; 4] = b"iPAd";
pub static CRYPTED_HEADER_SIZE: usize = 0x30;
pub static HEADER_SIZE: usize = 0x90;
pub static PHILIPS_EXTRA_HEADER_SIZE: usize = 0x80;
pub static _PHILIPS_FOOTER_SIGNATURE_SIZE: usize = 0x100;
#[derive(BinRead)]
pub struct Header {
pub vendor_magic_bytes: [u8; 4],
_mtk_magic: [u8; 8], //#DH@FiRm
version_bytes: [u8; 60],
pub file_size: u32,
_flags: u32,
product_name_bytes: [u8; 32],
}
impl Header {
pub fn vendor_magic(&self) -> String {
common::string_from_bytes(&self.vendor_magic_bytes)
}
pub fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
pub fn product_name(&self) -> String {
common::string_from_bytes(&self.product_name_bytes)
}
}
#[derive(BinRead)]
pub struct PartEntry {
name_bytes: [u8; 4],
pub flags: u32,
pub size: u32,
}
impl PartEntry {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
pub fn is_valid(&self) -> bool {
self.name().is_ascii()
}
pub fn is_encrypted(&self) -> bool {
(self.flags & 1 << 0) != 0
}
pub fn is_compressed(&self) -> bool { //lzhs fs
(self.flags & 1 << 8) != 0
}
}
+342
View File
@@ -0,0 +1,342 @@
use std::fs::{File, OpenOptions};
use std::io::{Write, Cursor, Seek, SeekFrom, Read};
use binrw::{BinRead, BinReaderExt};
use std::path::{PathBuf};
use super::huffman_tables::{CHARLEN, POS};
use crate::utils::compression::{decompress_lz4};
#[derive(BinRead)]
struct LzhsHeader {
uncompressed_size: u32,
compressed_size: u32,
checksum_or_seg_idx: u16, //as checksum in normal lzhs header, as index in lzhs_fs header
padding: [u8; 6],
}
#[derive(BinRead)]
struct LzhsOldSegmentHdr {
_uncompressed_size: u32,
_compressed_size: u32,
}
pub fn decompress_lzhs_fs_file2file(mut file: &File, output_file: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let file_size = file.metadata()?.len();
let mut out_file = OpenOptions::new().append(true).create(true).open(&output_file)?;
file.seek(SeekFrom::Start(0))?;
let mut uncompressed_heading = vec![0u8; 0x100000]; //first 1mb is uncompressed
file.read_exact(&mut uncompressed_heading)?;
out_file.write_all(&uncompressed_heading)?;
while file.stream_position().unwrap() < file_size {
let segment_header: LzhsHeader = file.read_le()?;
let lzhs_header: LzhsHeader = file.read_le()?;
//lz4 type uses a 4 byte checksum instead of 1(2) byte , so the padding is not 0 anymore
//maybe this method will be changed
let is_lz4 = if &lzhs_header.padding != b"\x00\x00\x00\x00\x00\x00" {true} else {false};
println!("[{}] Segment {} - Compressed size: {}, Decompressed size: {}, Expected Checksum: 0x{:02x?}",
if is_lz4 {"LZ4"} else {"LZHS"}, segment_header.checksum_or_seg_idx, lzhs_header.compressed_size, lzhs_header.uncompressed_size, lzhs_header.checksum_or_seg_idx);
let mut compressed_data = vec![0u8; lzhs_header.compressed_size as usize];
file.read_exact(&mut compressed_data)?;
let mut out_data;
if lzhs_header.compressed_size == 0 {
//odd variant no.1: if the compressed size is 0 , the output is just zeros with the uncompressed size.
out_data = vec![0; lzhs_header.uncompressed_size as usize];
} else if (lzhs_header.uncompressed_size > segment_header.uncompressed_size) &&
(lzhs_header.compressed_size == lzhs_header.uncompressed_size) &&
lzhs_header.checksum_or_seg_idx == 0x00
{
//odd variant no.2: the lzhs header compressed size is for some reason bigger by 8 bytes (and those 8 bytes are zeros), the data is stored UNCOMPRESSED in these
//the compressed and uncompressed size are the same and the checksum is 0.
out_data = compressed_data[..segment_header.uncompressed_size as usize].to_vec();
} else {
//normal variant
println!("- Decompressing...");
if is_lz4 {
out_data = decompress_lz4(&compressed_data, lzhs_header.uncompressed_size as i32)?;
} else {
//lzhs
let out_huff = unhuff(&compressed_data);
out_data = unlzss(&out_huff, lzhs_header.uncompressed_size as usize);
arm_thumb_convert(&mut out_data, 0, false);
let checksum = calc_checksum(&out_data);
println!("-- Calculated checksum: 0x{:02x?}", checksum);
if u16::from(checksum) != lzhs_header.checksum_or_seg_idx {
println!("--- Checksum mismatch! Expected: 0x{:02x?}, Got: 0x{:02x?}!", lzhs_header.checksum_or_seg_idx, checksum);
return Err("Checksum mismatch!".into());
} else {
println!("--- Checksum OK!")
}
}
}
out_file.write_all(&out_data)?;
//padded to 16 bytes
let pad_pos = (file.stream_position().unwrap() + 15) & !15;
file.seek(SeekFrom::Start(pad_pos))?;
}
Ok(())
}
// OLD VARIANT use in old mtk pkg
pub fn decompress_lzhs_fs_file2file_old(mut file: &File, output_file: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let file_size = file.metadata()?.len();
let mut out_file = OpenOptions::new().append(true).create(true).open(&output_file)?;
file.seek(SeekFrom::Start(0))?;
let mut uncompressed_heading = vec![0u8; 0x200]; //first 512b is uncompressed
file.read_exact(&mut uncompressed_heading)?;
out_file.write_all(&uncompressed_heading)?;
while file.stream_position().unwrap() < file_size {
let _segment_header: LzhsOldSegmentHdr = file.read_le()?;
let lzhs_header: LzhsHeader = file.read_le()?;
println!("[LZHS] Segment - Compressed size: {}, Decompressed size: {}, Expected Checksum: 0x{:02x?}",
lzhs_header.compressed_size, lzhs_header.uncompressed_size, lzhs_header.checksum_or_seg_idx);
let mut compressed_data = vec![0u8; lzhs_header.compressed_size as usize];
file.read_exact(&mut compressed_data)?;
let mut out_data;
let out_huff = unhuff(&compressed_data);
out_data = unlzss(&out_huff, lzhs_header.uncompressed_size as usize);
arm_thumb_convert(&mut out_data, 0, false);
let checksum = calc_checksum(&out_data);
println!("-- Calculated checksum: 0x{:02x?}", checksum);
if u16::from(checksum) != lzhs_header.checksum_or_seg_idx {
println!("--- Checksum mismatch! Expected: 0x{:02x?}, Got: 0x{:02x?}!", lzhs_header.checksum_or_seg_idx, checksum);
return Err("Checksum mismatch!".into());
} else {
println!("--- Checksum OK!")
}
out_file.write_all(&out_data)?;
}
Ok(())
}
#[derive(Debug)]
struct HuffmanCtx {
c: u8,
code: u32,
len: u32,
bitno: u8,
code_buf: [u8; 32],
code_buf_ptr: usize,
mask: u8,
}
fn get_byte<Cursor: Read>(ctx: &mut HuffmanCtx, input: &mut Cursor) -> bool {
if ctx.bitno > 7 {
ctx.bitno = 0;
let mut buf = [0u8; 1];
if input.read_exact(&mut buf).is_err() {
return false;
}
ctx.c = buf[0];
}
let bit = ((ctx.c >> (7 - ctx.bitno)) & 1) as u32;
ctx.code = (ctx.code << 1) | bit;
ctx.len += 1;
ctx.bitno += 1;
true
}
fn unhuff(data: &[u8]) -> Vec<u8> {
let charlen = &CHARLEN;
let pos = &POS;
let mut ctx = HuffmanCtx {
c: 0,
code: 0,
len: 0,
bitno: 8,
code_buf: [0u8; 32],
code_buf_ptr: 1,
mask: 1,
};
let mut lookup_charlen = vec![-1i16; 131072];
let mut lookup_charpos = vec![-1i16; 512];
let mut in_cur = Cursor::new(data);
let mut out: Vec<u8> = Vec::with_capacity(data.len());
loop {
if !get_byte(&mut ctx, &mut in_cur) { /*println!("break1");*/break; }
if ctx.len < 4 { continue; }
let key = (((ctx.len & 0xF) as usize) << 13) | ((ctx.code & 0x1FFF) as usize);
let mut idx = lookup_charlen[key];
if idx == -2 { continue; }
if idx == -1 {
let mut found = false;
for (i, &(code, len)) in charlen.iter().enumerate() {
if len == ctx.len && code == ctx.code {
lookup_charlen[key] = i as i16;
idx = i as i16; found = true; break;
}
}
if !found { lookup_charlen[key] = -2; continue; }
}
let i = idx as i32;
if i > 255 {
let val = (i - 256) as u8;
if ctx.code_buf_ptr < ctx.code_buf.len() { ctx.code_buf[ctx.code_buf_ptr] = val; ctx.code_buf_ptr += 1; }
ctx.code = 0; ctx.len = 0;
let found_j: i32;
loop {
if !get_byte(&mut ctx, &mut in_cur) {
//println!("retA");
//flush
if ctx.code_buf_ptr > 1 { for j in 0..ctx.code_buf_ptr { out.push(ctx.code_buf[j]); } };
return out;
}
if ctx.len < 2 { continue; }
let keyp = (((ctx.len & 0x7) as usize) << 6) | ((ctx.code & 0x3F) as usize);
let mut jdx = lookup_charpos[keyp];
if jdx == -2 { continue; }
if jdx == -1 {
let mut found = false;
for (j, &(code, len)) in pos.iter().enumerate() {
if len == ctx.len && code == ctx.code {
lookup_charpos[keyp] = j as i16; jdx = j as i16; found = true; break;
}
}
if !found { lookup_charpos[keyp] = -2; continue; }
}
found_j = jdx as i32;
let b = ((jdx as i32) >> 1) as u8;
if ctx.code_buf_ptr < ctx.code_buf.len() { ctx.code_buf[ctx.code_buf_ptr] = b; ctx.code_buf_ptr += 1; }
break;
}
ctx.code = 0;
for _ in 0..7 { if !get_byte(&mut ctx, &mut in_cur) {
//println!("retB");
//flush
if ctx.code_buf_ptr > 1 { for j in 0..ctx.code_buf_ptr { out.push(ctx.code_buf[j]); } };
return out;
}
}
let combined = (ctx.code | ((found_j as u32) << 7)) as u32;
if ctx.code_buf_ptr < ctx.code_buf.len() { ctx.code_buf[ctx.code_buf_ptr] = (combined & 0xFF) as u8; ctx.code_buf_ptr += 1; }
ctx.code = 0; ctx.len = 0;
} else {
ctx.code_buf[0] |= ctx.mask;
if ctx.code_buf_ptr < ctx.code_buf.len() { ctx.code_buf[ctx.code_buf_ptr] = i as u8; ctx.code_buf_ptr += 1; }
ctx.code = 0; ctx.len = 0;
}
ctx.mask = ctx.mask.wrapping_shl(1);
if ctx.mask == 0 {
for j in 0..ctx.code_buf_ptr { out.push(ctx.code_buf[j]); }
ctx.code_buf[0] = 0; ctx.code_buf_ptr = 1; ctx.mask = 1;
}
}
if ctx.code_buf_ptr > 1 { for j in 0..ctx.code_buf_ptr { out.push(ctx.code_buf[j]); } }
out
}
fn unlzss(data: &[u8], expected_size: usize) -> Vec<u8> {
let mut window = [0u8; 0x1000];
let mut dst = Vec::with_capacity(expected_size);
let mut src_i = 0;
let mut win_pos = 0usize;
let mut flags = 0u32;
while dst.len() < expected_size {
flags >>= 1;
if (flags & 0x100) == 0 {
if src_i >= data.len() {
break;
}
flags = data[src_i] as u32 | 0xFF00;
src_i += 1;
}
if (flags & 1) != 0 {
// literal
if src_i >= data.len() {
break;
}
let c = data[src_i];
src_i += 1;
dst.push(c);
window[win_pos] = c;
win_pos = (win_pos + 1) & 0xFFF;
} else {
// backreference
if src_i + 2 >= data.len() {
break;
}
let j = data[src_i] as usize;
let off = ((data[src_i + 1] as usize) << 8) | data[src_i + 2] as usize;
src_i += 3;
let count = j + 3;
for _ in 0..count {
if dst.len() >= expected_size {
break;
}
let c = window[(win_pos.wrapping_sub(off)) & 0xFFF];
dst.push(c);
window[win_pos] = c;
win_pos = (win_pos + 1) & 0xFFF;
}
}
}
dst
}
fn arm_thumb_convert(data: &mut [u8], now_pos: u32, encoding: bool) {
let size = data.len() as u32;
let mut i: u32 = 0;
while i + 4 <= size {
let idx = i as usize;
if (data[idx + 1] & 0xF8) == 0xF0 && (data[idx + 3] & 0xF8) == 0xF8 {
let mut src: u32 =
((data[idx + 1] as u32 & 0x7) << 19) |
((data[idx + 0] as u32) << 11) |
((data[idx + 3] as u32 & 0x7) << 8) |
(data[idx + 2] as u32);
src <<= 1;
let dest = if encoding {
now_pos + i + 4 + src
} else {
src.wrapping_sub(now_pos + i + 4)
} >> 1;
data[idx + 1] = 0xF0 | ((dest >> 19) & 0x7) as u8;
data[idx + 0] = ((dest >> 11) & 0xFF) as u8;
data[idx + 3] = 0xF8 | ((dest >> 8) & 0x7) as u8;
data[idx + 2] = (dest & 0xFF) as u8;
i += 2;
}
i += 2;
}
}
fn calc_checksum(data: &[u8]) -> u8 {
let mut checksum: u8 = 0;
for &bt in data {
checksum = checksum.wrapping_add(bt);
}
checksum
}
@@ -1,86 +1,25 @@
pub mod include;
pub mod lzhs;
mod huffman_tables;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "mtk_pkg", detector_func: is_mtk_pkg_file, extractor_func: extract_mtk_pkg }
}
use crate::AppContext;
use std::path::Path;
use std::fs::{self, OpenOptions};
use std::io::{Write, Cursor, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
use crate::utils::lzhs::{decompress_lzhs_fs_file2file};
use crate::keys;
use lzhs::{decompress_lzhs_fs_file2file};
use include::*;
pub struct MtkPkgContext {
is_philips_variant: bool,
decrypted_header: Vec<u8>,
}
#[derive(BinRead)]
struct Header {
#[br(count = 4)] vendor_magic_bytes: Vec<u8>,
#[br(count = 8)] _mtk_magic: Vec<u8>, //#DH@FiRm
#[br(count = 60)] version_bytes: Vec<u8>,
file_size: u32,
_flags: u32,
#[br(count = 32)] product_name_bytes: Vec<u8>,
#[br(count = 32)] _digest: Vec<u8>,
}
impl Header {
fn vendor_magic(&self) -> String {
common::string_from_bytes(&self.vendor_magic_bytes)
}
fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
fn product_name(&self) -> String {
common::string_from_bytes(&self.product_name_bytes)
}
}
#[derive(BinRead)]
struct PartEntry {
#[br(count = 4)] name_bytes: Vec<u8>,
flags: u32,
size: u32,
}
impl PartEntry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
fn is_valid(&self) -> bool {
self.name().is_ascii()
}
fn is_encrypted(&self) -> bool {
(self.flags & 1 << 0) != 0
}
fn is_compressed(&self) -> bool { //lzhs fs
(self.flags & 1 << 8) != 0
}
}
pub static MTK_HEADER_MAGIC: &[u8; 8] = b"#DH@FiRm";
pub static MTK_RESERVED_MAGIC: &[u8; 16] = b"reserved mtk inc";
pub static MTK_META_MAGIC: &[u8; 4] = b"iMtK";
pub static MTK_META_PAD_MAGIC: &[u8; 4] = b"iPAd";
pub static CRYPTED_HEADER_SIZE: usize = 0x30;
static HEADER_SIZE: usize = 0x90;
static PHILIPS_EXTRA_HEADER_SIZE: usize = 0x80;
static _PHILIPS_FOOTER_SIGNATURE_SIZE: usize = 0x100;
static HEADER_KEY: [u8; 16] = [
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
];
static HEADER_IV: [u8; 16] = [0x00; 16];
pub fn is_mtk_pkg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+1
View File
@@ -0,0 +1 @@
pub static HEADER_SIZE: usize = 0x170;
@@ -1,20 +1,18 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "mtk_pkg_new", detector_func: is_mtk_pkg_new_file, extractor_func: extract_mtk_pkg_new }
}
use crate::AppContext;
use std::path::Path;
use std::fs::{self, OpenOptions};
use std::io::{Write, Cursor, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
use crate::utils::lzhs::{decompress_lzhs_fs_file2file};
use crate::keys;
use crate::formats::mtk_pkg::{MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
use crate::formats::mtk_pkg::lzhs::{decompress_lzhs_fs_file2file};
use crate::formats::mtk_pkg::include::{Header, PartEntry, MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
use include::*;
pub struct MtkPkgNewContext {
matching_key_name: String,
@@ -23,53 +21,6 @@ pub struct MtkPkgNewContext {
decrypted_header: Vec<u8>,
}
#[derive(BinRead)]
struct Header {
#[br(count = 4)] vendor_magic_bytes: Vec<u8>,
#[br(count = 8)] _mtk_magic: Vec<u8>, //#DH@FiRm
#[br(count = 56)] version_bytes: Vec<u8>,
_unk: u32,
file_size: u32,
_flags: u32,
#[br(count = 32)] product_name_bytes: Vec<u8>,
#[br(count = 256)] _digest: Vec<u8>,
}
impl Header {
fn vendor_magic(&self) -> String {
common::string_from_bytes(&self.vendor_magic_bytes)
}
fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
fn product_name(&self) -> String {
common::string_from_bytes(&self.product_name_bytes)
}
}
#[derive(BinRead)]
struct PartEntry {
#[br(count = 4)] name_bytes: Vec<u8>,
flags: u32,
size: u32,
}
impl PartEntry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
fn is_valid(&self) -> bool {
self.name().is_ascii()
}
fn is_encrypted(&self) -> bool {
(self.flags & 1 << 0) != 0
}
fn is_compressed(&self) -> bool { //lzhs fs
(self.flags & 1 << 8) != 0
}
}
static HEADER_SIZE: usize = 0x170;
pub fn is_mtk_pkg_new_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+33
View File
@@ -0,0 +1,33 @@
use crate::utils::common;
use binrw::BinRead;
pub static KEY: u32 = 0x94102909; //09 29 10 94
// first 4 bytes of header and content are additionally XORed, they have different masks although only differ by half a byte
pub static HEADER_XOR_MASK: u32 = 0x04BE7C75; //75 7C BE 04
pub static CONTENT_XOR_MASK: u32 = 0x04BE7C72; //72 7C BE 04
pub static HEADER_SIZE: usize = 0x98;
#[derive(BinRead)]
pub struct Header {
pub vendor_magic_bytes: [u8; 4],
_mtk_magic: [u8; 8],
version_bytes: [u8; 68],
pub file_size: u32,
_flags: u32,
product_name_bytes: [u8; 32],
_digest: [u8; 32],
}
impl Header {
pub fn vendor_magic(&self) -> String {
common::string_from_bytes(&self.vendor_magic_bytes)
}
pub fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
pub fn product_name(&self) -> String {
common::string_from_bytes(&self.product_name_bytes)
}
}
@@ -1,67 +1,18 @@
mod include;
mod mtk_crypto;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "mtk_pkg_old", detector_func: is_mtk_pkg_old_file, extractor_func: extract_mtk_pkg_old }
}
use crate::AppContext;
use std::path::Path;
use std::fs::{self, OpenOptions};
use std::io::{Write, Cursor, Seek};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::utils::mtk_crypto::{decrypt};
use crate::utils::lzhs::{decompress_lzhs_fs_file2file_old};
static KEY: u32 = 0x94102909; //09 29 10 94
// first 4 bytes of header and content are additionally XORed, they have different masks although only differ by half a byte
static HEADER_XOR_MASK: u32 = 0x04BE7C75; //75 7C BE 04
static CONTENT_XOR_MASK: u32 = 0x04BE7C72; //72 7C BE 04
use crate::formats::mtk_pkg::{MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
#[derive(BinRead)]
struct Header {
#[br(count = 4)] vendor_magic_bytes: Vec<u8>,
#[br(count = 8)] _mtk_magic: Vec<u8>, //#DH@FiRm
#[br(count = 68)] version_bytes: Vec<u8>,
file_size: u32,
_flags: u32,
#[br(count = 32)] product_name_bytes: Vec<u8>,
#[br(count = 32)] _digest: Vec<u8>,
}
impl Header {
fn vendor_magic(&self) -> String {
common::string_from_bytes(&self.vendor_magic_bytes)
}
fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
fn product_name(&self) -> String {
common::string_from_bytes(&self.product_name_bytes)
}
}
#[derive(BinRead)]
struct PartEntry {
#[br(count = 4)] name_bytes: Vec<u8>,
flags: u32,
size: u32,
}
impl PartEntry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
fn is_encrypted(&self) -> bool {
(self.flags & 1 << 0) == 1 << 0
}
fn is_compressed(&self) -> bool { //lzhs fs
(self.flags & 1 << 8) != 0
}
}
static HEADER_SIZE: usize = 0x98;
use crate::formats::mtk_pkg::lzhs::{decompress_lzhs_fs_file2file_old};
use crate::formats::mtk_pkg::include::{PartEntry, MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
use mtk_crypto::{decrypt};
use include::*;
pub fn is_mtk_pkg_old_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let mut file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+138
View File
@@ -0,0 +1,138 @@
//dword_78A9B0
const P_TABLE_1: [u8; 24] = [
0x00, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0C, 0x0B,
0x0A, 0x09, 0x08, 0x07, 0x08, 0x07, 0x06, 0x05,
0x04, 0x03, 0x04, 0x03, 0x02, 0x01, 0x00, 0x0F,
];
//dword_78AA10
const S_BOX: [u8; 256] = [
0x0D, 0x06, 0x00, 0x0A, 0x0E, 0x03, 0x0B, 0x05, 0x07, 0x01, 0x09, 0x04, 0x02, 0x08, 0x0C, 0x0F,
0x00, 0x05, 0x0A, 0x03, 0x07, 0x09, 0x0C, 0x0F, 0x0B, 0x02, 0x06, 0x0D, 0x08, 0x0E, 0x01, 0x04,
0x08, 0x03, 0x05, 0x09, 0x0B, 0x0C, 0x06, 0x0A, 0x01, 0x0D, 0x02, 0x0E, 0x04, 0x07, 0x0F, 0x00,
0x07, 0x00, 0x09, 0x05, 0x0C, 0x06, 0x0A, 0x03, 0x08, 0x0B, 0x0F, 0x02, 0x01, 0x0D, 0x04, 0x0E,
0x09, 0x0E, 0x05, 0x00, 0x0C, 0x07, 0x06, 0x0B, 0x02, 0x04, 0x0F, 0x03, 0x01, 0x0A, 0x08, 0x0D,
0x0F, 0x02, 0x03, 0x09, 0x06, 0x0C, 0x08, 0x05, 0x01, 0x0D, 0x04, 0x0A, 0x0B, 0x07, 0x0E, 0x00,
0x05, 0x0B, 0x09, 0x06, 0x0A, 0x01, 0x00, 0x0C, 0x0E, 0x08, 0x02, 0x0F, 0x07, 0x04, 0x0D, 0x03,
0x0A, 0x05, 0x00, 0x0C, 0x0D, 0x02, 0x07, 0x09, 0x04, 0x03, 0x0B, 0x06, 0x0E, 0x08, 0x01, 0x0F,
0x0C, 0x02, 0x05, 0x0B, 0x03, 0x0E, 0x0F, 0x04, 0x07, 0x08, 0x09, 0x06, 0x00, 0x0D, 0x0A, 0x01,
0x07, 0x0E, 0x0A, 0x05, 0x0C, 0x02, 0x01, 0x0B, 0x00, 0x03, 0x0F, 0x08, 0x09, 0x04, 0x06, 0x0D,
0x01, 0x0F, 0x0B, 0x0C, 0x0E, 0x05, 0x08, 0x02, 0x0A, 0x06, 0x04, 0x03, 0x09, 0x00, 0x07, 0x0D,
0x08, 0x02, 0x04, 0x0B, 0x07, 0x0C, 0x0D, 0x01, 0x05, 0x0F, 0x03, 0x06, 0x0E, 0x09, 0x00, 0x0A,
0x0E, 0x02, 0x07, 0x0C, 0x0B, 0x05, 0x04, 0x09, 0x08, 0x0D, 0x01, 0x0A, 0x06, 0x00, 0x0F, 0x03,
0x04, 0x08, 0x02, 0x05, 0x0E, 0x03, 0x01, 0x0F, 0x0D, 0x07, 0x0B, 0x0C, 0x00, 0x09, 0x06, 0x0A,
0x09, 0x0E, 0x0A, 0x01, 0x0C, 0x02, 0x07, 0x04, 0x03, 0x00, 0x0F, 0x06, 0x05, 0x0B, 0x08, 0x0D,
0x0F, 0x04, 0x0C, 0x0B, 0x05, 0x08, 0x02, 0x01, 0x0A, 0x09, 0x06, 0x00, 0x03, 0x0E, 0x0D, 0x07,
];
//dword_78AE10
const P_TABLE_2: [u8; 16] = [
0x0C, 0x08, 0x05, 0x00, 0x0A, 0x02, 0x0E, 0x07,
0x04, 0x09, 0x01, 0x0D, 0x03, 0x06, 0x0B, 0x0F,
];
//sub_379C90
fn permute_24bit(input: u16, table: &[u8]) -> u32 {
let mut output = 0u32;
for (i, &bit_pos) in table.iter().enumerate().take(24) {
if bit_pos < 32 && ((input >> bit_pos) & 1) != 0 {
output |= 1 << i;
}
}
output
}
//sub_379CB8
fn sbox_substitute(input: u32) -> u16 {
let mut output = 0u16;
for i in 0..4 {
let bits6 = ((input >> (i * 6)) & 0x3F) as usize;
let row_offset = 64 * i;
let index = row_offset + bits6;
let sbox_value = (S_BOX[index] & 0x0F) as u16;
output |= sbox_value << (4 * i);
}
output
}
//sub_379CE6
fn permute_16bit(input: u16, table: &[u8]) -> u16 {
let mut output = 0u16;
for (i, &target_bit) in table.iter().enumerate().take(16) {
if ((1 << i) & input) != 0 {
output |= 1 << target_bit;
}
}
output
}
//sub_379D0E
fn round_function(input16: u16, key16: u16) -> u16 {
let perm_input = permute_24bit(input16, &P_TABLE_1);
let perm_key = permute_24bit(key16, &P_TABLE_1);
let xor_result = perm_input ^ perm_key;
let sbox_output = sbox_substitute(xor_result);
permute_16bit(sbox_output, &P_TABLE_2)
}
//sub_379DE8
fn decrypt_block(encrypted32: u32, key_upper: u16, key_lower: u16, prev_block: u32) -> u32 {
let lower_half = (encrypted32 & 0xFFFF) as u16;
let upper_half = ((encrypted32 >> 16) & 0xFFFF) as u16;
//round 1
let mut temp1 = round_function(lower_half, key_upper);
temp1 ^= upper_half;
//round 2
let mut temp2 = round_function(temp1, key_lower);
temp2 ^= lower_half;
let decrypted = ((temp2 as u32) << 16) | (temp1 as u32);
decrypted ^ prev_block
}
pub fn decrypt(data: &[u8], key: u32, xor_mask: Option<u32>) -> Vec<u8> {
let key_upper = ((key >> 16) & 0xFFFF) as u16;
let key_lower = (key & 0xFFFF) as u16;
let mut decrypted = Vec::with_capacity(data.len());
let mut prev_block = 0u32; //initial iv = 0
//calculate how many complete 4 byte blocks
let aligned_len = (data.len() / 4) * 4;
for chunk in data[..aligned_len].chunks_exact(4) {
let encrypted32 = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
let decrypted32 = decrypt_block(encrypted32, key_upper, key_lower, prev_block);
decrypted.extend_from_slice(&decrypted32.to_le_bytes());
prev_block = encrypted32;
}
//the remaining bytes that are not 4 byte blocks are just XORed with 0x3D
for &byte in &data[aligned_len..] {
decrypted.push(byte ^ 0x3D);
}
//apply XOR to the first 4 bytes if a mask is provided
if let Some(mask) = xor_mask {
if decrypted.len() >= 4 {
let first_dword = u32::from_le_bytes([
decrypted[0],
decrypted[1],
decrypted[2],
decrypted[3],
]);
let xored = first_dword ^ mask;
decrypted[0..4].copy_from_slice(&xored.to_le_bytes());
}
}
decrypted
}
+30
View File
@@ -0,0 +1,30 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(BinRead)]
pub struct Header {
_magic_bytes: [u8; 4],
pub version_major: u32,
pub version_minor: u32,
_unused: u32,
firmware_name_bytes: [u8; 16],
pub data_size: u32,
_md5_checksum: [u8; 16], //data checksum
pub part_count: u32,
_data_start_offset: u32,
_signature: [u8; 128],
_header_checksum: u32, //CRC32, calculated with the field set to 0
}
impl Header {
pub fn firmware_name(&self) -> String {
common::string_from_bytes(&self.firmware_name_bytes)
}
}
#[derive(BinRead)]
pub struct PartEntry {
pub id: u32,
pub size: u32,
pub offset: u32,
_md5_checksum: [u8; 16],
}
@@ -1,44 +1,14 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "novatek", detector_func: is_novatek_file, extractor_func: extract_novatek }
}
use crate::AppContext;
use std::path::Path;
use std::fs::{self, OpenOptions};
use std::io::{Write};
use binrw::{BinRead, BinReaderExt};
use std::io::Write;
use binrw::BinReaderExt;
use crate::utils::common;
#[derive(BinRead)]
struct Header {
#[br(count = 4)] _magic_bytes: Vec<u8>,
version_major: u32,
version_minor: u32,
_unused: u32,
#[br(count = 16)] firmware_name_bytes: Vec<u8>,
data_size: u32,
#[br(count = 16)] _md5_checksum: Vec<u8>, //data checksum
part_count: u32,
_data_start_offset: u32,
#[br(count = 128)] _signature: Vec<u8>,
_header_checksum: u32, //CRC32, calculated with the field set to 0
}
impl Header {
fn firmware_name(&self) -> String {
common::string_from_bytes(&self.firmware_name_bytes)
}
}
#[derive(BinRead)]
struct PartEntry {
id: u32,
size: u32,
offset: u32,
#[br(count = 16)] _md5_checksum: Vec<u8>,
}
use include::*;
pub fn is_novatek_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+38
View File
@@ -0,0 +1,38 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(Debug, BinRead)]
pub struct TIMG {
_magic_bytes: [u8; 4], //TIMG
_unused1: u32,
pub data_size: u32,
_unused2: u32,
_md5_checksum: [u8; 16],
_signature: [u8; 256],
}
#[derive(Debug, BinRead)]
pub struct PIMG {
pub magic_bytes: [u8; 4], //PIMG
_unused1: u32,
pub size: u32,
_unused2: u32,
_md5_checksum: [u8; 16],
name_bytes: [u8; 16],
dest_dev_bytes: [u8; 64],
comp_type_bytes: [u8; 16],
_unknown1: u32,
_comment: [u8; 1024],
_unknown2: u32,
}
impl PIMG {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
pub fn dest_dev(&self) -> String {
common::string_from_bytes(&self.dest_dev_bytes)
}
pub fn comp_type(&self) -> String {
common::string_from_bytes(&self.comp_type_bytes)
}
}
@@ -1,53 +1,16 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "nvt_timg", detector_func: is_nvt_timg_file, extractor_func: extract_nvt_timg }
}
use crate::AppContext;
use std::path::{Path};
use std::path::Path;
use std::io::{Seek, Write};
use std::fs::{self, OpenOptions};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::utils::compression::{decompress_gzip};
use crate::utils::sparse::{unsparse_to_file};
#[derive(Debug, BinRead)]
struct TIMG {
#[br(count = 4)] _magic_bytes: Vec<u8>, //TIMG
_unused1: u32,
data_size: u32,
_unused2: u32,
#[br(count = 16)] _md5_checksum: Vec<u8>,
#[br(count = 256)] _signature: Vec<u8>,
}
#[derive(Debug, BinRead)]
struct PIMG {
#[br(count = 4)] magic_bytes: Vec<u8>, //PIMG
_unused1: u32,
size: u32,
_unused2: u32,
#[br(count = 16)] _md5_checksum: Vec<u8>,
#[br(count = 16)] name_bytes: Vec<u8>,
#[br(count = 64)] dest_dev_bytes: Vec<u8>,
#[br(count = 16)] comp_type_bytes: Vec<u8>,
_unknown1: u32,
#[br(count = 1024)] _comment: Vec<u8>,
_unknown2: u32,
}
impl PIMG {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
fn dest_dev(&self) -> String {
common::string_from_bytes(&self.dest_dev_bytes)
}
fn comp_type(&self) -> String {
common::string_from_bytes(&self.comp_type_bytes)
}
}
use include::*;
pub fn is_nvt_timg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -71,7 +34,7 @@ pub fn extract_nvt_timg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
while file.stream_position()? < file_size as u64 {
pimg_i += 1;
let pimg: PIMG = file.read_le()?;
if pimg.magic_bytes != b"PIMG" {
if &pimg.magic_bytes != b"PIMG" {
println!("Invalid PIMG magic!");
return Ok(());
}
+132
View File
@@ -0,0 +1,132 @@
use crate::utils::common;
use binrw::BinRead;
use super::pana_dvd_crypto::{decrypt_data};
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
pub fn find_key<'a>(key_array: &'a [&'a str], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result<Option<[u8; 8]>, Box<dyn std::error::Error>> {
for key_hex in key_array {
let key_bytes = hex::decode(key_hex)?;
let key_array: [u8; 8] = key_bytes.as_slice().try_into()?;
let decrypted = decrypt_data(data, &key_array);
if decrypted[magic_offset..].starts_with(expected_magic) {
return Ok(Some(key_array));
}
}
Ok(None)
}
pub fn find_aes_key_pair<'a>(key_array: &'a [(&'a str, &'a str, &'a str)], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result<Option<([u8; 16], [u8; 16], [u8; 8])>, Box<dyn std::error::Error>> {
for (aes_key_hex, aes_iv_hex, cust_key_hex) in key_array {
let aes_key: [u8; 16] = hex::decode(aes_key_hex)?.as_slice().try_into()?;
let aes_iv: [u8; 16] = hex::decode(aes_iv_hex)?.as_slice().try_into()?;
let aes_decrypted = decrypt_aes128_cbc_nopad(data, &aes_key, &aes_iv)?;
let key_bytes = hex::decode(cust_key_hex)?;
let key_array: [u8; 8] = key_bytes.as_slice().try_into()?;
let decrypted = decrypt_data(&aes_decrypted, &key_array);
if decrypted[magic_offset..].starts_with(expected_magic) {
return Ok(Some((aes_key, aes_iv, key_array)));
}
}
Ok(None)
}
pub static MAX_HEADER_SIZE: usize = 0x2000;
#[derive(BinRead)]
pub struct AesHeaderFileEntry {
pub offset: u32,
pub size: u32,
}
pub struct FileEntry {
pub offset: u32,
pub base_offset: u32,
}
//checksums are mostly Adler32, but some very old files use Checksum32 instead.
#[derive(BinRead)]
pub struct ModuleEntry {
#[br(count = 4)] pub name_bytes: Vec<u8>,
version_bytes: [u8; 4],
_unk: u32,
pub offset: u32,
platform_bytes: [u8; 8],
_unk1: u16,
id_bytes: [u8; 6],
pub size: u32,
pub data_checksum: u32, //checksum of the entrys' DATA
_unk2: u32,
_entry_checksum: u32, //checksum of THIS header entry (all previous 44 bytes)
}
impl ModuleEntry {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
pub fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
pub fn platform(&self) -> String {
common::string_from_bytes(&self.platform_bytes)
}
pub fn id(&self) -> String {
common::string_from_bytes(&self.id_bytes)
}
pub fn is_valid(&self) -> bool {
self.name().is_ascii() && self.platform().is_ascii()
}
}
#[derive(BinRead)]
pub struct MainListHeader {
_checksum: u32, //checksum of the MAIN LIST
_unk: u32, //seems to be always 1?
pub list_size: u32,
pub decompressed_part_size: u32,
_unk2: u32,
}
impl MainListHeader {
pub fn entry_count(&self) -> u32 {
(&self.list_size - 20) / 8
}
}
#[derive(BinRead)]
pub struct MainListEntry {
pub size: u32,
pub checksum: u32, //checksum of this MAIN entrys' data
}
pub const COMPRESSED_FILE_MAGIC: &[u8; 8] = b"EXTRHEAD";
#[derive(BinRead)]
pub struct CompressedFileHeader {
_header_string: [u8; 14], //EXTRHEADDRV \x01\x00
pub compression_type_byte: u16,
pub decompressed_size: u32,
_destination_address: u32,
pub compressed_size: u32,
_unk: u32,
_footer_offset: u32,
_base_address: u32,
_checksum: u32, //unknown type of checksum
_checksum_flag: u8,
_unused: [u8; 19],
}
impl CompressedFileHeader {
pub fn compression_type(&self) -> &str {
if self.compression_type_byte == 0 {
return "Uncompressed"
} else if self.compression_type_byte == 1 {
return "GZIP"
} else if self.compression_type_byte == 2 {
return "LZSS"
} else {
return "Unknown"
}
}
}
+51
View File
@@ -0,0 +1,51 @@
pub fn decompress_lzss(data: &[u8]) -> Vec<u8> {
let mut window = [0u8; 0x1000];
let mut dst = Vec::new();
let mut src_i = 0;
let mut win_pos = 0xFEE;
let mut flags = 0u16;
while src_i < data.len() {
flags >>= 1;
if (flags & 0x100) == 0 {
if src_i >= data.len() {
break;
}
flags = data[src_i] as u16 | 0xFF00;
src_i += 1;
}
if (flags & 1) == 0 {
// Backreference
if src_i + 1 >= data.len() {
break;
}
let b1 = data[src_i];
let b2 = data[src_i + 1];
src_i += 2;
let mut offset = (b1 as usize) | (((b2 & 0xF0) as usize) << 4);
let length = ((b2 & 0x0F) as usize) + 3;
for _ in 0..length {
let c = window[offset];
dst.push(c);
window[win_pos] = c;
win_pos = (win_pos + 1) & 0xFFF;
offset = (offset + 1) & 0xFFF;
}
} else {
// Literal
if src_i >= data.len() {
break;
}
let c = data[src_i];
src_i += 1;
dst.push(c);
window[win_pos] = c;
win_pos = (win_pos + 1) & 0xFFF;
}
}
dst
}
@@ -1,20 +1,21 @@
mod include;
mod pana_dvd_crypto;
mod lzss;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "pana_dvd", detector_func: is_pana_dvd_file, extractor_func: extract_pana_dvd }
}
use crate::AppContext;
use std::path::{Path, PathBuf};
use std::fs::{self, OpenOptions};
use std::io::{Write, Read, Cursor, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::keys;
use crate::utils::common;
use crate::utils::pana_dvd_crypto::{decrypt_data};
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
use crate::utils::compression::{decompress_gzip};
use crate::utils::lzss::{decompress_lzss};
use pana_dvd_crypto::{decrypt_data};
use lzss::{decompress_lzss};
use include::*;
pub struct PanaDvdContext {
matching_key: [u8; 8],
@@ -24,133 +25,6 @@ pub struct PanaDvdContext {
aes_iv: Option<[u8; 16]>
}
#[derive(BinRead)]
struct AesHeaderFileEntry {
offset: u32,
size: u32,
}
struct FileEntry {
offset: u32,
base_offset: u32,
}
//checksums are mostly Adler32, but some very old files use Checksum32 instead.
#[derive(BinRead)]
struct ModuleEntry {
#[br(count = 4)] name_bytes: Vec<u8>,
#[br(count = 4)] version_bytes: Vec<u8>,
_unk: u32,
offset: u32,
#[br(count = 8)] platform_bytes: Vec<u8>,
_unk1: u16,
#[br(count = 6)] id_bytes: Vec<u8>,
size: u32,
data_checksum: u32, //checksum of the entrys' DATA
_unk2: u32,
_entry_checksum: u32, //checksum of THIS header entry (all previous 44 bytes)
}
impl ModuleEntry {
fn name(&self) -> String {
common::string_from_bytes(&self.name_bytes)
}
fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
fn platform(&self) -> String {
common::string_from_bytes(&self.platform_bytes)
}
fn id(&self) -> String {
common::string_from_bytes(&self.id_bytes)
}
fn is_valid(&self) -> bool {
self.name().is_ascii() && self.platform().is_ascii()
}
}
#[derive(BinRead)]
struct MainListHeader {
_checksum: u32, //checksum of the MAIN LIST
_unk: u32, //seems to be always 1?
list_size: u32,
decompressed_part_size: u32,
_unk2: u32,
}
impl MainListHeader {
fn entry_count(&self) -> u32 {
(&self.list_size - 20) / 8
}
}
#[derive(BinRead)]
struct MainListEntry {
size: u32,
checksum: u32, //checksum of this MAIN entrys' data
}
const COMPRESSED_FILE_MAGIC: &[u8; 8] = b"EXTRHEAD";
#[derive(BinRead)]
struct CompressedFileHeader {
#[br(count = 14)] _header_string: Vec<u8>, //EXTRHEADDRV \x01\x00
compression_type_byte: u16,
decompressed_size: u32,
_destination_address: u32,
compressed_size: u32,
_unk: u32,
_footer_offset: u32,
_base_address: u32,
_checksum: u32, //unknown type of checksum
_checksum_flag: u8,
#[br(count = 19)] _unused: Vec<u8>,
}
impl CompressedFileHeader {
fn compression_type(&self) -> &str {
if self.compression_type_byte == 0 {
return "Uncompressed"
} else if self.compression_type_byte == 1 {
return "GZIP"
} else if self.compression_type_byte == 2 {
return "LZSS"
} else {
return "Unknown"
}
}
}
static MAX_HEADER_SIZE: usize = 0x2000;
pub fn find_key<'a>(key_array: &'a [&'a str], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result<Option<[u8; 8]>, Box<dyn std::error::Error>> {
for key_hex in key_array {
let key_bytes = hex::decode(key_hex)?;
let key_array: [u8; 8] = key_bytes.as_slice().try_into()?;
let decrypted = decrypt_data(data, &key_array);
if decrypted[magic_offset..].starts_with(expected_magic) {
return Ok(Some(key_array));
}
}
Ok(None)
}
pub fn find_aes_key_pair<'a>(key_array: &'a [(&'a str, &'a str, &'a str)], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result<Option<([u8; 16], [u8; 16], [u8; 8])>, Box<dyn std::error::Error>> {
for (aes_key_hex, aes_iv_hex, cust_key_hex) in key_array {
let aes_key: [u8; 16] = hex::decode(aes_key_hex)?.as_slice().try_into()?;
let aes_iv: [u8; 16] = hex::decode(aes_iv_hex)?.as_slice().try_into()?;
let aes_decrypted = decrypt_aes128_cbc_nopad(data, &aes_key, &aes_iv)?;
let key_bytes = hex::decode(cust_key_hex)?;
let key_array: [u8; 8] = key_bytes.as_slice().try_into()?;
let decrypted = decrypt_data(&aes_decrypted, &key_array);
if decrypted[magic_offset..].starts_with(expected_magic) {
return Ok(Some((aes_key, aes_iv, key_array)));
}
}
Ok(None)
}
pub fn is_pana_dvd_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
let header = common::read_file(&file, 0, 64)?;
+167
View File
@@ -0,0 +1,167 @@
//libfmupre.so
//DAT_29f06d60
const SBOX: [u8; 256] = [
0x00,0x01,0xbb,0x9c,0x3f,0x5e,0xc7,0x3e,0x36,0xa4,0x92,0x93,0x38,0x9b,0x8d,0x1a,
0x3c,0x84,0xf7,0x2a,0xcb,0x3d,0x70,0xad,0x30,0xa2,0xc1,0x95,0x03,0x39,0xa5,0x0e,
0xe6,0xfb,0x09,0x1e,0xb9,0xc4,0x24,0x57,0x5f,0xc3,0x5d,0xfa,0x60,0x56,0xfd,0xed,
0x81,0xf3,0x4a,0xff,0x85,0xdb,0xcd,0xa0,0xd0,0x9d,0x8b,0x26,0x4c,0x50,0x0c,0x1d,
0x7d,0x77,0xde,0x7a,0x0a,0x0b,0x73,0xe1,0x54,0x76,0xe8,0xc2,0x28,0x05,0x98,0x99,
0x29,0x4b,0xee,0x87,0x42,0x59,0x65,0xb8,0x1f,0xdf,0x23,0x58,0x63,0x69,0x1c,0x79,
0x64,0xc5,0x6f,0x13,0x3b,0xce,0x08,0xa7,0xb2,0x12,0x20,0x7f,0xe2,0x89,0x21,0x2b,
0x41,0x49,0x7c,0x82,0xbe,0xf2,0x43,0x8e,0x86,0x02,0x9e,0xd8,0x67,0x8f,0xa3,0xe7,
0xbc,0xf8,0x66,0x22,0x4d,0xe5,0xba,0xd2,0xda,0xea,0x61,0x25,0xb0,0xc0,0x7b,0xa6,
0x48,0x96,0xdd,0xdc,0x71,0x90,0x55,0x5b,0x4f,0x4e,0x6d,0xd1,0x11,0x34,0xaa,0x1b,
0xf4,0x17,0x80,0xb7,0xcc,0x8c,0xd9,0x37,0x8a,0x2c,0x94,0x16,0x72,0x40,0xef,0x9a,
0xc8,0x47,0xf6,0xfc,0x2e,0xb3,0x2f,0xbd,0xcf,0xa9,0x15,0x10,0x18,0xec,0x6a,0x6e,
0xc9,0x91,0x53,0x78,0xa8,0x3a,0xaf,0x0d,0xe0,0x97,0x32,0x51,0xb1,0xf5,0x27,0x2d,
0x35,0x88,0x14,0xe3,0xfe,0x06,0xd7,0xd6,0xab,0xa1,0xd5,0xe4,0x45,0x44,0x9f,0x0f,
0x5a,0x83,0xeb,0xca,0x07,0x04,0xb4,0xb6,0x52,0xae,0xd4,0x33,0x31,0x75,0xd3,0x6c,
0x62,0xbf,0x6b,0xf9,0xac,0x46,0xf0,0x5c,0x19,0x7e,0x68,0xb5,0xf1,0x74,0xc6,0xe9,
];
//FUN_29f05520
fn round_function(state: &mut [u8; 4], a5: &[u8; 4]) {
let v5 = a5[1] ^ state[1];
let v6 = a5[2] ^ state[2];
let v7 = state[3] ^ a5[3];
let v8 = ((v5 >> 7) | (v5 << 1)) & 0xFF;
let v9 = ((v6 >> 5) | (v6 << 3)) & 0xFF;
let v10 = a5[0] ^ state[0];
let v11 = ((v7 >> 2) | (v7 << 6)) & 0xFF;
let v12 = v10 ^ v9 ^ v8;
let temp_v7 = v10 >> 1;
let temp_v10 = ((v10 << 7) | (v11 >> 1)) & 0xFF;
let new_a4 = ((v9 >> 1) | (v11 << 7)) & 0xFF;
let v13 = SBOX[(v11 ^ v12) as usize];
let new_a3 = ((v8 >> 1) | (v9 << 7)) & 0xFF;
let new_a2 = (temp_v7 | (v8 << 7)) & 0xFF;
let new_result = temp_v10 & 0xFF;
let a4_final = new_a4.wrapping_add(v13);
let a3_final = new_a3.wrapping_add(((v13 >> 7) | (v13 << 1)) & 0xFF);
let a2_final = new_a2.wrapping_add(((v13 >> 6) | (v13 << 2)) & 0xFF);
let result_final = new_result.wrapping_add(((v13 >> 5) | (v13 << 3)) & 0xFF);
state[0] = result_final;
state[1] = a2_final;
state[2] = a3_final;
state[3] = a4_final;
}
//FUN_29f055dc
fn key_schedule(xored_key: &[u8; 8], out: &mut [u8; 32]) {
//in original function 2 keys were XORed together to make the actual key (that the round keys are derived from)
// why? i dont know (obfuscation?) but i use the XORed key here directly
let mut v26 = [0u8; 8];
for i in 0..7 {
v26[i] = xored_key[i];
}
let v12 = xored_key[7];
v26[1] ^= v12;
v26[4] ^= v12;
for i in 0..7 {
out[i] = v26[i];
}
for i in 7..32 {
out[i] = 0;
}
let mut v22: [u8; 4] = [53, 125, 251, 154];
for v16 in 0..16 {
let v17 = 4 * (v16 & 7);
let mut state: [u8; 4] = out[v17..v17 + 4].try_into().unwrap();
round_function(&mut state, &v22);
out[v17..v17 + 4].copy_from_slice(&state);
v22.copy_from_slice(&state);
}
}
//FUN_29f056b0
fn decrypt_block(a1: &[u8; 32], a2: &mut [u8; 8]) {
let mut v6 = a2[0];
let mut v7 = a2[1];
let mut v5 = a2[2];
let mut v4 = a2[3];
let mut v24 = v6;
let mut v25 = v7;
let mut v26 = v5;
let mut v27 = v4;
let mut v10 = a2[4];
let mut v11 = a2[5];
let mut v12 = a2[6];
let mut v13 = a2[7];
let mut v15: u8 = 0;
let mut v16: u8 = 0;
let mut v17: u8 = 0;
let mut v18: u8 = 0;
for v2 in (0..16).rev() {
let v14 = (v2 & 7) as usize;
let subkey: [u8; 4] = a1[4 * v14..4 * v14 + 4].try_into().unwrap();
let mut st = [v24, v25, v26, v27];
round_function(&mut st, &subkey);
v24 = st[0];
v25 = st[1];
v26 = st[2];
v27 = st[3];
v15 = v10 ^ v24;
v16 = v11 ^ v25;
v17 = v12 ^ v26;
v18 = v13 ^ v27;
v24 ^= v10;
v25 ^= v11;
v26 ^= v12;
v27 ^= v13;
v10 = v6;
v11 = v7;
v12 = v5;
v13 = v4;
if v2 == 0 {
break;
}
v6 = v15;
v7 = v16;
v5 = v17;
v4 = v18;
}
a2[0] = v6;
a2[1] = v7;
a2[2] = v5;
a2[3] = v4;
a2[4] = v15;
a2[5] = v16;
a2[6] = v17;
a2[7] = v18;
}
fn decrypt_data_inplace(data: &mut [u8], key: &[u8; 8]) {
let mut subkeys = [0u8; 32];
key_schedule(key, &mut subkeys);
for chunk in data.chunks_mut(8) {
if chunk.len() == 8 {
let mut block: [u8; 8] = chunk.try_into().unwrap();
decrypt_block(&subkeys, &mut block);
chunk.copy_from_slice(&block);
}
}
}
pub fn decrypt_data(data: &[u8], key: &[u8; 8]) -> Vec<u8> {
let mut buf = data.to_vec();
decrypt_data_inplace(&mut buf, key);
buf
}
+67
View File
@@ -0,0 +1,67 @@
use crate::utils::common;
use binrw::BinRead;
use aes::Aes256;
use ecb::{Decryptor, cipher::{BlockDecryptMut, KeyInit, generic_array::GenericArray}};
pub static AUTO_FWS: &[(&str, &str)] = &[
("Q5551", "q5551"),
("Q5553", "q5551"),
("Q554E", "q5551"),
("Q554M", "q5551"),
("QF1EU", "qf1eu"),
("QF2EU", "qf1eu"),
("Q591E", "q591e"),
("Q522E", "q522e"),
("Q582E", "q522e"),
("Q5481", "q5481"),
("Q5431", "q5431"),
("Q5492", "q5492"),
("S5551", "q5551"), //Sharp
];
type Aes256EcbDec = Decryptor<Aes256>;
pub fn decrypt_aes256_ecb(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let key_array: [u8; 32] = key.try_into()?;
let mut decryptor = Aes256EcbDec::new(&key_array.into());
let mut buffer = ciphertext.to_vec();
for chunk in buffer.chunks_exact_mut(16) {
let block: &mut [u8; 16] = chunk.try_into()?;
decryptor.decrypt_block_mut(GenericArray::from_mut_slice(block));
}
Ok(buffer)
}
#[derive(BinRead)]
pub struct Header {
_magic_bytes: [u8; 8],
pub header_size: u32,
pub data_size: u32,
_crc32: u32,
pub mask: u32,
_data_size_decompressed: u32,
_padding2: u32,
description_bytes: [u8; 512],
}
impl Header {
pub fn description(&self) -> String {
common::string_from_bytes(&self.description_bytes)
}
}
#[derive(BinRead)]
pub struct FileHeader {
file_name_bytes: [u8; 60],
pub real_size: u32,
pub stored_size: u32,
pub header_size: u32,
pub attributes: [u8; 4],
}
impl FileHeader {
pub fn file_name(&self) -> String {
common::string_from_bytes(&self.file_name_bytes)
}
}
@@ -1,52 +1,17 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "pfl_upg", detector_func: is_pfl_upg_file, extractor_func: extract_pfl_upg }
}
use crate::AppContext;
use rsa::{RsaPublicKey, BigUint};
use hex::decode;
use std::path::Path;
use std::io::{Read, Cursor, Write};
use std::fs::{self, OpenOptions};
use aes::Aes256;
use ecb::{Decryptor, cipher::{BlockDecryptMut, KeyInit, generic_array::GenericArray}};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::keys;
#[derive(BinRead)]
struct Header {
#[br(count = 8)] _magic_bytes: Vec<u8>,
header_size: u32,
data_size: u32,
#[br(count = 4)] _crc32: Vec<u8>,
mask: u32,
_data_size_decompressed: u32,
_padding2: u32,
#[br(count = 512)] description_bytes: Vec<u8>,
}
impl Header {
fn description(&self) -> String {
common::string_from_bytes(&self.description_bytes)
}
}
#[derive(BinRead)]
struct FileHeader {
#[br(count = 60)] file_name_bytes: Vec<u8>,
real_size: u32,
stored_size: u32,
header_size: u32,
#[br(count = 4)] attributes: Vec<u8>,
}
impl FileHeader {
fn file_name(&self) -> String {
common::string_from_bytes(&self.file_name_bytes)
}
}
use include::*;
pub fn is_pfl_upg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -59,38 +24,6 @@ pub fn is_pfl_upg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box
}
}
static AUTO_FWS: &[(&str, &str)] = &[
("Q5551", "q5551"),
("Q5553", "q5551"),
("Q554E", "q5551"),
("Q554M", "q5551"),
("QF1EU", "qf1eu"),
("QF2EU", "qf1eu"),
("Q591E", "q591e"),
("Q522E", "q522e"),
("Q582E", "q522e"),
("Q5481", "q5481"),
("Q5431", "q5431"),
("Q5492", "q5492"),
("S5551", "q5551"), //Sharp
];
type Aes256EcbDec = Decryptor<Aes256>;
fn decrypt_aes256_ecb(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let key_array: [u8; 32] = key.try_into()?;
let mut decryptor = Aes256EcbDec::new(&key_array.into());
let mut buffer = ciphertext.to_vec();
for chunk in buffer.chunks_exact_mut(16) {
let block: &mut [u8; 16] = chunk.try_into()?;
decryptor.decrypt_block_mut(GenericArray::from_mut_slice(block));
}
Ok(buffer)
}
pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
+45
View File
@@ -0,0 +1,45 @@
use binrw::BinRead;
#[derive(BinRead)]
pub struct Header {
_magic_bytes: [u8; 4],
_unk1: u32,
_unk2: u16,
_flags: u8,
_unk3: u8,
_header_size: u16,
_hash_size: u16,
pub file_size: u64,
pub entry_count: u16,
_hash_count: u16,
_unk4: u32,
}
#[derive(BinRead, Clone)]
pub struct Entry {
pub flags: u32,
_unk1: u32,
pub offset: u64,
pub compressed_size: u64,
pub uncompressed_size: u64,
}
impl Entry {
pub fn id(&self) -> u32 {
self.flags >> 20
}
pub fn is_compressed(&self) -> bool {
(self.flags & 8) != 0
}
pub fn is_blocked(&self) -> bool {
(self.flags & 0x800) != 0
}
pub fn is_block_table(&self) -> bool {
(self.flags & 1) != 0
}
}
#[derive(BinRead, Clone)]
pub struct BlockEntry {
pub offset: u32,
pub size: u32,
}
+4 -49
View File
@@ -1,60 +1,15 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "pup", detector_func: is_pup_file, extractor_func: extract_pup }
}
use crate::AppContext;
use std::path::{Path};
use std::fs::{self, OpenOptions};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use std::io::{Write, Seek, SeekFrom};
use crate::utils::common;
use crate::utils::compression::{decompress_zlib};
#[derive(BinRead)]
struct Header {
#[br(count = 4)] _magic_bytes: Vec<u8>,
_unk1: u32,
_unk2: u16,
_flags: u8,
_unk3: u8,
_header_size: u16,
_hash_size: u16,
file_size: u64,
entry_count: u16,
_hash_count: u16,
_unk4: u32,
}
#[derive(BinRead, Clone)]
struct Entry {
flags: u32,
_unk1: u32,
offset: u64,
compressed_size: u64,
uncompressed_size: u64,
}
impl Entry {
fn id(&self) -> u32 {
self.flags >> 20
}
fn is_compressed(&self) -> bool {
(self.flags & 8) != 0
}
fn is_blocked(&self) -> bool {
(self.flags & 0x800) != 0
}
fn is_block_table(&self) -> bool {
(self.flags & 1) != 0
}
}
#[derive(BinRead, Clone)]
struct BlockEntry {
offset: u32,
size: u32,
}
use include::*;
pub fn is_pup_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+49
View File
@@ -0,0 +1,49 @@
use binrw::BinRead;
pub static FILE_KEY: [u8; 16] = [
0x2A, 0x54, 0xA5, 0x30, 0xE0, 0x09, 0xA3, 0xDC,
0x03, 0xFB, 0xC3, 0x5E, 0x23, 0xA2, 0xC1, 0x0D,
];
pub static FILE_IV: [u8; 16] = [0x00; 16];
#[derive(BinRead)]
pub struct ImageHeader {
_empty: [u8; 8],
_magic_bytes: [u8; 8], //imgARMcC
_target_bytes: [u8; 4],
_platform_id: [u8; 4],
pub image_type: u32,
pub size1: u32,
_size2: u32,
pub data_start_offset: u32,
_data_link_address: u32,
_data_entry_point_offset: u32,
pub flags: u32,
_timestamp: u32,
_build_host: [u8; 4],
_unk: [u8; 4],
_rest_of_header: [u8; 192],
}
impl ImageHeader {
pub fn is_encrypted(&self) -> bool {
self.flags == 0x80
}
pub fn type_string(&self) -> &str {
if self.image_type == 0xa {
return "initfs"
} else if self.image_type == 0x18 {
return "uImage"
} else if self.image_type == 0x3 {
return "loader"
} else if self.image_type == 0xd {
return "app_cramfs"
} else if self.image_type == 0x6 {
return "Customization Package"
} else if self.image_type == 0xe {
return "firmware_blob"
} else {
return "unknown"
}
}
}
@@ -1,65 +1,16 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "roku", detector_func: is_roku_file, extractor_func: extract_roku }
}
use crate::AppContext;
use std::fs::{self, OpenOptions};
use std::path::Path;
use std::io::{Write, Seek, Read, Cursor};
use tar::Archive;
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::utils::aes::{decrypt_aes128_cbc_nopad, decrypt_aes128_cbc_pcks7};
static FILE_KEY: [u8; 16] = [
0x2A, 0x54, 0xA5, 0x30, 0xE0, 0x09, 0xA3, 0xDC,
0x03, 0xFB, 0xC3, 0x5E, 0x23, 0xA2, 0xC1, 0x0D,
];
static FILE_IV: [u8; 16] = [0x00; 16];
#[derive(BinRead)]
struct ImageHeader {
#[br(count = 8)] _empty: Vec<u8>,
#[br(count = 8)] _magic_bytes: Vec<u8>, //imgARMcC
#[br(count = 4)] _target_bytes: Vec<u8>,
#[br(count = 4)] _platform_id: Vec<u8>,
image_type: u32,
size1: u32,
_size2: u32,
data_start_offset: u32,
_data_link_address: u32,
_data_entry_point_offset: u32,
flags: u32,
_timestamp: u32,
#[br(count = 4)] _build_host: Vec<u8>,
#[br(count = 4)] _unk: Vec<u8>,
#[br(count = 192)] _rest_of_header: Vec<u8>,
}
impl ImageHeader {
fn is_encrypted(&self) -> bool {
self.flags == 0x80
}
fn type_string(&self) -> &str {
if self.image_type == 0xa {
return "initfs"
} else if self.image_type == 0x18 {
return "uImage"
} else if self.image_type == 0x3 {
return "loader"
} else if self.image_type == 0xd {
return "app_cramfs"
} else if self.image_type == 0x6 {
return "Customization Package"
} else if self.image_type == 0xe {
return "firmware_blob"
} else {
return "unknown"
}
}
}
use include::*;
pub fn is_roku_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+64
View File
@@ -0,0 +1,64 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(BinRead)]
pub struct RufHeader {
_magic_bytes: [u8; 6],
_upgrade_type_bytes: [u8; 2],
_unk1: u32,
date_time_bytes: [u8; 32],
buyer_bytes: [u8; 8],
model_bytes: [u8; 32],
region_info_bytes: [u8; 32],
pub version_bytes: [u8; 4],
pub data_size: u32,
_unk2: [u8; 20],
pub dual_ruf_flag: u32,
_unk3: [u8; 44],
pub payload_count: u16,
_payload_entry_size: u16,
pub payloads_start_offset: u32,
}
impl RufHeader {
pub fn date_time(&self) -> String {
common::string_from_bytes(&self.date_time_bytes)
}
pub fn buyer(&self) -> String {
common::string_from_bytes(&self.buyer_bytes)
}
pub fn model(&self) -> String {
common::string_from_bytes(&self.model_bytes)
}
pub fn region_info(&self) -> String {
common::string_from_bytes(&self.region_info_bytes)
}
pub fn is_dual_ruf(&self) -> bool {
if self.dual_ruf_flag == 0x44 {true} else {false}
}
}
#[derive(BinRead)]
pub struct RufEntry {
_metadata: [u8; 32],
pub payload_type_bytes: u32,
pub size: u32,
_unk1: u32,
_unk2: [u8; 20],
}
impl RufEntry {
pub fn payload_type(&self) -> &str {
if self.payload_type_bytes == 1 {
return "squashfs"
} else if self.payload_type_bytes == 2 {
return "cfe"
} else if self.payload_type_bytes == 3 {
return "vmlinuz"
} else if self.payload_type_bytes == 4 {
return "loader"
} else if self.payload_type_bytes == 5 {
return "splash"
} else {
return "unknown"
}
}
}
+4 -67
View File
@@ -1,79 +1,16 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "ruf", detector_func: is_ruf_file, extractor_func: extract_ruf }
}
use crate::AppContext;
use std::path::{Path, PathBuf};
use std::fs::{self, File, OpenOptions};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use std::io::{Write, Seek, SeekFrom, Cursor};
use crate::utils::common;
use crate::keys;
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
#[derive(BinRead)]
struct RufHeader {
#[br(count = 6)] _magic_bytes: Vec<u8>,
#[br(count = 2)] _upgrade_type_bytes: Vec<u8>,
_unk1: u32,
#[br(count = 32)] date_time_bytes: Vec<u8>,
#[br(count = 8)] buyer_bytes: Vec<u8>,
#[br(count = 32)] model_bytes: Vec<u8>,
#[br(count = 32)] region_info_bytes: Vec<u8>,
#[br(count = 4)] version_bytes: Vec<u8>,
data_size: u32,
#[br(count = 20)] _unk2: Vec<u8>,
dual_ruf_flag: u32,
#[br(count = 44)] _unk3: Vec<u8>,
payload_count: u16,
_payload_entry_size: u16,
payloads_start_offset: u32,
}
impl RufHeader {
fn date_time(&self) -> String {
common::string_from_bytes(&self.date_time_bytes)
}
fn buyer(&self) -> String {
common::string_from_bytes(&self.buyer_bytes)
}
fn model(&self) -> String {
common::string_from_bytes(&self.model_bytes)
}
fn region_info(&self) -> String {
common::string_from_bytes(&self.region_info_bytes)
}
fn is_dual_ruf(&self) -> bool {
if self.dual_ruf_flag == 0x44 {true} else {false}
}
}
#[derive(BinRead)]
struct RufEntry {
#[br(count = 32)] _metadata: Vec<u8>,
payload_type_bytes: u32,
size: u32,
_unk1: u32,
#[br(count = 20)] _unk2: Vec<u8>,
}
impl RufEntry {
fn payload_type(&self) -> &str {
if self.payload_type_bytes == 1 {
return "squashfs"
} else if self.payload_type_bytes == 2 {
return "cfe"
} else if self.payload_type_bytes == 3 {
return "vmlinuz"
} else if self.payload_type_bytes == 4 {
return "loader"
} else if self.payload_type_bytes == 5 {
return "splash"
} else {
return "unknown"
}
}
}
use include::*;
pub fn is_ruf_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+7
View File
@@ -0,0 +1,7 @@
pub fn decrypt_xor(data: &[u8]) -> Vec<u8> {
let key_bytes = b"\xCC\xF0\xC8\xC4\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA";
data.iter()
.enumerate()
.map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()])
.collect()
}
+3 -13
View File
@@ -1,22 +1,13 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "rvp", detector_func: is_rvp_file, extractor_func: extract_rvp }
}
use crate::AppContext;
use std::path::Path;
use std::fs::{self, OpenOptions};
use std::io::{Write, Read, Cursor, Seek};
use crate::utils::common;
fn decrypt_xor(data: &[u8]) -> Vec<u8> {
let key_bytes = b"\xCC\xF0\xC8\xC4\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA";
data.iter()
.enumerate()
.map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()])
.collect()
}
use include::*;
pub fn is_rvp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let mut file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -118,7 +109,6 @@ pub fn extract_rvp(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
} else {
println!("Unsupported header size!");
break
}
println!("Size: {}", size);
+7
View File
@@ -0,0 +1,7 @@
pub fn decrypt_xor(data: &[u8], key: &str) -> Vec<u8> {
let key_bytes = key.as_bytes();
data.iter()
.enumerate()
.map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()])
.collect()
}
@@ -1,8 +1,6 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "samsung_old", detector_func: is_samsung_old_dir, extractor_func: extract_samsung_old }
}
use crate::AppContext;
use std::fs;
use std::path::{Path};
@@ -10,12 +8,12 @@ use std::fs::{File, OpenOptions};
use std::io::{Write};
use hex::decode;
use sha1::{Digest, Sha1};
use md5;
use crate::utils::common;
use crate::keys;
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
use md5;
use include::decrypt_xor;
pub fn is_samsung_old_dir(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let dir = match app_ctx.dir() {Some(d) => d, None => return Ok(None)};
@@ -27,14 +25,6 @@ pub fn is_samsung_old_dir(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>,
}
}
fn decrypt_xor(data: &[u8], key: &str) -> Vec<u8> {
let key_bytes = key.as_bytes();
data.iter()
.enumerate()
.map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()])
.collect()
}
pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let path = app_ctx.dir().ok_or("Extractor expected directory")?;
+212
View File
@@ -0,0 +1,212 @@
//sddl_dec 6.0 https://github.com/theubusu/sddl_dec
use crate::utils::common;
use binrw::BinRead;
pub fn decipher(s: &[u8]) -> Vec<u8> {
let len_ = s.len();
let mut v3: u32 = 904;
let mut out = s.to_vec();
let mut cnt = 0;
if len_ > 0 {
let true_len = if len_ >= 0x80 {
128
} else {
len_
};
if true_len > 0 {
let mut i = 0;
let mut j: u8 = 0;
while i < s.len() {
let iter_ = s[i];
i += 1;
j = j.wrapping_add(1);
let v11 = (iter_ as u32).wrapping_add(38400) & 0xffffffff;
let v12 = iter_ ^ ((v3 & 0xff00) >> 8) as u8;
v3 = v3.wrapping_add(v11).wrapping_add(163) & 0xffffffff;
if j == 0 {
v3 = 904;
}
if cnt < out.len() {
out[cnt] = v12;
cnt += 1;
}
}
}
}
out
}
// -- STRUCTURES --
// -- SECFILE --
pub static DOWNLOAD_ID: [u8; 4] = [0x11, 0x22, 0x33, 0x44];
#[derive(Debug, BinRead)]
pub struct SecHeader {
pub _download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
key_id_str_bytes: [u8; 4], //"key_id", purpose unknown
grp_num_str_bytes: [u8; 4], //"grp_num", the count of groups, also represents the count of info files because each group has a respective info file
prg_num_str_bytes: [u8; 4], //"prg_num", the count of module (.FXX) files
_unused_or_reserved: [u8; 16], //not used, is zeros
}
impl SecHeader {
pub fn key_id(&self) -> u32 {
let string = common::string_from_bytes(&self.key_id_str_bytes);
string.parse().unwrap()
}
pub fn grp_num(&self) -> u32 {
let string = common::string_from_bytes(&self.grp_num_str_bytes);
string.parse().unwrap()
}
pub fn prg_num(&self) -> u32 {
let string = common::string_from_bytes(&self.prg_num_str_bytes);
string.parse().unwrap()
}
}
pub static INFO_FILE_EXTENSION: &str = ".TXT";
#[derive(Debug, BinRead)]
pub struct FileHeader {
name_str_bytes: [u8; 12],
size_str_bytes: [u8; 12],
}
impl FileHeader {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_str_bytes)
}
pub fn size(&self) -> u64 {
let string = common::string_from_bytes(&self.size_str_bytes);
string.parse().unwrap()
}
}
// -- MODULE --
#[derive(Debug, BinRead)]
pub struct ModuleComHeader { //"com_header"
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
_outer_maker_id: u8,
_outer_model_id: u8,
_inner_maker_id: u8,
_reserve1: u8,
_reserve2: u32,
_reserve3: u32,
_start_version: [u8; 4], //the first version that can upgrade to the new version
_end_version: [u8; 4], //the last version that can upgrade to the new version
_new_version: [u8; 4], //the new version, as in the version of the data in this module
_reserve4: u16,
_module_num: u16, //the logic seems to indicate that there can be multiple entries in one module, but i have never seen this go above 1.
}
#[derive(Debug, BinRead)]
pub struct ModuleHeader { //"header", appears after com_header
_module_id: u16,
module_atr: u8,
_target_id: u8,
pub cmp_size: u32,
_org_size: u32,
_crc_value: u32,
}
impl ModuleHeader {
pub fn is_ciphered(&self) -> bool {
(self.module_atr & 0x02) != 0
}
pub fn is_compressed(&self) -> bool {
(self.module_atr & 0x01) != 0
}
}
#[derive(Debug, BinRead)]
pub struct ContentHeader {
_magic1: u8, //always 0x01?
_dest_offset: u32,
_source_offset: u32,
pub size: u32,
_magic2: u8, //always 0x21?
}
impl ContentHeader {
//these hacks are needed because for some reason older files have the first nibble of the offset set to D/C
//no idea why, but masking them off makes it works properly
pub fn dest_offset(&self) -> u32 {
if ((self._dest_offset >> 28) & 0xF) == 0xD {
self._dest_offset & 0x0FFFFFFF
} else {
self._dest_offset
}
}
pub fn source_offset(&self) -> u32 {
if ((self._source_offset >> 28) & 0xF) == 0xC {
self._source_offset & 0x0FFFFFFF
} else {
self._source_offset
}
}
pub fn has_subfile(&self) -> bool {
self.source_offset() == 0x10E
}
}
// -- TDI --
// Called SDIT.FDI in the secfile
pub static TDI_FILENAME: &str = "SDIT.FDI";
pub static SUPPORTED_TDI_VERSION: u16 = 2;
#[derive(Debug, BinRead)]
pub struct TdiHead {
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
pub num_of_group: u8,
_reserve1: u8,
pub format_version: u16, //checks for "2" here
}
#[derive(Debug, BinRead)]
pub struct TdiGroupHead {
pub group_id: u8,
pub num_of_target: u8, //logic checks that this is not more than 5
_reserved: u16,
}
#[derive(Debug, BinRead)]
pub struct TdiTgtInf {
_outer_maker_id: u8,
_outer_model_id: u8,
_inner_maker_id: u8,
_reserve3: u8,
_inner_model_id: [u8; 4],
_ext_model_id: [u8; 4],
pub _start_version: [u8; 4], //the first version that can upgrade to the new version
pub _end_version: [u8; 4], //the last version that can upgrade to the new version
pub new_version: [u8; 4], //the new version, as in the version of the data in this module
pub target_id: u8,
_num_of_compatible_target: u8,
pub num_of_txx: u16, //"TXX" refers to the ".FXX" segment files of each module. I assume F is an encrypted version of T, the same happens with SDIT; "TDI" -> "FDI"
_unknown: [u8; 8],
module_name_bytes: [u8; 8],
}
impl TdiTgtInf {
pub fn module_name(&self) -> String {
common::string_from_bytes(&self.module_name_bytes)
}
pub fn version_string(&self) -> String {
format!("{}.{}{}{}", self.new_version[0], self.new_version[1], self.new_version[2], self.new_version[3])
}
}
// -- dec key --
pub static DEC_KEY: [u8; 16] = [
0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB,
0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C,
];
pub static DEC_IV: [u8; 16] = [
0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54,
0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66,
];
@@ -1,227 +1,17 @@
//sddl_dec 6.0
//sddl_dec 6.0 https://github.com/theubusu/sddl_dec
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "sddl_sec", detector_func: is_sddl_sec_file, extractor_func: extract_sddl_sec }
}
use crate::AppContext;
use std::path::{Path, PathBuf};
use std::fs::{self, File, OpenOptions};
use std::io::{Cursor, Seek, SeekFrom, Write};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
use crate::utils::compression::{decompress_zlib};
pub fn decipher(s: &[u8]) -> Vec<u8> {
let len_ = s.len();
let mut v3: u32 = 904;
let mut out = s.to_vec();
let mut cnt = 0;
if len_ > 0 {
let true_len = if len_ >= 0x80 {
128
} else {
len_
};
if true_len > 0 {
let mut i = 0;
let mut j: u8 = 0;
while i < s.len() {
let iter_ = s[i];
i += 1;
j = j.wrapping_add(1);
let v11 = (iter_ as u32).wrapping_add(38400) & 0xffffffff;
let v12 = iter_ ^ ((v3 & 0xff00) >> 8) as u8;
v3 = v3.wrapping_add(v11).wrapping_add(163) & 0xffffffff;
if j == 0 {
v3 = 904;
}
if cnt < out.len() {
out[cnt] = v12;
cnt += 1;
}
}
}
}
out
}
// -- STRUCTURES --
// -- SECFILE --
pub static DOWNLOAD_ID: [u8; 4] = [0x11, 0x22, 0x33, 0x44];
#[derive(Debug, BinRead)]
pub struct SecHeader {
pub _download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
key_id_str_bytes: [u8; 4], //"key_id", purpose unknown
grp_num_str_bytes: [u8; 4], //"grp_num", the count of groups, also represents the count of info files because each group has a respective info file
prg_num_str_bytes: [u8; 4], //"prg_num", the count of module (.FXX) files
_unused_or_reserved: [u8; 16], //not used, is zeros
}
impl SecHeader {
pub fn key_id(&self) -> u32 {
let string = common::string_from_bytes(&self.key_id_str_bytes);
string.parse().unwrap()
}
pub fn grp_num(&self) -> u32 {
let string = common::string_from_bytes(&self.grp_num_str_bytes);
string.parse().unwrap()
}
pub fn prg_num(&self) -> u32 {
let string = common::string_from_bytes(&self.prg_num_str_bytes);
string.parse().unwrap()
}
}
pub static INFO_FILE_EXTENSION: &str = ".TXT";
#[derive(Debug, BinRead)]
pub struct FileHeader {
name_str_bytes: [u8; 12],
size_str_bytes: [u8; 12],
}
impl FileHeader {
pub fn name(&self) -> String {
common::string_from_bytes(&self.name_str_bytes)
}
pub fn size(&self) -> u64 {
let string = common::string_from_bytes(&self.size_str_bytes);
string.parse().unwrap()
}
}
// -- MODULE --
#[derive(Debug, BinRead)]
pub struct ModuleComHeader { //"com_header"
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
_outer_maker_id: u8,
_outer_model_id: u8,
_inner_maker_id: u8,
_reserve1: u8,
_reserve2: u32,
_reserve3: u32,
_start_version: [u8; 4], //the first version that can upgrade to the new version
_end_version: [u8; 4], //the last version that can upgrade to the new version
_new_version: [u8; 4], //the new version, as in the version of the data in this module
_reserve4: u16,
_module_num: u16, //the logic seems to indicate that there can be multiple entries in one module, but i have never seen this go above 1.
}
#[derive(Debug, BinRead)]
pub struct ModuleHeader { //"header", appears after com_header
_module_id: u16,
module_atr: u8,
_target_id: u8,
pub cmp_size: u32,
_org_size: u32,
_crc_value: u32,
}
impl ModuleHeader {
pub fn is_ciphered(&self) -> bool {
(self.module_atr & 0x02) != 0
}
pub fn is_compressed(&self) -> bool {
(self.module_atr & 0x01) != 0
}
}
#[derive(Debug, BinRead)]
pub struct ContentHeader {
_magic1: u8, //always 0x01?
_dest_offset: u32,
_source_offset: u32,
pub size: u32,
_magic2: u8, //always 0x21?
}
impl ContentHeader {
//these hacks are needed because for some reason older files have the first nibble of the offset set to D/C
//no idea why, but masking them off makes it works properly
pub fn dest_offset(&self) -> u32 {
if ((self._dest_offset >> 28) & 0xF) == 0xD {
self._dest_offset & 0x0FFFFFFF
} else {
self._dest_offset
}
}
pub fn source_offset(&self) -> u32 {
if ((self._source_offset >> 28) & 0xF) == 0xC {
self._source_offset & 0x0FFFFFFF
} else {
self._source_offset
}
}
pub fn has_subfile(&self) -> bool {
self.source_offset() == 0x10E
}
}
// -- TDI --
// Called SDIT.FDI in the secfile
pub static TDI_FILENAME: &str = "SDIT.FDI";
pub static SUPPORTED_TDI_VERSION: u16 = 2;
#[derive(Debug, BinRead)]
pub struct TdiHead {
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
pub num_of_group: u8,
_reserve1: u8,
pub format_version: u16, //checks for "2" here
}
#[derive(Debug, BinRead)]
pub struct TdiGroupHead {
pub group_id: u8,
pub num_of_target: u8, //logic checks that this is not more than 5
_reserved: u16,
}
#[derive(Debug, BinRead)]
pub struct TdiTgtInf {
_outer_maker_id: u8,
_outer_model_id: u8,
_inner_maker_id: u8,
_reserve3: u8,
_inner_model_id: [u8; 4],
_ext_model_id: [u8; 4],
pub _start_version: [u8; 4], //the first version that can upgrade to the new version
pub _end_version: [u8; 4], //the last version that can upgrade to the new version
pub new_version: [u8; 4], //the new version, as in the version of the data in this module
pub target_id: u8,
_num_of_compatible_target: u8,
pub num_of_txx: u16, //"TXX" refers to the ".FXX" segment files of each module. I assume F is an encrypted version of T, the same happens with SDIT; "TDI" -> "FDI"
_unknown: [u8; 8],
module_name_bytes: [u8; 8],
}
impl TdiTgtInf {
pub fn module_name(&self) -> String {
common::string_from_bytes(&self.module_name_bytes)
}
pub fn version_string(&self) -> String {
format!("{}.{}{}{}", self.new_version[0], self.new_version[1], self.new_version[2], self.new_version[3])
}
}
// -- dec key --
static DEC_KEY: [u8; 16] = [
0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB,
0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C,
];
static DEC_IV: [u8; 16] = [
0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54,
0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66,
];
use include::*;
pub fn is_sddl_sec_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+43
View File
@@ -0,0 +1,43 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(BinRead)]
pub struct Header {
_magic_bytes: [u8; 4],
version_bytes: [u8; 8],
model_bytes: [u8; 16],
firmware_bytes: [u8; 16],
_unk: u32,
check: [u8; 8],
_unk2: [u8; 8],
}
impl Header {
pub fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
pub fn model(&self) -> String {
common::string_from_bytes(&self.model_bytes)
}
pub fn firmware(&self) -> String {
common::string_from_bytes(&self.firmware_bytes)
}
pub fn is_new_type(&self) -> bool {
&self.check == b"\x01VER_PR1"
}
}
#[derive(BinRead)]
pub struct EntryOld {
pub size: u32,
pub _unk: u32,
pub offset: u32,
pub _unk2: u32,
}
#[derive(BinRead)]
pub struct EntryNew {
pub size: u32,
_unk: u32,
pub offset: u32,
_unk2: [u8; 12],
}
+4 -46
View File
@@ -1,56 +1,14 @@
mod include;
use std::any::Any;
use crate::{AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "slp", detector_func: is_slp_file, extractor_func: extract_slp }
}
use crate::AppContext;
use std::path::{Path};
use std::fs::{self, OpenOptions};
use std::io::{Write, Seek, SeekFrom};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
#[derive(BinRead)]
struct Header {
#[br(count = 4)] _magic_bytes: Vec<u8>,
#[br(count = 8)] version_bytes: Vec<u8>,
#[br(count = 16)] model_bytes: Vec<u8>,
#[br(count = 16)] firmware_bytes: Vec<u8>,
_unk: u32,
#[br(count = 8)] check: Vec<u8>,
#[br(count = 8)] _unk2: Vec<u8>,
}
impl Header {
fn version(&self) -> String {
common::string_from_bytes(&self.version_bytes)
}
fn model(&self) -> String {
common::string_from_bytes(&self.model_bytes)
}
fn firmware(&self) -> String {
common::string_from_bytes(&self.firmware_bytes)
}
fn is_new_type(&self) -> bool {
self.check == b"\x01VER_PR1"
}
}
#[derive(BinRead)]
struct EntryOld {
size: u32,
_unk: u32,
offset: u32,
_unk2: u32,
}
#[derive(BinRead)]
struct EntryNew {
size: u32,
_unk: u32,
offset: u32,
#[br(count = 12)] _unk2: Vec<u8>,
}
use include::*;
pub fn is_slp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
+52
View File
@@ -0,0 +1,52 @@
use crate::utils::common;
use binrw::BinRead;
//thx sony
static HEX_SUBSTITUTION: [u8; 256] = [
0xE8, 0x4D, 0x63, 0xF4, 0xF8, 0xA9, 0x21, 0x9C, 0xC7, 0x82, 0xCD, 0xE3, 0xC1, 0xCE, 0xC0, 0xFA, 0xE7, 0xD6, 0x96, 0x46, 0x12, 0x03, 0x14, 0x33, 0xED, 0x10, 0xEC, 0x69, 0x16, 0xE0, 0x28, 0x30,
0x77, 0x0E, 0x3D, 0xEF, 0x36, 0x4C, 0x18, 0xEB, 0x41, 0x89, 0x64, 0x8A, 0x70, 0x0C, 0x23, 0xA3, 0x79, 0x6D, 0x75, 0x7E, 0x1A, 0x2D, 0x01, 0x91, 0x88, 0xCB, 0xFC, 0x8B, 0xFD, 0x94, 0x0A, 0x39,
0xBC, 0x98, 0x87, 0xBB, 0xC2, 0x9B, 0x81, 0x1C, 0x4B, 0xA6, 0x58, 0x59, 0x45, 0x57, 0x8C, 0x7B, 0x5A, 0x56, 0x08, 0x73, 0x65, 0xEE, 0x2A, 0x25, 0xB0, 0x5B, 0x55, 0xB2, 0xB8, 0x1E, 0xEA, 0xC5,
0x6A, 0x40, 0x86, 0x5C, 0x3C, 0x54, 0xBF, 0xF6, 0xA8, 0xF2, 0x06, 0x4A, 0xFE, 0xC4, 0x32, 0x8D, 0xF0, 0x5D, 0x35, 0x53, 0xD7, 0xBD, 0xBA, 0x20, 0x2F, 0xCA, 0xE1, 0xCC, 0xA4, 0x44, 0x85, 0xDE,
0xDB, 0x5E, 0x27, 0x52, 0x6E, 0x38, 0x04, 0x66, 0xD0, 0x92, 0xD3, 0xC6, 0x7D, 0x71, 0xDA, 0x2C, 0x9A, 0x49, 0x8E, 0x80, 0x13, 0x5F, 0x11, 0x51, 0x15, 0x22, 0xCF, 0xAC, 0x0F, 0xD5, 0xFF, 0x3F,
0x17, 0xAD, 0xD8, 0xD9, 0xAB, 0x02, 0x6B, 0x0D, 0xDF, 0xF1, 0x84, 0x3B, 0x78, 0x19, 0x76, 0x60, 0x50, 0xDC, 0xC3, 0xB5, 0x43, 0x0B, 0x95, 0x97, 0x67, 0xD4, 0x29, 0xF7, 0xE4, 0x1B, 0xAE, 0x48,
0xB1, 0x8F, 0x24, 0x7A, 0x74, 0xFB, 0x34, 0x09, 0x00, 0x31, 0x9F, 0x61, 0x4F, 0xB6, 0xA0, 0xA7, 0xB4, 0x9E, 0x1D, 0xAA, 0xF9, 0xBE, 0x37, 0x2E, 0xB9, 0x6F, 0xA5, 0x83, 0xA1, 0x93, 0x07, 0xE2,
0x7F, 0x3E, 0xF3, 0x99, 0x62, 0x4E, 0xE9, 0xC8, 0x6C, 0x68, 0x1F, 0x47, 0x42, 0x26, 0x9D, 0xE5, 0x7C, 0x72, 0x3A, 0x2B, 0xE6, 0xF5, 0xD2, 0x90, 0x05, 0xD1, 0xDD, 0xC9, 0xAF, 0xB7, 0xA2, 0xB3,
];
pub fn hex_substitute(data: &[u8]) -> Vec<u8> {
data.iter().map(|&b| HEX_SUBSTITUTION[b as usize]).collect()
}
#[derive(BinRead)]
pub struct Header {
firmware_name_bytes: [u8; 8],
_unk_version_bytes: [u8; 8],
firmware_version_bytes: [u8; 16],
date_bytes: [u8; 16],
_unk2_version_bytes: [u8; 8],
_unk3_version_bytes: [u8; 8],
_unk4_version_bytes: [u8; 16],
_unk: u32,
_checksum: u64,
pub file_size: u32,
_unk5_version_bytes: [u8; 16],
_unk6_version_bytes: [u8; 16],
_unk2: [u8; 32],
}
impl Header {
pub fn firmware_name(&self) -> String {
common::string_from_bytes(&self.firmware_name_bytes)
}
pub fn firmware_version(&self) -> String {
common::string_from_bytes(&self.firmware_version_bytes)
}
pub fn date(&self) -> String {
common::string_from_bytes(&self.date_bytes)
}
}
#[derive(BinRead)]
pub struct Entry {
pub offset: u32,
pub size: u32,
}
@@ -1,67 +1,16 @@
mod include;
use std::any::Any;
use crate::{InputTarget, AppContext, formats::Format};
pub fn format() -> Format {
Format { name: "sony_bdp", detector_func: is_sony_bdp_file, extractor_func: extract_sony_bdp }
}
use crate::{InputTarget, AppContext};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::fs::{self, OpenOptions};
use std::io::{Cursor, Write};
use binrw::{BinRead, BinReaderExt};
use binrw::BinReaderExt;
use crate::utils::common;
use crate::formats;
//thx sony
static HEX_SUBSTITUTION: [u8; 256] = [
0xE8, 0x4D, 0x63, 0xF4, 0xF8, 0xA9, 0x21, 0x9C, 0xC7, 0x82, 0xCD, 0xE3, 0xC1, 0xCE, 0xC0, 0xFA, 0xE7, 0xD6, 0x96, 0x46, 0x12, 0x03, 0x14, 0x33, 0xED, 0x10, 0xEC, 0x69, 0x16, 0xE0, 0x28, 0x30,
0x77, 0x0E, 0x3D, 0xEF, 0x36, 0x4C, 0x18, 0xEB, 0x41, 0x89, 0x64, 0x8A, 0x70, 0x0C, 0x23, 0xA3, 0x79, 0x6D, 0x75, 0x7E, 0x1A, 0x2D, 0x01, 0x91, 0x88, 0xCB, 0xFC, 0x8B, 0xFD, 0x94, 0x0A, 0x39,
0xBC, 0x98, 0x87, 0xBB, 0xC2, 0x9B, 0x81, 0x1C, 0x4B, 0xA6, 0x58, 0x59, 0x45, 0x57, 0x8C, 0x7B, 0x5A, 0x56, 0x08, 0x73, 0x65, 0xEE, 0x2A, 0x25, 0xB0, 0x5B, 0x55, 0xB2, 0xB8, 0x1E, 0xEA, 0xC5,
0x6A, 0x40, 0x86, 0x5C, 0x3C, 0x54, 0xBF, 0xF6, 0xA8, 0xF2, 0x06, 0x4A, 0xFE, 0xC4, 0x32, 0x8D, 0xF0, 0x5D, 0x35, 0x53, 0xD7, 0xBD, 0xBA, 0x20, 0x2F, 0xCA, 0xE1, 0xCC, 0xA4, 0x44, 0x85, 0xDE,
0xDB, 0x5E, 0x27, 0x52, 0x6E, 0x38, 0x04, 0x66, 0xD0, 0x92, 0xD3, 0xC6, 0x7D, 0x71, 0xDA, 0x2C, 0x9A, 0x49, 0x8E, 0x80, 0x13, 0x5F, 0x11, 0x51, 0x15, 0x22, 0xCF, 0xAC, 0x0F, 0xD5, 0xFF, 0x3F,
0x17, 0xAD, 0xD8, 0xD9, 0xAB, 0x02, 0x6B, 0x0D, 0xDF, 0xF1, 0x84, 0x3B, 0x78, 0x19, 0x76, 0x60, 0x50, 0xDC, 0xC3, 0xB5, 0x43, 0x0B, 0x95, 0x97, 0x67, 0xD4, 0x29, 0xF7, 0xE4, 0x1B, 0xAE, 0x48,
0xB1, 0x8F, 0x24, 0x7A, 0x74, 0xFB, 0x34, 0x09, 0x00, 0x31, 0x9F, 0x61, 0x4F, 0xB6, 0xA0, 0xA7, 0xB4, 0x9E, 0x1D, 0xAA, 0xF9, 0xBE, 0x37, 0x2E, 0xB9, 0x6F, 0xA5, 0x83, 0xA1, 0x93, 0x07, 0xE2,
0x7F, 0x3E, 0xF3, 0x99, 0x62, 0x4E, 0xE9, 0xC8, 0x6C, 0x68, 0x1F, 0x47, 0x42, 0x26, 0x9D, 0xE5, 0x7C, 0x72, 0x3A, 0x2B, 0xE6, 0xF5, 0xD2, 0x90, 0x05, 0xD1, 0xDD, 0xC9, 0xAF, 0xB7, 0xA2, 0xB3,
];
fn hex_substitute(data: &[u8]) -> Vec<u8> {
data.iter().map(|&b| HEX_SUBSTITUTION[b as usize]).collect()
}
#[derive(BinRead)]
struct Header {
#[br(count = 8)] firmware_name_bytes: Vec<u8>,
#[br(count = 8)] _unk_version_bytes: Vec<u8>,
#[br(count = 16)] firmware_version_bytes: Vec<u8>,
#[br(count = 16)] date_bytes: Vec<u8>,
#[br(count = 8)] _unk2_version_bytes: Vec<u8>,
#[br(count = 8)] _unk3_version_bytes: Vec<u8>,
#[br(count = 16)] _unk4_version_bytes: Vec<u8>,
_unk: u32,
_checksum: u64,
file_size: u32,
#[br(count = 16)] _unk5_version_bytes: Vec<u8>,
#[br(count = 16)] _unk6_version_bytes: Vec<u8>,
#[br(count = 32)] _unk2: Vec<u8>,
}
impl Header {
fn firmware_name(&self) -> String {
common::string_from_bytes(&self.firmware_name_bytes)
}
fn firmware_version(&self) -> String {
common::string_from_bytes(&self.firmware_version_bytes)
}
fn date(&self) -> String {
common::string_from_bytes(&self.date_bytes)
}
}
#[derive(BinRead)]
struct Entry {
offset: u32,
size: u32,
}
use include::*;
pub fn is_sony_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};