1use std::fmt;
2
3use zerocopy::{
4 FromBytes, Immutable, IntoBytes, KnownLayout, TryFromBytes, Unaligned,
5 byteorder::big_endian::{U16, U32, U64},
6};
7
8#[derive(Debug, snafu::Snafu)]
10#[non_exhaustive]
11pub enum ReadError {
12 #[snafu(display("PKG file is too small"))]
13 TooSmall,
14 #[snafu(display("invalid PKG magic"))]
15 InvalidMagic,
16
17 #[snafu(display("invalid source bytes"))]
18 InvalidSourceBytes,
19}
20
21type Result<T, E = ReadError> = std::result::Result<T, E>;
22
23const PKG_MAGIC: u32 = 0x7F434E54;
24
25#[derive(Debug, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
26#[repr(C)]
27pub struct PkgHeaderRaw {
28 pub pkg_magic: U32, pub pkg_type: U32, pub pkg_0x008: U32, pub pkg_file_count: U32, pub pkg_entry_count: U32, pub pkg_sc_entry_count: U16, pub pkg_entry_count_2: U16, pub pkg_table_offset: U32, pub pkg_entry_data_size: U32, pub pkg_body_offset: U64, pub pkg_body_size: U64, pub pkg_content_offset: U64, pub pkg_content_size: U64, pub pkg_content_id: ContentId, pub pkg_padding: [u8; 0xC], pub pkg_drm_type: U32, pub pkg_content_type: U32, pub pkg_content_flags: U32, pub pkg_promote_size: U32, pub pkg_version_date: U32, pub pkg_version_hash: U32, pub pkg_0x088: U32, pub pkg_0x08c: U32, pub pkg_0x090: U32, pub pkg_0x094: U32, pub pkg_iro_tag: U32, pub pkg_drm_type_version: U32, pub padding_0x0a0: [u8; 0x60],
59
60 pub digest_table: DigestTable,
62
63 pub padding_0x180: [u8; 0x284],
65
66 pub pfs_image_count: U32, pub pfs_image_flags: U64, pub pfs_image_offset: U64, pub pfs_image_size: U64, pub mount_image_offset: U64, pub mount_image_size: U64, pub pkg_size: U64, pub pfs_signed_size: U32, pub pfs_cache_size: U32, pub pfs_image_digest: [u8; 0x20], pub pfs_signed_digest: [u8; 0x20], pub pfs_split_size_nth_0: U64, pub pfs_split_size_nth_1: U64, pub padding_0x490: [u8; 0xB50],
83
84 pub pkg_digest: [u8; 0x20], }
88
89#[derive(
94 Clone,
95 Copy,
96 Default,
97 PartialEq,
98 Eq,
99 PartialOrd,
100 Ord,
101 Hash,
102 FromBytes,
103 IntoBytes,
104 KnownLayout,
105 Immutable,
106 Unaligned,
107)]
108#[repr(C)]
109pub struct ContentId {
110 service_id: [u8; 2],
112 publisher_code: [u8; 4],
114 _sep1: u8,
116 title_id: [u8; 9],
118 _sep2: u8,
120 version: [u8; 2],
122 _sep3: u8,
124 label: [u8; 16],
126}
127
128impl ContentId {
129 #[must_use]
131 pub fn service_id(&self) -> &str {
132 std::str::from_utf8(&self.service_id).unwrap_or("")
133 }
134
135 #[must_use]
137 pub fn publisher_code(&self) -> &str {
138 std::str::from_utf8(&self.publisher_code).unwrap_or("")
139 }
140
141 #[must_use]
143 pub fn title_id(&self) -> &str {
144 let bytes = &self.title_id;
145 let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
146 std::str::from_utf8(&bytes[..len]).unwrap_or("")
147 }
148
149 #[must_use]
151 pub fn version(&self) -> &str {
152 std::str::from_utf8(&self.version).unwrap_or("")
153 }
154
155 #[must_use]
157 pub fn label(&self) -> &str {
158 let bytes = &self.label;
159 let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
160 std::str::from_utf8(&bytes[..len]).unwrap_or("")
161 }
162
163 #[must_use]
165 pub fn as_str(&self) -> &str {
166 let bytes = self.as_bytes();
167 let len = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
168 std::str::from_utf8(&bytes[..len]).unwrap_or("<invalid>")
169 }
170}
171
172impl fmt::Display for ContentId {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 write!(f, "{}", self.as_str())
175 }
176}
177
178impl fmt::Debug for ContentId {
179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
180 f.debug_struct("ContentId")
181 .field("service_id", &self.service_id())
182 .field("publisher_code", &self.publisher_code())
183 .field("title_id", &self.title_id())
184 .field("version", &self.version())
185 .field("label", &self.label())
186 .finish()
187 }
188}
189
190#[derive(
191 Clone,
192 Copy,
193 Debug,
194 Default,
195 PartialEq,
196 Eq,
197 PartialOrd,
198 Ord,
199 Hash,
200 FromBytes,
201 IntoBytes,
202 KnownLayout,
203 Immutable,
204)]
205#[repr(C)]
206pub struct ContentFlags(u32);
207
208bitflags::bitflags! {
209 impl ContentFlags: u32 {
210 const FIRST_PATCH = 0x00100000;
211 const PATCHGO = 0x00200000;
212 const REMASTER = 0x00400000;
213 const PS_CLOUD = 0x00800000;
214 const DELTA_PATCH_X = 0x01000000;
215 const GD_AC = 0x02000000;
216 const NON_GAME = 0x04000000;
217 const UNKNOWN_1 = 0x08000000;
218 const UNKNOWN_2 = 0x10000000;
219 const CUMULATIVE_PATCH_X = 0x20000000;
220 const SUBSEQUENT_PATCH = 0x40000000;
221 const DELTA_PATCH = 0x41000000;
222 const CUMULATIVE_PATCH = 0x60000000;
223 }
224}
225
226impl fmt::Display for ContentFlags {
227 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
228 if self.is_empty() {
229 return write!(f, "(none)");
230 }
231
232 let mut first = true;
233 let mut write_flag = |name: &str| -> fmt::Result {
234 if !first {
235 write!(f, ", ")?;
236 }
237 first = false;
238 write!(f, "{}", name)
239 };
240
241 if self.contains(Self::CUMULATIVE_PATCH) {
243 write_flag("Cumulative Patch")?;
244 } else if self.contains(Self::DELTA_PATCH) {
245 write_flag("Delta Patch")?;
246 } else {
247 if self.contains(Self::FIRST_PATCH) {
249 write_flag("First Patch")?;
250 }
251 if self.contains(Self::PATCHGO) {
252 write_flag("PatchGo")?;
253 }
254 if self.contains(Self::REMASTER) {
255 write_flag("Remaster")?;
256 }
257 if self.contains(Self::PS_CLOUD) {
258 write_flag("PS Cloud")?;
259 }
260 if self.contains(Self::DELTA_PATCH_X) {
261 write_flag("Delta Patch X")?;
262 }
263 if self.contains(Self::GD_AC) {
264 write_flag("GD/AC")?;
265 }
266 if self.contains(Self::NON_GAME) {
267 write_flag("Non-Game")?;
268 }
269 if self.contains(Self::UNKNOWN_1) {
270 write_flag("Unknown (0x08000000)")?;
271 }
272 if self.contains(Self::UNKNOWN_2) {
273 write_flag("Unknown (0x10000000)")?;
274 }
275 if self.contains(Self::CUMULATIVE_PATCH_X) {
276 write_flag("Cumulative Patch X")?;
277 }
278 if self.contains(Self::SUBSEQUENT_PATCH) {
279 write_flag("Subsequent Patch")?;
280 }
281 }
282
283 Ok(())
284 }
285}
286
287#[derive(Debug, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
288#[repr(C)]
289pub struct DigestTable {
290 pub digest_entries1: [u8; 0x20],
291 pub digest_entries2: [u8; 0x20],
292 pub digest_table_digest: [u8; 0x20],
293 pub digest_body_digest: [u8; 0x20],
294}
295
296#[must_use]
298pub const fn content_type_name(content_type: u32) -> &'static str {
299 match content_type {
300 0x01 => "GD (Game Data)",
301 0x02 => "AC (Additional Content)",
302 0x03 => "AL (App License)",
303 0x04 => "DP (Delta Patch)",
304 0x05 => "DP (Cumulative Patch)", 0x06 => "Remaster",
306 0x1A => "GD (Game Data)",
307 0x1B => "AC (Additional Content)",
308 _ => "Unknown",
309 }
310}
311
312#[must_use]
314pub const fn drm_type_name(drm_type: u32) -> &'static str {
315 match drm_type {
316 0x0 => "None",
317 0x1 => "PS4",
318 0xD => "PS4 (Free)",
319 0xF => "PS4",
320 _ => "Unknown",
321 }
322}
323
324#[derive(Debug)]
326#[must_use]
327pub struct PkgHeader {
328 raw_header: PkgHeaderRaw,
329}
330
331impl PkgHeader {
332 pub fn read(pkg: &[u8]) -> Result<Self, ReadError> {
338 snafu::ensure!(pkg.len() >= 0x1000, TooSmallSnafu);
340
341 let (raw_header, _) =
342 PkgHeaderRaw::try_read_from_prefix(pkg).map_err(|_| InvalidSourceBytesSnafu.build())?;
343
344 snafu::ensure!(raw_header.pkg_magic.get() == PKG_MAGIC, InvalidMagicSnafu);
346
347 Ok(Self { raw_header })
348 }
349
350 #[must_use]
352 pub const fn entry_count(&self) -> usize {
353 self.raw_header.pkg_entry_count.get() as _
354 }
355
356 #[must_use]
358 pub const fn table_offset(&self) -> usize {
359 self.raw_header.pkg_table_offset.get() as _
360 }
361
362 #[must_use]
364 pub const fn pfs_offset(&self) -> usize {
365 self.raw_header.pfs_image_offset.get() as _
366 }
367
368 #[must_use]
370 pub const fn pfs_size(&self) -> usize {
371 self.raw_header.pfs_image_size.get() as _
372 }
373
374 #[must_use]
376 pub fn content_id(&self) -> &ContentId {
377 &self.raw_header.pkg_content_id
378 }
379
380 #[must_use]
382 pub const fn pkg_type(&self) -> u32 {
383 self.raw_header.pkg_type.get()
384 }
385
386 #[must_use]
388 pub const fn drm_type(&self) -> u32 {
389 self.raw_header.pkg_drm_type.get()
390 }
391
392 #[must_use]
394 pub const fn drm_type_name(&self) -> &'static str {
395 drm_type_name(self.drm_type())
396 }
397
398 #[must_use]
400 pub const fn content_type(&self) -> u32 {
401 self.raw_header.pkg_content_type.get()
402 }
403
404 #[must_use]
406 pub const fn content_type_name(&self) -> &'static str {
407 content_type_name(self.content_type())
408 }
409
410 #[must_use]
412 pub const fn content_flags(&self) -> ContentFlags {
413 ContentFlags::from_bits_truncate(self.raw_header.pkg_content_flags.get())
414 }
415
416 #[must_use]
418 pub const fn pkg_size(&self) -> u64 {
419 self.raw_header.pkg_size.get()
420 }
421
422 #[must_use]
424 pub const fn file_count(&self) -> u32 {
425 self.raw_header.pkg_file_count.get()
426 }
427
428 #[must_use]
430 pub const fn raw_header(&self) -> &PkgHeaderRaw {
431 &self.raw_header
432 }
433}