feat: Add support for prerelease entries

This commit is contained in:
beerpsi 2024-01-03 00:34:18 +07:00
parent be3b29047c
commit 2c3d165a70
6 changed files with 75 additions and 9 deletions

2
Cargo.lock generated
View File

@ -294,7 +294,7 @@ dependencies = [
[[package]] [[package]]
name = "icf-reader" name = "icf-reader"
version = "0.1.1" version = "0.1.2"
dependencies = [ dependencies = [
"aes", "aes",
"anyhow", "anyhow",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "icf-reader" name = "icf-reader"
version = "0.1.1" version = "0.1.2"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -35,12 +35,14 @@ interface IcfInnerData {
version: Version, version: Version,
required_system_version: Version, required_system_version: Version,
datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss
is_prerelease: boolean,
} }
interface IcfOptionData { interface IcfOptionData {
type: "Option", type: "Option",
option_id: string, // Must be 4 characters option_id: string, // Must be 4 characters
datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss
is_prerelease: boolean,
} }
interface IcfPatchData { interface IcfPatchData {
@ -55,6 +57,8 @@ interface IcfPatchData {
target_version: Version, target_version: Version,
target_datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss target_datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss
target_required_system_version: Version, target_required_system_version: Version,
is_prerelease: boolean,
} }
type IcfData = IcfInnerData | IcfOptionData | IcfPatchData; type IcfData = IcfInnerData | IcfOptionData | IcfPatchData;

View File

@ -129,13 +129,16 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result<Vec<IcfData>> {
let mut entries: Vec<IcfData> = Vec::with_capacity(entry_count); let mut entries: Vec<IcfData> = Vec::with_capacity(entry_count);
for _ in 0..entry_count { for _ in 0..entry_count {
let sig = rd.read_bytes(4)?; let sig = rd.read_u16()?;
if sig[0] != 2 || sig[1] != 1 { if sig != 0x0102 && sig != 0x0201 {
return Err(anyhow!( 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 rd.pos
)); ));
} }
let _ = rd.read_bytes(2)?;
let is_prerelease = sig == 0x0201;
let container_type = rd.read_u32()?; let container_type = rd.read_u32()?;
for _ in 0..3 { for _ in 0..3 {
@ -162,12 +165,14 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result<Vec<IcfData>> {
version, version,
datetime, datetime,
required_system_version, required_system_version,
is_prerelease,
}), }),
0x0001 => IcfData::App(IcfInnerData { 0x0001 => IcfData::App(IcfInnerData {
id: app_id.clone(), id: app_id.clone(),
version, version,
datetime, datetime,
required_system_version, required_system_version,
is_prerelease,
}), }),
_ => unreachable!(), _ => unreachable!(),
} }
@ -188,6 +193,7 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result<Vec<IcfData>> {
option_id, option_id,
datetime, datetime,
required_system_version, required_system_version,
is_prerelease,
}) })
} }
_ => { _ => {
@ -219,6 +225,7 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result<Vec<IcfData>> {
target_version, target_version,
target_datetime, target_datetime,
target_required_system_version, target_required_system_version,
is_prerelease,
}) })
} }
}; };
@ -263,7 +270,20 @@ pub fn serialize_icf(data: &[IcfData]) -> Result<Vec<u8>> {
let mut app_id: Option<String> = None; let mut app_id: Option<String> = None;
for container in data { 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 { match container {
IcfData::System(s) => { IcfData::System(s) => {
@ -316,11 +336,19 @@ pub fn serialize_icf(data: &[IcfData]) -> Result<Vec<u8>> {
None => return Err(anyhow!("Missing entry of type System in provided ICF data")), 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 { let app_id = match app_id {
Some(s) => s, Some(s) => s,
None => return Err(anyhow!("Missing entry of type App in provided ICF data")), 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; let mut containers_checksum: u32 = 0;
for container in icf.chunks(0x40).skip(1) { for container in icf.chunks(0x40).skip(1) {
if container[0] == 2 && container[1] == 1 { if container[0] == 2 && container[1] == 1 {

View File

@ -22,6 +22,9 @@ pub struct IcfInnerData {
pub version: Version, pub version: Version,
pub required_system_version: Version, pub required_system_version: Version,
pub datetime: NaiveDateTime, pub datetime: NaiveDateTime,
#[serde(default = "default_is_prerelease")]
pub is_prerelease: bool,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -35,6 +38,9 @@ pub struct IcfOptionData {
pub required_system_version: Version, pub required_system_version: Version,
pub datetime: NaiveDateTime, pub datetime: NaiveDateTime,
#[serde(default = "default_is_prerelease")]
pub is_prerelease: bool,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -51,6 +57,9 @@ pub struct IcfPatchData {
pub target_version: Version, pub target_version: Version,
pub target_datetime: NaiveDateTime, pub target_datetime: NaiveDateTime,
pub target_required_system_version: Version, pub target_required_system_version: Version,
#[serde(default = "default_is_prerelease")]
pub is_prerelease: bool,
} }
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -63,6 +72,15 @@ pub enum IcfData {
} }
impl 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 { pub fn filename(&self) -> String {
match self { match self {
IcfData::System(data) => format!( IcfData::System(data) => format!(
@ -103,4 +121,8 @@ fn empty_string() -> String {
fn empty_version() -> Version { fn empty_version() -> Version {
Version { major: 0, minor: 0, build: 0 } Version { major: 0, minor: 0, build: 0 }
} }
fn default_is_prerelease() -> bool {
false
}

View File

@ -49,7 +49,13 @@ fn main() -> Result<(), anyhow::Error> {
let icf = parse_icf(&icf_buf)?; let icf = parse_icf(&icf_buf)?;
for entry in icf { 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)?; let encrypted_icf = encrypt_icf(&icf_buf, ICF_KEY, ICF_IV)?;
@ -72,7 +78,13 @@ fn main() -> Result<(), anyhow::Error> {
} }
for entry in icf { for entry in icf {
println!("{}", entry.filename()) print!("{}", entry.filename());
if entry.is_prerelease() {
print!(" (PRERELEASE)");
}
println!();
} }
} }
Commands::Encode { json_input, output } => { Commands::Encode { json_input, output } => {