diff --git a/Cargo.lock b/Cargo.lock index 513c432..64bf443 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,7 +294,7 @@ dependencies = [ [[package]] name = "icf-reader" -version = "0.1.1" +version = "0.1.2" dependencies = [ "aes", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index dc03785..656855f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "icf-reader" -version = "0.1.1" +version = "0.1.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index abf2382..d364975 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,14 @@ interface IcfInnerData { version: Version, required_system_version: Version, datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss + is_prerelease: boolean, } interface IcfOptionData { type: "Option", option_id: string, // Must be 4 characters datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss + is_prerelease: boolean, } interface IcfPatchData { @@ -55,6 +57,8 @@ interface IcfPatchData { target_version: Version, target_datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss target_required_system_version: Version, + + is_prerelease: boolean, } type IcfData = IcfInnerData | IcfOptionData | IcfPatchData; diff --git a/src/icf/mod.rs b/src/icf/mod.rs index 33ba7da..6d68518 100644 --- a/src/icf/mod.rs +++ b/src/icf/mod.rs @@ -129,13 +129,16 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result> { let mut entries: Vec = Vec::with_capacity(entry_count); for _ in 0..entry_count { - let sig = rd.read_bytes(4)?; - if sig[0] != 2 || sig[1] != 1 { + let sig = rd.read_u16()?; + if sig != 0x0102 && sig != 0x0201 { return Err(anyhow!( - "Container does not start with signature (0x0102), byte {:#06x}", + "Container does not start with signature (0x0102 or 0x0201), byte {:#06x}", rd.pos )); } + let _ = rd.read_bytes(2)?; + + let is_prerelease = sig == 0x0201; let container_type = rd.read_u32()?; for _ in 0..3 { @@ -162,12 +165,14 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result> { version, datetime, required_system_version, + is_prerelease, }), 0x0001 => IcfData::App(IcfInnerData { id: app_id.clone(), version, datetime, required_system_version, + is_prerelease, }), _ => unreachable!(), } @@ -188,6 +193,7 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result> { option_id, datetime, required_system_version, + is_prerelease, }) } _ => { @@ -219,6 +225,7 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result> { target_version, target_datetime, target_required_system_version, + is_prerelease, }) } }; @@ -263,7 +270,20 @@ pub fn serialize_icf(data: &[IcfData]) -> Result> { let mut app_id: Option = None; for container in data { - icf.extend([0x02, 0x01, 0x00, 0x00]); + // I don't think there's a really good way to do this? + // At least, not without breaking backwards compatibility. + let is_prerelease = match container { + IcfData::System(s) => s.is_prerelease, + IcfData::App(a) => a.is_prerelease, + IcfData::Option(o) => o.is_prerelease, + IcfData::Patch(p) => p.is_prerelease, + }; + + if is_prerelease { + icf.extend([0x01, 0x02, 0x00, 0x00]); + } else { + icf.extend([0x02, 0x01, 0x00, 0x00]); + } match container { IcfData::System(s) => { @@ -316,11 +336,19 @@ pub fn serialize_icf(data: &[IcfData]) -> Result> { None => return Err(anyhow!("Missing entry of type System in provided ICF data")), }; + if platform_id.len() != 3 { + return Err(anyhow!("Incorrect platform ID length: expected 3, got {}", platform_id.len())); + } + let app_id = match app_id { Some(s) => s, None => return Err(anyhow!("Missing entry of type App in provided ICF data")), }; + if app_id.len() != 4 { + return Err(anyhow!("Incorrect app ID length: expected 4, got {}", app_id.len())); + } + let mut containers_checksum: u32 = 0; for container in icf.chunks(0x40).skip(1) { if container[0] == 2 && container[1] == 1 { diff --git a/src/icf/models.rs b/src/icf/models.rs index 166042a..b86e47b 100644 --- a/src/icf/models.rs +++ b/src/icf/models.rs @@ -22,6 +22,9 @@ pub struct IcfInnerData { pub version: Version, pub required_system_version: Version, pub datetime: NaiveDateTime, + + #[serde(default = "default_is_prerelease")] + pub is_prerelease: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -35,6 +38,9 @@ pub struct IcfOptionData { pub required_system_version: Version, pub datetime: NaiveDateTime, + + #[serde(default = "default_is_prerelease")] + pub is_prerelease: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -51,6 +57,9 @@ pub struct IcfPatchData { pub target_version: Version, pub target_datetime: NaiveDateTime, pub target_required_system_version: Version, + + #[serde(default = "default_is_prerelease")] + pub is_prerelease: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -63,6 +72,15 @@ pub enum IcfData { } impl IcfData { + pub fn is_prerelease(&self) -> bool { + match self { + IcfData::System(s) => s.is_prerelease, + IcfData::App(a) => a.is_prerelease, + IcfData::Option(o) => o.is_prerelease, + IcfData::Patch(p) => p.is_prerelease, + } + } + pub fn filename(&self) -> String { match self { IcfData::System(data) => format!( @@ -103,4 +121,8 @@ fn empty_string() -> String { fn empty_version() -> Version { Version { major: 0, minor: 0, build: 0 } -} \ No newline at end of file +} + +fn default_is_prerelease() -> bool { + false +} diff --git a/src/main.rs b/src/main.rs index 0c3c67a..40a1470 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,7 +49,13 @@ fn main() -> Result<(), anyhow::Error> { let icf = parse_icf(&icf_buf)?; for entry in icf { - println!("{}", entry.filename()); + print!("{}", entry.filename()); + + if entry.is_prerelease() { + print!(" (PRERELEASE)"); + } + + println!(); } let encrypted_icf = encrypt_icf(&icf_buf, ICF_KEY, ICF_IV)?; @@ -72,7 +78,13 @@ fn main() -> Result<(), anyhow::Error> { } for entry in icf { - println!("{}", entry.filename()) + print!("{}", entry.filename()); + + if entry.is_prerelease() { + print!(" (PRERELEASE)"); + } + + println!(); } } Commands::Encode { json_input, output } => {