mirror of
https://gitea.tendokyu.moe/beerpsi/icf-reader.git
synced 2024-11-23 22:50:59 +01:00
Refactor + allow creating ICFs from JSON
This commit is contained in:
parent
a0808a7081
commit
0dff839654
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
||||
/target
|
||||
*.bin
|
||||
*.icf
|
257
Cargo.lock
generated
257
Cargo.lock
generated
@ -28,6 +28,54 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
@ -104,8 +152,9 @@ dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -118,6 +167,52 @@ dependencies = [
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
@ -162,6 +257,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "0.4.1"
|
||||
@ -200,8 +301,11 @@ dependencies = [
|
||||
"binary-reader",
|
||||
"cbc",
|
||||
"chrono",
|
||||
"clap",
|
||||
"crc32fast",
|
||||
"hex-literal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -214,6 +318,12 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.66"
|
||||
@ -252,27 +362,70 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.70"
|
||||
version = "1.0.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.41"
|
||||
name = "ryu"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
|
||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.194"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.194"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.110"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -291,6 +444,12 @@ version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
@ -357,7 +516,16 @@ version = "0.51.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -366,13 +534,28 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.0",
|
||||
"windows_aarch64_msvc 0.52.0",
|
||||
"windows_i686_gnu 0.52.0",
|
||||
"windows_i686_msvc 0.52.0",
|
||||
"windows_x86_64_gnu 0.52.0",
|
||||
"windows_x86_64_gnullvm 0.52.0",
|
||||
"windows_x86_64_msvc 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -381,38 +564,80 @@ version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
||||
|
@ -10,6 +10,9 @@ aes = "0.8.3"
|
||||
anyhow = "1.0.75"
|
||||
binary-reader = "0.4.5"
|
||||
cbc = { version = "0.1.2", features = ["std"] }
|
||||
chrono = "0.4.31"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
clap = { version = "4.4.12", features = ["derive"] }
|
||||
crc32fast = "1.3.2"
|
||||
hex-literal = "0.4.1"
|
||||
serde = { version = "1.0.194", features = ["derive"] }
|
||||
serde_json = "1.0.110"
|
||||
|
60
README.md
60
README.md
@ -4,13 +4,69 @@ Tools for decoding, decrypting and encrypting ICFs
|
||||
|
||||
## Usage
|
||||
```shell
|
||||
icf-reader <PATH TO ICF>
|
||||
icf-reader <decrypt | encrypt> <INPUT> <OUTPUT>
|
||||
Usage: icf-reader.exe <COMMAND>
|
||||
|
||||
Commands:
|
||||
encrypt Fixes some common ICF errors, then encrypt the given ICF
|
||||
decrypt Decrypts the given ICF
|
||||
decode Decodes the given ICF (optionally to a JSON file)
|
||||
encode Encodes a JSON file from the decode subcommand to an ICF
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-h, --help Print help
|
||||
```
|
||||
|
||||
Encrypting the ICF will also correct CRC checksums, so worrying about the
|
||||
correct checksum is not needed.
|
||||
|
||||
## Creating an ICF file from JSON
|
||||
The JSON file is an array of `IcfData`, which follows the format below:
|
||||
```typescript
|
||||
interface Version {
|
||||
major: number;
|
||||
minor: number;
|
||||
build: number;
|
||||
}
|
||||
|
||||
interface IcfInnerData {
|
||||
type: "System" | "App",
|
||||
id: string, // Must be 3 characters for "System" and 4 characters for "App"
|
||||
version: Version,
|
||||
required_system_version: Version,
|
||||
datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss
|
||||
}
|
||||
|
||||
interface IcfOptionData {
|
||||
type: "Option",
|
||||
app_id: string, // Does not go into the ICF, so can be anything, but must be specified
|
||||
option_id: string, // Must be 4 characters
|
||||
required_system_version: Version, // Can be zeroed out, e.g. { major: 0, minor: 0, build: 0 }
|
||||
datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss
|
||||
}
|
||||
|
||||
interface IcfPatchData {
|
||||
type: "Patch",
|
||||
id: string, // Does not go into the ICF, so can be anything, but must be specified
|
||||
|
||||
sequence_number: number, // Incremented for every patch, starting from 1
|
||||
|
||||
source_version: Version,
|
||||
source_datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss
|
||||
source_required_system_version: Version,
|
||||
|
||||
target_version: Version,
|
||||
target_datetime: string, // ISO8601 string yyyy-MM-dd'T'HH:mm:ss
|
||||
target_required_system_version: Version,
|
||||
}
|
||||
|
||||
type IcfData = IcfInnerData | IcfOptionData | IcfPatchData;
|
||||
```
|
||||
|
||||
At least one entry of type `System` and `App` must be specified.
|
||||
|
||||
When done, create your ICF using `icf-reader.exe encode input.json output.icf`.
|
||||
|
||||
## Building
|
||||
```
|
||||
ICF_KEY=<key> ICF_IV=<iv> cargo build --release
|
||||
|
74
src/icf/crypto.rs
Normal file
74
src/icf/crypto.rs
Normal file
@ -0,0 +1,74 @@
|
||||
use aes::cipher::{block_padding::NoPadding, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
|
||||
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
|
||||
|
||||
pub const ICF_KEY: [u8; 16] = hex_literal::decode(&[env!("ICF_KEY").as_bytes()]);
|
||||
pub const ICF_IV: [u8; 16] = hex_literal::decode(&[env!("ICF_IV").as_bytes()]);
|
||||
|
||||
/// Decrypts an ICF using the provided key and IV.
|
||||
pub fn decrypt_icf(
|
||||
data: &mut [u8],
|
||||
key: impl AsRef<[u8]>,
|
||||
iv: impl AsRef<[u8]>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let size = data.len();
|
||||
|
||||
let mut decrypted = Vec::with_capacity(size);
|
||||
|
||||
for i in (0..size).step_by(4096) {
|
||||
let from_start = i;
|
||||
|
||||
let bufsz = std::cmp::min(4096, size - from_start);
|
||||
let buf = &data[i..i + bufsz];
|
||||
let mut decbuf = vec![0; bufsz];
|
||||
|
||||
let cipher = Aes128CbcDec::new_from_slices(key.as_ref(), iv.as_ref())?;
|
||||
cipher
|
||||
.decrypt_padded_b2b_mut::<NoPadding>(buf, &mut decbuf)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
|
||||
let xor1 = u64::from_le_bytes(decbuf[0..8].try_into()?) ^ (from_start as u64);
|
||||
let xor2 = u64::from_le_bytes(decbuf[8..16].try_into()?) ^ (from_start as u64);
|
||||
|
||||
decrypted.extend(xor1.to_le_bytes());
|
||||
decrypted.extend(xor2.to_le_bytes());
|
||||
decrypted.extend(&decbuf[16..]);
|
||||
}
|
||||
|
||||
Ok(decrypted)
|
||||
}
|
||||
|
||||
/// Encrypts an ICF using the provided key and IV.
|
||||
pub fn encrypt_icf(data: &[u8], key: impl AsRef<[u8]>, iv: impl AsRef<[u8]>) -> Result<Vec<u8>> {
|
||||
let size = data.len();
|
||||
|
||||
let mut encrypted = Vec::with_capacity(size);
|
||||
let mut to_be_encrypted = Vec::with_capacity(std::cmp::min(4096, size));
|
||||
|
||||
for i in (0..size).step_by(4096) {
|
||||
let from_start = i;
|
||||
|
||||
let bufsz = std::cmp::min(4096, size - from_start);
|
||||
let buf = &data[i..i + bufsz];
|
||||
let mut encbuf = vec![0; bufsz];
|
||||
|
||||
let xor1 = u64::from_le_bytes(buf[0..8].try_into()?) ^ (from_start as u64);
|
||||
let xor2 = u64::from_le_bytes(buf[8..16].try_into()?) ^ (from_start as u64);
|
||||
|
||||
to_be_encrypted.extend(xor1.to_le_bytes());
|
||||
to_be_encrypted.extend(xor2.to_le_bytes());
|
||||
to_be_encrypted.extend(&buf[16..]);
|
||||
|
||||
let cipher = Aes128CbcEnc::new_from_slices(key.as_ref(), iv.as_ref())?;
|
||||
cipher
|
||||
.encrypt_padded_b2b_mut::<NoPadding>(&to_be_encrypted, &mut encbuf)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
|
||||
encrypted.extend(encbuf);
|
||||
to_be_encrypted.clear();
|
||||
}
|
||||
|
||||
Ok(encrypted)
|
||||
}
|
@ -1,166 +1,22 @@
|
||||
use std::fmt::Display;
|
||||
pub mod crypto;
|
||||
pub mod models;
|
||||
pub mod parser;
|
||||
|
||||
use aes::cipher::{block_padding::NoPadding, BlockDecryptMut, KeyIvInit, BlockEncryptMut};
|
||||
use anyhow::{anyhow, Result};
|
||||
use binary_reader::{BinaryReader, Endian};
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
use chrono::{Datelike, Timelike, NaiveDateTime};
|
||||
|
||||
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
|
||||
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
|
||||
use self::crypto::{decrypt_icf, ICF_IV, ICF_KEY};
|
||||
use self::models::{IcfData, IcfInnerData, IcfOptionData, IcfPatchData, Version};
|
||||
use self::parser::{decode_icf_datetime, decode_icf_version};
|
||||
|
||||
pub const ICF_KEY: [u8; 16] = hex_literal::decode(&[env!("ICF_KEY").as_bytes()]);
|
||||
pub const ICF_IV: [u8; 16] = hex_literal::decode(&[env!("ICF_IV").as_bytes()]);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Version {
|
||||
pub major: u16,
|
||||
pub minor: u8,
|
||||
pub build: u8,
|
||||
}
|
||||
|
||||
impl Display for Version {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}.{:0>2}.{:0>2}", self.major, self.minor, self.build)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct IcfInnerData {
|
||||
pub id: String,
|
||||
pub version: Version,
|
||||
pub required_system_version: Version,
|
||||
pub datetime: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct IcfOptionData {
|
||||
pub app_id: String,
|
||||
pub option_id: String,
|
||||
pub required_system_version: Version,
|
||||
pub datetime: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct IcfPatchData {
|
||||
pub id: String,
|
||||
pub source_version: Version,
|
||||
pub target_version: Version,
|
||||
pub required_system_version: Version,
|
||||
pub datetime: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum IcfData {
|
||||
System(IcfInnerData),
|
||||
App(IcfInnerData),
|
||||
Patch(IcfPatchData),
|
||||
Option(IcfOptionData),
|
||||
}
|
||||
|
||||
pub fn decrypt_icf(data: &mut [u8], key: impl AsRef<[u8]>, iv: impl AsRef<[u8]>) -> Result<Vec<u8>> {
|
||||
let size = data.len();
|
||||
|
||||
let mut decrypted = Vec::with_capacity(size);
|
||||
|
||||
for i in (0..size).step_by(4096) {
|
||||
let from_start = i;
|
||||
|
||||
let bufsz = std::cmp::min(4096, size - from_start);
|
||||
let buf = &data[i..i + bufsz];
|
||||
let mut decbuf = vec![0; bufsz];
|
||||
|
||||
let cipher = Aes128CbcDec::new_from_slices(key.as_ref(), iv.as_ref())?;
|
||||
cipher
|
||||
.decrypt_padded_b2b_mut::<NoPadding>(buf, &mut decbuf)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
|
||||
let xor1 = u64::from_le_bytes(decbuf[0..8].try_into()?) ^ (from_start as u64);
|
||||
let xor2 = u64::from_le_bytes(decbuf[8..16].try_into()?) ^ (from_start as u64);
|
||||
|
||||
decrypted.extend(xor1.to_le_bytes());
|
||||
decrypted.extend(xor2.to_le_bytes());
|
||||
decrypted.extend(&decbuf[16..]);
|
||||
}
|
||||
|
||||
Ok(decrypted)
|
||||
}
|
||||
|
||||
pub fn encrypt_icf(data: &[u8], key: impl AsRef<[u8]>, iv: impl AsRef<[u8]>) -> Result<Vec<u8>> {
|
||||
let size = data.len();
|
||||
|
||||
let mut encrypted = Vec::with_capacity(size);
|
||||
|
||||
for i in (0..size).step_by(4096) {
|
||||
let from_start = i;
|
||||
|
||||
let bufsz = std::cmp::min(4096, size - from_start);
|
||||
let buf = &data[i..i + bufsz];
|
||||
let mut to_be_encrypted = Vec::with_capacity(bufsz);
|
||||
let mut encbuf = vec![0; bufsz];
|
||||
|
||||
let xor1 = u64::from_le_bytes(buf[0..8].try_into()?) ^ (from_start as u64);
|
||||
let xor2 = u64::from_le_bytes(buf[8..16].try_into()?) ^ (from_start as u64);
|
||||
|
||||
to_be_encrypted.extend(xor1.to_le_bytes());
|
||||
to_be_encrypted.extend(xor2.to_le_bytes());
|
||||
to_be_encrypted.extend(&buf[16..]);
|
||||
|
||||
let cipher = Aes128CbcEnc::new_from_slices(key.as_ref(), iv.as_ref())?;
|
||||
cipher
|
||||
.encrypt_padded_b2b_mut::<NoPadding>(&to_be_encrypted, &mut encbuf)
|
||||
.map_err(|err| anyhow!(err))?;
|
||||
|
||||
encrypted.extend(encbuf);
|
||||
}
|
||||
|
||||
Ok(encrypted)
|
||||
}
|
||||
|
||||
pub fn decode_icf_datetime_version(
|
||||
rd: &mut BinaryReader,
|
||||
) -> Result<(NaiveDateTime, Version)> {
|
||||
let datetime = NaiveDate::from_ymd_opt(
|
||||
rd.read_i16()? as i32,
|
||||
rd.read_u8()? as u32,
|
||||
rd.read_u8()? as u32,
|
||||
)
|
||||
.ok_or(anyhow!("Invalid date"))?
|
||||
.and_hms_milli_opt(
|
||||
rd.read_u8()? as u32,
|
||||
rd.read_u8()? as u32,
|
||||
rd.read_u8()? as u32,
|
||||
rd.read_u8()? as u32,
|
||||
)
|
||||
.ok_or(anyhow!("Invalid time"))?;
|
||||
|
||||
let required_system_version = Version {
|
||||
build: rd.read_u8()?,
|
||||
minor: rd.read_u8()?,
|
||||
major: rd.read_u16()?,
|
||||
};
|
||||
|
||||
Ok((datetime, required_system_version))
|
||||
}
|
||||
|
||||
pub fn decode_icf_version(
|
||||
rd: &mut BinaryReader,
|
||||
) -> Result<Version> {
|
||||
let version = Version {
|
||||
build: rd.read_u8()?,
|
||||
minor: rd.read_u8()?,
|
||||
major: rd.read_u16()?,
|
||||
};
|
||||
|
||||
Ok(version)
|
||||
}
|
||||
|
||||
/// Fixes incorrect CRC32s caused by hex editing the ICF
|
||||
/// Fixes incorrect metadata caused by hex editing the ICF
|
||||
pub fn fixup_icf(data: &mut [u8]) -> Result<()> {
|
||||
let mut rd = BinaryReader::from_u8(data);
|
||||
rd.endian = Endian::Little;
|
||||
|
||||
let reported_icf_crc = rd.read_u32()?;
|
||||
|
||||
|
||||
let reported_size = rd.read_u32()?;
|
||||
let actual_size = data.len() as u32;
|
||||
if actual_size != reported_size {
|
||||
@ -179,7 +35,7 @@ pub fn fixup_icf(data: &mut [u8]) -> Result<()> {
|
||||
println!("[WARN] Expected size {expected_size} ({entry_count} entries) does not match actual size {actual_size}, automatically fixing");
|
||||
|
||||
let actual_entry_count = actual_size as u64 / 0x40 - 1;
|
||||
|
||||
|
||||
data[16..24].copy_from_slice(&actual_entry_count.to_le_bytes());
|
||||
|
||||
actual_entry_count
|
||||
@ -275,7 +131,10 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result<Vec<IcfData>> {
|
||||
for _ in 0..entry_count {
|
||||
let sig = rd.read_bytes(4)?;
|
||||
if sig[0] != 2 || sig[1] != 1 {
|
||||
return Err(anyhow!("Container does not start with signature (0x0102), byte {:#06x}", rd.pos));
|
||||
return Err(anyhow!(
|
||||
"Container does not start with signature (0x0102), byte {:#06x}",
|
||||
rd.pos
|
||||
));
|
||||
}
|
||||
|
||||
let container_type = rd.read_u32()?;
|
||||
@ -288,7 +147,8 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result<Vec<IcfData>> {
|
||||
let data: IcfData = match container_type {
|
||||
0x0000 | 0x0001 => {
|
||||
let version = decode_icf_version(&mut rd)?;
|
||||
let (datetime, required_system_version) = decode_icf_datetime_version(&mut rd)?;
|
||||
let datetime = decode_icf_datetime(&mut rd)?;
|
||||
let required_system_version = decode_icf_version(&mut rd)?;
|
||||
|
||||
for _ in 0..2 {
|
||||
if rd.read_u64()? != 0 {
|
||||
@ -309,12 +169,13 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result<Vec<IcfData>> {
|
||||
datetime,
|
||||
required_system_version,
|
||||
}),
|
||||
_ => unreachable!()
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
0x0002 => {
|
||||
let option_id = String::from_utf8(rd.read_bytes(4)?.to_vec())?;
|
||||
let (datetime, required_system_version) = decode_icf_datetime_version(&mut rd)?;
|
||||
let datetime = decode_icf_datetime(&mut rd)?;
|
||||
let required_system_version = decode_icf_version(&mut rd)?;
|
||||
|
||||
for _ in 0..2 {
|
||||
if rd.read_u64()? != 0 {
|
||||
@ -328,28 +189,38 @@ pub fn parse_icf(data: impl AsRef<[u8]>) -> Result<Vec<IcfData>> {
|
||||
datetime,
|
||||
required_system_version,
|
||||
})
|
||||
|
||||
}
|
||||
0x0101 => {
|
||||
_ => {
|
||||
// PATCH container type also encode the patch's sequence number
|
||||
// in the higher 16 bits.
|
||||
// The lower 16 bits will always be 1.
|
||||
let sequence_number = (container_type >> 8) as u8;
|
||||
|
||||
if (container_type & 1) == 0 || sequence_number == 0 {
|
||||
println!("Unknown ICF container type {container_type:#06x} at byte {:#06x}, skipping", rd.pos);
|
||||
rd.read_bytes(32)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let target_version = decode_icf_version(&mut rd)?;
|
||||
let (datetime, required_system_version) = decode_icf_datetime_version(&mut rd)?;
|
||||
let target_datetime = decode_icf_datetime(&mut rd)?;
|
||||
let target_required_system_version = decode_icf_version(&mut rd)?;
|
||||
|
||||
let source_version = decode_icf_version(&mut rd)?;
|
||||
let (_, _) = decode_icf_datetime_version(&mut rd)?;
|
||||
let source_datetime = decode_icf_datetime(&mut rd)?;
|
||||
let source_required_system_version = decode_icf_version(&mut rd)?;
|
||||
|
||||
IcfData::Patch(IcfPatchData {
|
||||
id: app_id.clone(),
|
||||
sequence_number,
|
||||
source_version,
|
||||
source_datetime,
|
||||
source_required_system_version,
|
||||
target_version,
|
||||
required_system_version,
|
||||
datetime,
|
||||
target_datetime,
|
||||
target_required_system_version,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
println!("Unknown ICF container type {container_type:#06x} at byte {:#06x}, skipping", rd.pos);
|
||||
rd.read_bytes(32)?;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
entries.push(data);
|
||||
@ -363,3 +234,109 @@ pub fn decode_icf(data: &mut [u8]) -> Result<Vec<IcfData>> {
|
||||
|
||||
parse_icf(decrypted)
|
||||
}
|
||||
|
||||
pub fn serialize_datetime(data: &mut Vec<u8>, datetime: NaiveDateTime) {
|
||||
data.extend((datetime.year() as u16).to_le_bytes());
|
||||
data.extend([
|
||||
datetime.month() as u8,
|
||||
datetime.day() as u8,
|
||||
datetime.hour() as u8,
|
||||
datetime.minute() as u8,
|
||||
datetime.second() as u8,
|
||||
0x00,
|
||||
]);
|
||||
}
|
||||
|
||||
pub fn serialize_version(data: &mut Vec<u8>, version: Version) {
|
||||
data.extend([version.build, version.minor]);
|
||||
data.extend(version.major.to_le_bytes());
|
||||
}
|
||||
|
||||
pub fn serialize_icf(data: &[IcfData]) -> Result<Vec<u8>> {
|
||||
let entry_count = data.len();
|
||||
let icf_length = 0x40 * (entry_count + 1);
|
||||
let mut icf: Vec<u8> = Vec::with_capacity(icf_length);
|
||||
|
||||
icf.extend([0x00; 0x40]);
|
||||
|
||||
let mut platform_id: Option<String> = None;
|
||||
let mut app_id: Option<String> = None;
|
||||
|
||||
for container in data {
|
||||
icf.extend([0x02, 0x01, 0x00, 0x00]);
|
||||
|
||||
match container {
|
||||
IcfData::System(s) => {
|
||||
platform_id = Some(s.id.clone());
|
||||
icf.extend([0x00; 4]);
|
||||
}
|
||||
IcfData::App(a) => {
|
||||
app_id = Some(a.id.clone());
|
||||
icf.extend([0x01, 0x00, 0x00, 0x00]);
|
||||
}
|
||||
IcfData::Option(_) => {
|
||||
icf.extend([0x02, 0x00, 0x00, 0x00]);
|
||||
}
|
||||
IcfData::Patch(p) => {
|
||||
icf.extend([0x01, p.sequence_number, 0x00, 0x00]);
|
||||
}
|
||||
}
|
||||
|
||||
icf.extend([0x00; 24]);
|
||||
|
||||
if let IcfData::Option(o) = container {
|
||||
icf.extend(o.option_id.as_bytes());
|
||||
serialize_datetime(&mut icf, o.datetime);
|
||||
icf.extend([0x00; 20]);
|
||||
continue;
|
||||
}
|
||||
|
||||
let (version, datetime, required_system_version) = match container {
|
||||
IcfData::System(s) => (s.version, s.datetime, s.required_system_version),
|
||||
IcfData::App(s) => (s.version, s.datetime, s.required_system_version),
|
||||
IcfData::Patch(s) => (s.target_version, s.target_datetime, s.target_required_system_version),
|
||||
IcfData::Option(_) => unreachable!(),
|
||||
};
|
||||
|
||||
serialize_version(&mut icf, version);
|
||||
serialize_datetime(&mut icf, datetime);
|
||||
serialize_version(&mut icf, required_system_version);
|
||||
|
||||
if let IcfData::Patch(p) = container {
|
||||
serialize_version(&mut icf, p.source_version);
|
||||
serialize_datetime(&mut icf, p.source_datetime);
|
||||
serialize_version(&mut icf, p.source_required_system_version);
|
||||
} else {
|
||||
icf.extend([0x00; 16]);
|
||||
}
|
||||
}
|
||||
|
||||
let platform_id = match platform_id {
|
||||
Some(s) => s,
|
||||
None => return Err(anyhow!("Missing entry of type System in provided ICF data")),
|
||||
};
|
||||
|
||||
let app_id = match app_id {
|
||||
Some(s) => s,
|
||||
None => return Err(anyhow!("Missing entry of type App in provided ICF data")),
|
||||
};
|
||||
|
||||
let mut containers_checksum: u32 = 0;
|
||||
for container in icf.chunks(0x40).skip(1) {
|
||||
if container[0] == 2 && container[1] == 1 {
|
||||
containers_checksum ^= crc32fast::hash(container);
|
||||
}
|
||||
}
|
||||
|
||||
icf[4..8].copy_from_slice(&(icf_length as u32).to_le_bytes());
|
||||
icf[16..24].copy_from_slice(&(entry_count as u64).to_le_bytes());
|
||||
icf[24..28].copy_from_slice(app_id.as_bytes());
|
||||
icf[28..31].copy_from_slice(platform_id.as_bytes());
|
||||
icf[32..36].copy_from_slice(&containers_checksum.to_le_bytes());
|
||||
|
||||
let icf_crc = crc32fast::hash(&icf[4..]);
|
||||
|
||||
icf[0..4].copy_from_slice(&icf_crc.to_le_bytes());
|
||||
|
||||
Ok(icf)
|
||||
}
|
92
src/icf/models.rs
Normal file
92
src/icf/models.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use chrono::NaiveDateTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Version {
|
||||
pub major: u16,
|
||||
pub minor: u8,
|
||||
pub build: u8,
|
||||
}
|
||||
|
||||
impl Display for Version {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}.{:0>2}.{:0>2}", self.major, self.minor, self.build)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct IcfInnerData {
|
||||
pub id: String,
|
||||
pub version: Version,
|
||||
pub required_system_version: Version,
|
||||
pub datetime: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct IcfOptionData {
|
||||
pub app_id: String,
|
||||
pub option_id: String,
|
||||
pub required_system_version: Version,
|
||||
pub datetime: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct IcfPatchData {
|
||||
pub id: String,
|
||||
|
||||
pub sequence_number: u8,
|
||||
|
||||
pub source_version: Version,
|
||||
pub source_datetime: NaiveDateTime,
|
||||
pub source_required_system_version: Version,
|
||||
|
||||
pub target_version: Version,
|
||||
pub target_datetime: NaiveDateTime,
|
||||
pub target_required_system_version: Version,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum IcfData {
|
||||
System(IcfInnerData),
|
||||
App(IcfInnerData),
|
||||
Patch(IcfPatchData),
|
||||
Option(IcfOptionData),
|
||||
}
|
||||
|
||||
impl IcfData {
|
||||
pub fn filename(&self) -> String {
|
||||
match self {
|
||||
IcfData::System(data) => format!(
|
||||
"{}_{:04}.{:02}.{:02}_{}_0.pack",
|
||||
data.id,
|
||||
data.version.major,
|
||||
data.version.minor,
|
||||
data.version.build,
|
||||
data.datetime.format("%Y%m%d%H%M%S")
|
||||
),
|
||||
IcfData::App(data) => format!(
|
||||
"{}_{}_{}_0.app",
|
||||
data.id,
|
||||
data.version,
|
||||
data.datetime.format("%Y%m%d%H%M%S")
|
||||
),
|
||||
IcfData::Option(data) => format!(
|
||||
"{}_{}_{}_0.opt",
|
||||
data.app_id,
|
||||
data.option_id,
|
||||
data.datetime.format("%Y%m%d%H%M%S")
|
||||
),
|
||||
IcfData::Patch(data) => format!(
|
||||
"{}_{}_{}_{}_{}.app",
|
||||
data.id,
|
||||
data.target_version,
|
||||
data.target_datetime.format("%Y%m%d%H%M%S"),
|
||||
data.sequence_number,
|
||||
data.source_version,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
33
src/icf/parser.rs
Normal file
33
src/icf/parser.rs
Normal file
@ -0,0 +1,33 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use binary_reader::BinaryReader;
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
|
||||
use super::models::Version;
|
||||
|
||||
pub fn decode_icf_datetime(rd: &mut BinaryReader) -> Result<NaiveDateTime> {
|
||||
let datetime = NaiveDate::from_ymd_opt(
|
||||
rd.read_i16()? as i32,
|
||||
rd.read_u8()? as u32,
|
||||
rd.read_u8()? as u32,
|
||||
)
|
||||
.ok_or(anyhow!("Invalid date"))?
|
||||
.and_hms_milli_opt(
|
||||
rd.read_u8()? as u32,
|
||||
rd.read_u8()? as u32,
|
||||
rd.read_u8()? as u32,
|
||||
rd.read_u8()? as u32,
|
||||
)
|
||||
.ok_or(anyhow!("Invalid time"))?;
|
||||
|
||||
Ok(datetime)
|
||||
}
|
||||
|
||||
pub fn decode_icf_version(rd: &mut BinaryReader) -> Result<Version> {
|
||||
let version = Version {
|
||||
build: rd.read_u8()?,
|
||||
minor: rd.read_u8()?,
|
||||
major: rd.read_u16()?,
|
||||
};
|
||||
|
||||
Ok(version)
|
||||
}
|
123
src/main.rs
123
src/main.rs
@ -1,67 +1,90 @@
|
||||
use std::{env, process::exit};
|
||||
|
||||
use anyhow::anyhow;
|
||||
|
||||
use icf::{decode_icf, decrypt_icf, ICF_KEY, ICF_IV, parse_icf, encrypt_icf, fixup_icf};
|
||||
use crate::icf::IcfData;
|
||||
|
||||
mod icf;
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use icf::serialize_icf;
|
||||
|
||||
use crate::icf::{
|
||||
crypto::{decrypt_icf, encrypt_icf, ICF_IV, ICF_KEY},
|
||||
decode_icf, fixup_icf,
|
||||
models::IcfData,
|
||||
parse_icf,
|
||||
};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
#[command(about = "Fixes some common ICF errors, then encrypt the given ICF")]
|
||||
Encrypt { input: String, output: String },
|
||||
|
||||
#[command(about = "Decrypts the given ICF")]
|
||||
Decrypt { input: String, output: String },
|
||||
|
||||
#[command(about = "Decodes the given ICF (optionally to a JSON file)")]
|
||||
Decode {
|
||||
icf: String,
|
||||
json_output: Option<String>,
|
||||
},
|
||||
|
||||
#[command(about = "Encodes a JSON file from the decode subcommand to an ICF")]
|
||||
Encode {
|
||||
json_input: String,
|
||||
output: String,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() < 2 {
|
||||
println!("Usage: icf-reader.exe <PATH TO ICF>");
|
||||
exit(1);
|
||||
}
|
||||
let cli = Cli::parse();
|
||||
|
||||
if args[1] == "decrypt" {
|
||||
if args.len() < 4 {
|
||||
println!("Usage: icf-reader.exe decrypt <PATH TO ICF> <OUTPUT>");
|
||||
exit(1);
|
||||
match &cli.command {
|
||||
Commands::Encrypt { input, output } => {
|
||||
let mut icf_buf = std::fs::read(input)?;
|
||||
fixup_icf(&mut icf_buf)?;
|
||||
let icf = parse_icf(&icf_buf)?;
|
||||
|
||||
for entry in icf {
|
||||
println!("{}", entry.filename());
|
||||
}
|
||||
|
||||
let encrypted_icf = encrypt_icf(&icf_buf, ICF_KEY, ICF_IV)?;
|
||||
|
||||
std::fs::write(output, encrypted_icf)?;
|
||||
}
|
||||
Commands::Decrypt { input, output } => {
|
||||
let mut icf_buf = std::fs::read(input)?;
|
||||
let decrypted_icf = decrypt_icf(&mut icf_buf, ICF_KEY, ICF_IV)?;
|
||||
|
||||
let mut icf_buf = std::fs::read(args[2].clone())?;
|
||||
let decrypted_icf = decrypt_icf(&mut icf_buf, ICF_KEY, ICF_IV)?;
|
||||
|
||||
std::fs::write(args[3].clone(), decrypted_icf)?;
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if args[1] == "encrypt" {
|
||||
if args.len() < 4 {
|
||||
println!("Usage: icf-reader.exe encrypt <PATH TO ICF> <OUTPUT>");
|
||||
exit(1);
|
||||
std::fs::write(output, decrypted_icf)?;
|
||||
}
|
||||
Commands::Decode { icf, json_output } => {
|
||||
let mut icf_buf = std::fs::read(icf)?;
|
||||
let icf = decode_icf(&mut icf_buf)?;
|
||||
|
||||
let mut icf_buf = std::fs::read(args[2].clone())?;
|
||||
fixup_icf(&mut icf_buf)?;
|
||||
let icf = parse_icf(&icf_buf)?;
|
||||
if let Some(json_output) = json_output {
|
||||
let f = File::create(json_output)?;
|
||||
serde_json::to_writer_pretty(f, &icf)?;
|
||||
}
|
||||
|
||||
for entry in icf {
|
||||
match entry {
|
||||
IcfData::System(data) => println!("{}_{:04}.{:02}.{:02}_{}_0.pack", data.id, data.version.major, data.version.minor, data.version.build, data.datetime.format("%Y%m%d%H%M%S")),
|
||||
IcfData::App(data) => println!("{}_{}.{:02}.{:02}_{}_0.app", data.id, data.version.major, data.version.minor, data.version.build, data.datetime.format("%Y%m%d%H%M%S")),
|
||||
IcfData::Option(data) => println!("{}_{}_{}_0.opt", data.app_id, data.option_id, data.datetime.format("%Y%m%d%H%M%S")),
|
||||
IcfData::Patch(data) => println!("{}_{}.{:02}.{:02}_{}_1_{}.{:02}.{:02}.app", data.id, data.target_version.major, data.target_version.minor, data.target_version.build, data.datetime.format("%Y%m%d%H%M%S"), data.required_system_version.major, data.required_system_version.minor, data.required_system_version.build),
|
||||
for entry in icf {
|
||||
println!("{}", entry.filename())
|
||||
}
|
||||
}
|
||||
Commands::Encode { json_input, output } => {
|
||||
let input = std::fs::read_to_string(json_input)?;
|
||||
let icf: Vec<IcfData> = serde_json::from_str(&input)?;
|
||||
let out = serialize_icf(&icf)?;
|
||||
|
||||
let encrypted_icf = encrypt_icf(&icf_buf, ICF_KEY, ICF_IV)?;
|
||||
std::fs::write("test.bin", &out)?;
|
||||
|
||||
std::fs::write(args[3].clone(), encrypted_icf)?;
|
||||
let encrypted = encrypt_icf(&out, ICF_KEY, ICF_IV)?;
|
||||
|
||||
exit(0)
|
||||
}
|
||||
|
||||
let mut icf_buf = std::fs::read(args[1].clone())?;
|
||||
let icf = decode_icf(&mut icf_buf).map_err(|err| anyhow!("Reading ICF failed: {:#}", err))?;
|
||||
|
||||
for entry in icf {
|
||||
match entry {
|
||||
IcfData::System(data) => println!("{}_{:04}.{:02}.{:02}_{}_0.pack", data.id, data.version.major, data.version.minor, data.version.build, data.datetime.format("%Y%m%d%H%M%S")),
|
||||
IcfData::App(data) => println!("{}_{}.{:02}.{:02}_{}_0.app", data.id, data.version.major, data.version.minor, data.version.build, data.datetime.format("%Y%m%d%H%M%S")),
|
||||
IcfData::Option(data) => println!("{}_{}_{}_0.opt", data.app_id, data.option_id, data.datetime.format("%Y%m%d%H%M%S")),
|
||||
IcfData::Patch(data) => println!("{}_{}.{:02}.{:02}_{}_1_{}.{:02}.{:02}.app", data.id, data.target_version.major, data.target_version.minor, data.target_version.build, data.datetime.format("%Y%m%d%H%M%S"), data.required_system_version.major, data.required_system_version.minor, data.required_system_version.build),
|
||||
std::fs::write(output, encrypted)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user