mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-16 05:04:22 +02:00
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:
@@ -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)};
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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)};
|
||||
@@ -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;
|
||||
@@ -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)};
|
||||
@@ -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)
|
||||
@@ -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)};
|
||||
@@ -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}};
|
||||
@@ -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.
|
||||
}
|
||||
@@ -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)?;
|
||||
@@ -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()?;
|
||||
@@ -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")?;
|
||||
|
||||
@@ -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();
|
||||
@@ -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),
|
||||
];
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)};
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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)};
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)};
|
||||
@@ -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(());
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")?;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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)};
|
||||
@@ -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)};
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)};
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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")?;
|
||||
|
||||
@@ -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)};
|
||||
@@ -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],
|
||||
}
|
||||
@@ -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)};
|
||||
@@ -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)};
|
||||
Reference in New Issue
Block a user