use serde::{Deserialize, Serialize};
use crate::dir::{
gen3::{ALWAYS_RETAIN, CPD_MAGIC_BYTES, CodePartitionDirectory},
man::Manifest,
};
use crate::dump48;
use crate::part::{
fpt::{DIR_PARTS, FPT, FPTEntry, FS_PARTS, FTPR},
generic::{
ClearOptions, Partition, UnknownOrMalformedPartition, dir_clean, retain, strs_to_strings,
},
};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct CPDPartition {
pub entry: FPTEntry,
pub data: Vec<u8>,
pub cpd: CodePartitionDirectory,
}
impl CPDPartition {
pub fn check_signature(&self) -> Result<(), String> {
if let Ok(m) = &self.cpd.manifest {
if m.verify() {
Ok(())
} else {
Err("hash mismatch".into())
}
} else {
Err("no manifest found".into())
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DataPartition {
pub entry: FPTEntry,
pub data: Vec<u8>,
pub manifest: Manifest,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum Gen3Partition {
Dir(CPDPartition),
Data(DataPartition),
MalformedOrUnknown(UnknownOrMalformedPartition),
}
impl Partition for Gen3Partition {
fn data(&self) -> &Vec<u8> {
match self {
Self::Dir(d) => &d.data,
Self::Data(d) => &d.data,
Self::MalformedOrUnknown(d) => &d.data,
}
}
fn entry(&self) -> &FPTEntry {
match self {
Self::Dir(d) => &d.entry,
Self::Data(d) => &d.entry,
Self::MalformedOrUnknown(d) => &d.entry,
}
}
fn set_data(&mut self, data: Vec<u8>) {
match self {
Self::Dir(d) => d.data = data,
Self::Data(d) => d.data = data,
Self::MalformedOrUnknown(d) => d.data = data,
}
}
fn set_entry(&mut self, entry: FPTEntry) {
match self {
Self::Dir(d) => d.entry = entry,
Self::Data(d) => d.entry = entry,
Self::MalformedOrUnknown(d) => d.entry = entry,
}
}
}
impl Gen3Partition {
pub fn parse(data: &[u8], entry: FPTEntry, debug: bool) -> Self {
let o = entry.offset();
let n = entry.name();
let n = n.as_str();
let data = data.to_vec();
match entry {
_ if data.len() > 4 && &data[..4] == CPD_MAGIC_BYTES => {
if !DIR_PARTS.contains(&n) && debug {
println!("Unknown CPD {n} @ 0x{o:08x}");
}
match CodePartitionDirectory::new(&data, o) {
Ok(cpd) => Gen3Partition::Dir(CPDPartition { entry, data, cpd }),
Err(e) => {
let note =
format!("Expected CPD {n} @ 0x{o:08x}, but could not parse it: {e}");
Gen3Partition::MalformedOrUnknown(UnknownOrMalformedPartition {
entry,
data,
note,
})
}
}
}
_ if FS_PARTS.contains(&n) => {
let note = "file system parsing not yet implemented".to_string();
Gen3Partition::MalformedOrUnknown(UnknownOrMalformedPartition { entry, data, note })
}
_ => {
if let Ok(manifest) = Manifest::new(&data) {
if debug {
println!("Manifest found in {n} @ 0x{o:08x}: {manifest}");
}
return Gen3Partition::Data(DataPartition {
entry,
data,
manifest,
});
}
let note = format!("Cannot (yet) parse {n} @ 0x{o:08x}, skipping...");
if debug {
println!("{note}");
dump48(&data);
}
Gen3Partition::MalformedOrUnknown(UnknownOrMalformedPartition { entry, data, note })
}
}
}
pub fn relocate(&mut self, offset: u32) -> Result<(), String> {
match self {
Self::Dir(p) => p.entry.set_offset(offset),
Self::Data(p) => p.entry.set_offset(offset),
Self::MalformedOrUnknown(p) => p.entry.set_offset(offset),
}
Ok(())
}
}
pub fn parse(fpt: &FPT, data: &[u8], debug: bool) -> Vec<Gen3Partition> {
fpt.entries
.iter()
.map(|e| {
let offset = e.offset();
let size = e.size as usize;
let end = offset + size;
let l = data.len();
if end > l {
let note = format!("{offset:08x}..{end:08x} out of bounds ({l:08x})");
Gen3Partition::MalformedOrUnknown(UnknownOrMalformedPartition {
entry: *e,
data: vec![],
note,
})
} else {
Gen3Partition::parse(&data[offset..end], *e, debug)
}
})
.collect()
}
pub fn clean(parts: &[Gen3Partition], options: &ClearOptions) -> Vec<Gen3Partition> {
use log::info;
let mut reduced = parts
.iter()
.filter(|p| {
let e = p.entry();
let n = e.name();
if retain(n, options) {
info!("Retain {e}");
true
} else {
info!("Remove {e}");
false
}
})
.cloned()
.collect::<Vec<Gen3Partition>>();
if options.keep_modules {
return reduced;
}
if let Some(p) = reduced.iter_mut().find(|p| p.entry().name() == FTPR) {
let offset = p.entry().offset();
info!("FTPR @ {offset:08x}");
let retention_list = strs_to_strings(ALWAYS_RETAIN);
let mut cleaned = p.data().clone();
if let Gen3Partition::Dir(dir) = &p {
dir_clean(&dir.cpd, &retention_list, &mut cleaned);
}
p.set_data(cleaned);
}
reduced
}