use std::collections::HashSet;
use crate::{error::Error, util::read::Reader, version::Version};
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum DataChecksum {
Adler32(u32),
Crc32(u32),
Md5([u8; 16]),
Sha1([u8; 20]),
Sha256([u8; 32]),
}
stable_name_enum!(DataChecksum, {
Self::Adler32(_) => "adler32",
Self::Crc32(_) => "crc32",
Self::Md5(_) => "md5",
Self::Sha1(_) => "sha1",
Self::Sha256(_) => "sha256",
});
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum DataFlag {
VersionInfoValid,
VersionInfoNotValid,
BZipped,
TimeStampInUTC,
IsUninstallerExe,
CallInstructionOptimized,
Touch,
ChunkEncrypted,
ChunkCompressed,
SolidBreak,
Sign,
SignOnce,
}
stable_flag_enum!(DataFlag, {
VersionInfoValid => "version_info_valid",
VersionInfoNotValid => "version_info_not_valid",
BZipped => "bzipped",
TimeStampInUTC => "time_stamp_in_utc",
IsUninstallerExe => "is_uninstaller_exe",
CallInstructionOptimized => "call_instruction_optimized",
Touch => "touch",
ChunkEncrypted => "chunk_encrypted",
ChunkCompressed => "chunk_compressed",
SolidBreak => "solid_break",
Sign => "sign",
SignOnce => "sign_once",
});
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SignMode {
NoSetting,
Yes,
Once,
Check,
}
stable_name_enum!(SignMode, {
Self::NoSetting => "no_setting",
Self::Yes => "yes",
Self::Once => "once",
Self::Check => "check",
});
#[derive(Clone, Debug)]
pub struct DataEntry {
pub first_slice: u32,
pub last_slice: u32,
pub start_offset: u32,
pub chunk_sub_offset: u64,
pub original_size: u64,
pub chunk_compressed_size: u64,
pub checksum: DataChecksum,
pub timestamp_seconds: i64,
pub timestamp_nanos: u32,
pub file_version: u64,
pub flags: HashSet<DataFlag>,
pub flags_raw: Vec<u8>,
pub sign_mode: SignMode,
pub sign_mode_raw: u8,
}
impl DataEntry {
pub(crate) fn read(reader: &mut Reader<'_>, version: &Version) -> Result<Self, Error> {
let mut first_slice = reader.u32_le("Data.FirstSlice")?;
let mut last_slice = reader.u32_le("Data.LastSlice")?;
if !version.at_least(4, 0, 0) {
first_slice = first_slice.saturating_sub(1);
last_slice = last_slice.saturating_sub(1);
}
let start_offset = if version.at_least(6, 5, 2) {
let v = reader.i64_le("Data.StartOffset")?;
u32::try_from(v.unsigned_abs()).unwrap_or(u32::MAX)
} else {
reader.u32_le("Data.StartOffset")?
};
let chunk_sub_offset = if version.at_least(4, 0, 1) {
reader.u64_le("Data.ChunkSubOffset")?
} else {
0
};
let (original_size, chunk_compressed_size) = if version.at_least(4, 0, 0) {
(
reader.u64_le("Data.OriginalSize")?,
reader.u64_le("Data.ChunkCompressedSize")?,
)
} else {
(
u64::from(reader.u32_le("Data.OriginalSize")?),
u64::from(reader.u32_le("Data.ChunkCompressedSize")?),
)
};
let checksum = read_checksum(reader, version)?;
let (timestamp_seconds, timestamp_nanos) = read_filetime(reader)?;
let file_version_ms = reader.u32_le("Data.FileVersionMS")?;
let file_version_ls = reader.u32_le("Data.FileVersionLS")?;
let file_version = (u64::from(file_version_ms) << 32) | u64::from(file_version_ls);
let table = data_flag_table(version);
let raw = reader.set_bytes(table.len(), true, "Data.Flags")?;
let mut flags = super::decode_packed_flags(&raw, &table);
if !version.at_least(4, 2, 5) {
flags.insert(DataFlag::ChunkCompressed);
}
let (sign_mode, sign_mode_raw) = if version.at_least(6, 4, 3) {
(SignMode::NoSetting, 0)
} else if version.at_least(6, 3, 0) {
let raw = reader.u8("Data.SignMode")?;
(decode_sign_mode(raw).unwrap_or(SignMode::NoSetting), raw)
} else if flags.contains(&DataFlag::SignOnce) {
(SignMode::Once, 0)
} else if flags.contains(&DataFlag::Sign) {
(SignMode::Yes, 0)
} else {
(SignMode::NoSetting, 0)
};
Ok(Self {
first_slice,
last_slice,
start_offset,
chunk_sub_offset,
original_size,
chunk_compressed_size,
checksum,
timestamp_seconds,
timestamp_nanos,
file_version,
flags,
flags_raw: raw,
sign_mode,
sign_mode_raw,
})
}
}
const FILETIME_OFFSET: i64 = 0x019D_B1DE_D53E_8000;
fn read_filetime(reader: &mut Reader<'_>) -> Result<(i64, u32), Error> {
let raw = reader.i64_le("Data.Timestamp")?;
let shifted = raw.checked_sub(FILETIME_OFFSET).unwrap_or(0);
let secs = shifted.checked_div(10_000_000).unwrap_or(0);
let rem = shifted.checked_rem(10_000_000).unwrap_or(0);
let nanos_signed = rem.checked_mul(100).unwrap_or(0);
let nanos = u32::try_from(nanos_signed.unsigned_abs()).unwrap_or(0);
Ok((secs, nanos))
}
fn read_checksum(reader: &mut Reader<'_>, version: &Version) -> Result<DataChecksum, Error> {
if version.at_least(6, 4, 0) {
Ok(DataChecksum::Sha256(reader.array::<32>("Data.SHA256")?))
} else if version.at_least(5, 3, 9) {
Ok(DataChecksum::Sha1(reader.array::<20>("Data.SHA1")?))
} else if version.at_least(4, 2, 0) {
Ok(DataChecksum::Md5(reader.array::<16>("Data.MD5")?))
} else if version.at_least(4, 0, 1) {
Ok(DataChecksum::Crc32(reader.u32_le("Data.CRC32")?))
} else {
Ok(DataChecksum::Adler32(reader.u32_le("Data.Adler32")?))
}
}
fn data_flag_table(version: &Version) -> Vec<DataFlag> {
if version.at_least(6, 4, 3) {
return vec![
DataFlag::VersionInfoValid,
DataFlag::TimeStampInUTC,
DataFlag::CallInstructionOptimized,
DataFlag::ChunkEncrypted,
DataFlag::ChunkCompressed,
];
}
let mut t = vec![DataFlag::VersionInfoValid, DataFlag::VersionInfoNotValid];
if version.at_least(2, 0, 17) && !version.at_least(4, 0, 1) {
t.push(DataFlag::BZipped);
}
if version.at_least(4, 0, 10) {
t.push(DataFlag::TimeStampInUTC);
}
if version.at_least(4, 1, 0) {
t.push(DataFlag::IsUninstallerExe);
}
if version.at_least(4, 1, 8) {
t.push(DataFlag::CallInstructionOptimized);
}
if version.at_least(4, 2, 0) {
t.push(DataFlag::Touch);
}
if version.at_least(4, 2, 2) {
t.push(DataFlag::ChunkEncrypted);
}
if version.at_least(4, 2, 5) {
t.push(DataFlag::ChunkCompressed);
}
if version.at_least(5, 1, 13) {
t.push(DataFlag::SolidBreak);
}
if version.at_least(5, 5, 7) && !version.at_least(6, 3, 0) {
t.push(DataFlag::Sign);
t.push(DataFlag::SignOnce);
}
t
}
fn decode_sign_mode(b: u8) -> Option<SignMode> {
match b {
0 => Some(SignMode::NoSetting),
1 => Some(SignMode::Yes),
2 => Some(SignMode::Once),
3 => Some(SignMode::Check),
_ => None,
}
}