Skip to main content

orbis_pkg/
lib.rs

1//! A library for parsing and extracting PlayStation 4 PKG files.
2//!
3//! This crate provides functionality to parse PKG files, which are the package
4//! format used by PlayStation 4 for distributing games, updates, and DLC.
5//!
6//! # Features
7//!
8//! - Parse PKG headers and metadata (content ID, type, DRM info)
9//! - Iterate over and extract PKG entries
10//! - Decrypt encrypted entries using the appropriate keys
11//! - Access the embedded PFS image for game content
12//!
13//! # Example
14//!
15//! ```no_run
16//! use orbis_pkg::Pkg;
17//!
18//! // Open a PKG from any byte source
19//! let bytes = std::fs::read("game.pkg").unwrap();
20//! let pkg = Pkg::new(bytes).unwrap();
21//!
22//! // Access header information
23//! println!("Content ID: {}", pkg.header().content_id());
24//! println!("Entry count: {}", pkg.entry_count());
25//!
26//! // Iterate over entries
27//! for result in pkg.entries() {
28//!     let (index, entry) = result.unwrap();
29//!     println!("Entry {}: id=0x{:08X}", index, entry.id());
30//! }
31//!
32//! // Access the PFS image
33//! if let Some(pfs) = pkg.get_pfs_image() {
34//!     // Use orbis_pfs to read the PFS image
35//!     println!("PFS image size: {} bytes", pfs.data.len());
36//! }
37//! ```
38//!
39//! # References
40//!
41//! - [PS4 Developer Wiki - PKG files](https://www.psdevwiki.com/ps4/PKG_files)
42
43use self::entry::{EntryId, PkgEntry};
44use self::header::PkgHeader;
45use self::keys::{fake_pfs_key, pkg_key3};
46use aes::cipher::generic_array::GenericArray;
47use aes::cipher::{BlockDecryptMut, KeyIvInit};
48use sha2::Digest;
49use snafu::{ResultExt, Snafu};
50use std::io::Read;
51
52use open_error::*;
53
54pub mod entry;
55pub mod header;
56pub mod keys;
57
58/// A parsed PS4 PKG file.
59///
60/// This struct provides read-only access to the PKG contents including
61/// entries, headers, and the encrypted PFS image.
62///
63/// Reference: <https://www.psdevwiki.com/ps4/PKG_files>
64#[must_use]
65pub struct Pkg<R: AsRef<[u8]>> {
66    raw: R,
67    header: PkgHeader,
68    entry_key3: Vec<u8>,
69    ekpfs: Vec<u8>,
70}
71
72impl<R: AsRef<[u8]>> std::fmt::Debug for Pkg<R> {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        f.debug_struct("Pkg")
75            .field("header", &self.header)
76            .field("entry_count", &self.entry_count())
77            .finish_non_exhaustive()
78    }
79}
80
81impl<R: AsRef<[u8]>> Pkg<R> {
82    /// Creates a new [`Pkg`] from raw bytes.
83    ///
84    /// Parses the header, entry keys, and EKPFS from the provided data.
85    ///
86    /// # Example
87    ///
88    /// ```no_run
89    /// use orbis_pkg::Pkg;
90    ///
91    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
92    /// let bytes = std::fs::read("game.pkg")?;
93    /// let pkg = Pkg::new(bytes)?;
94    /// println!("Content ID: {}", pkg.header().content_id());
95    /// # Ok(())
96    /// # }
97    /// ```
98    pub fn new(raw: R) -> Result<Self, OpenError> {
99        let header = PkgHeader::read(raw.as_ref()).context(ReadHeaderFailedSnafu)?;
100
101        let mut pkg = Self {
102            raw,
103            header,
104            entry_key3: Vec::new(),
105            ekpfs: Vec::new(),
106        };
107        pkg.load_entry_key3()?;
108        pkg.load_ekpfs()?;
109        Ok(pkg)
110    }
111
112    /// Returns a reference to the PKG header.
113    pub fn header(&self) -> &PkgHeader {
114        &self.header
115    }
116
117    /// Returns the number of entries in the PKG.
118    #[must_use]
119    pub fn entry_count(&self) -> usize {
120        self.header.entry_count()
121    }
122
123    /// Returns an iterator over all entries in the PKG.
124    ///
125    /// Each item contains the entry index and the entry metadata.
126    ///
127    /// # Example
128    ///
129    /// ```no_run
130    /// use orbis_pkg::Pkg;
131    ///
132    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
133    /// let bytes = std::fs::read("game.pkg")?;
134    /// let pkg = Pkg::new(bytes)?;
135    ///
136    /// for result in pkg.entries() {
137    ///     let (index, entry) = result?;
138    ///     println!("Entry {}: id=0x{:08X}, size={}", index, entry.id(), entry.data_size());
139    /// }
140    /// # Ok(())
141    /// # }
142    /// ```
143    pub fn entries(&self) -> PkgEntries<'_> {
144        PkgEntries {
145            raw: self.raw.as_ref(),
146            table_offset: self.header.table_offset(),
147            current: 0,
148            total: self.header.entry_count(),
149        }
150    }
151
152    /// Gets the decrypted data for an entry.
153    ///
154    /// Returns the decrypted data with any padding removed.
155    ///
156    /// # Errors
157    ///
158    /// Returns [`EntryDataError::NoDecryptionKey`] if the entry is encrypted
159    /// and no decryption key is available for its key index.
160    ///
161    /// # Example
162    ///
163    /// ```no_run
164    /// use orbis_pkg::Pkg;
165    /// use orbis_pkg::entry::EntryId;
166    ///
167    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
168    /// let bytes = std::fs::read("game.pkg")?;
169    /// let pkg = Pkg::new(bytes)?;
170    ///
171    /// // Find and extract param.sfo
172    /// if let Ok((entry, _)) = pkg.find_entry(EntryId::ParamSfo) {
173    ///     let data = pkg.entry_data(&entry)?;
174    ///     std::fs::write("param.sfo", &data)?;
175    /// }
176    /// # Ok(())
177    /// # }
178    /// ```
179    pub fn entry_data(&self, entry: &PkgEntry) -> Result<Vec<u8>, EntryDataError> {
180        // Check if we have a decryption key for encrypted entries.
181        if entry.is_encrypted() && (entry.key_index() != 3 || self.entry_key3.is_empty()) {
182            return Err(EntryDataError::NoDecryptionKey {
183                key_index: entry.key_index(),
184            });
185        }
186
187        // Get entry data offset and size.
188        let offset = entry.data_offset();
189        let padded_size = if entry.is_encrypted() {
190            (entry.data_size() + 15) & !15 // Include padding for decryption.
191        } else {
192            entry.data_size()
193        };
194
195        let raw_data = self
196            .raw
197            .as_ref()
198            .get(offset..(offset + padded_size))
199            .ok_or(EntryDataError::InvalidDataOffset)?;
200
201        // Decrypt if needed.
202        if entry.is_encrypted() {
203            if raw_data.len() % 16 != 0 {
204                return Err(EntryDataError::MisalignedData {
205                    size: raw_data.len(),
206                });
207            }
208
209            let mut decrypted = self.decrypt_entry_data(entry, raw_data);
210            // Truncate to actual size (remove padding).
211            decrypted.truncate(entry.data_size());
212            Ok(decrypted)
213        } else {
214            Ok(raw_data.to_vec())
215        }
216    }
217
218    /// Returns the embedded PFS image and its encryption key.
219    ///
220    /// Returns `None` if the PFS offset/size is invalid.
221    #[must_use]
222    pub fn get_pfs_image(&self) -> Option<PfsImage<'_>> {
223        let offset = self.header.pfs_offset();
224        let size = self.header.pfs_size();
225        let data = self.raw.as_ref().get(offset..(offset + size))?;
226        Some(PfsImage {
227            data,
228            ekpfs: &self.ekpfs,
229        })
230    }
231
232    /// Finds an entry by its ID.
233    ///
234    /// Returns the entry and its index if found.
235    pub fn find_entry(&self, id: EntryId) -> Result<(PkgEntry, usize), FindEntryError> {
236        self.find_entry_raw(id.as_u32())
237    }
238
239    /// Finds an entry by its raw numeric ID.
240    ///
241    /// This is useful when working with unknown/unsupported IDs.
242    pub fn find_entry_raw(&self, id: u32) -> Result<(PkgEntry, usize), FindEntryError> {
243        for num in 0..self.header.entry_count() {
244            let offset = self.header.table_offset() + num * PkgEntry::RAW_SIZE;
245            let raw = self
246                .raw
247                .as_ref()
248                .get(offset..(offset + PkgEntry::RAW_SIZE))
249                .ok_or(FindEntryError::InvalidOffset { num })?;
250
251            let entry =
252                PkgEntry::read(raw).map_err(|source| FindEntryError::ReadFailed { source })?;
253
254            if entry.id() == id {
255                return Ok((entry, num));
256            }
257        }
258
259        Err(FindEntryError::NotFound)
260    }
261
262    fn load_ekpfs(&mut self) -> Result<(), OpenError> {
263        // Locate image key entry.
264        let (entry, _) = match self.find_entry(EntryId::PfsImageKey) {
265            Ok(v) => v,
266            Err(e) => match e {
267                FindEntryError::NotFound => return Err(OpenError::PfsImageKeyNotFound),
268                _ => return Err(OpenError::FindPfsImageKeyFailed { source: e }),
269            },
270        };
271
272        // Get and decrypt the entry data.
273        let data = self
274            .entry_data(&entry)
275            .context(open_error::GetPfsImageKeyFailedSnafu)?;
276
277        // Decrypt EKPFS with fake pkg key.
278        let fake_key = fake_pfs_key();
279        self.ekpfs = fake_key
280            .decrypt(rsa::Pkcs1v15Encrypt, &data)
281            .context(DecryptEkpfsFailedSnafu)?;
282
283        Ok(())
284    }
285
286    fn decrypt_entry_data(&self, entry: &PkgEntry, mut encrypted: &[u8]) -> Vec<u8> {
287        debug_assert_eq!(encrypted.len() % 16, 0);
288
289        // Setup decryptor.
290        let (key, iv) = self.derive_entry_key3(entry);
291        let mut decryptor = cbc::Decryptor::<aes::Aes128>::new(&key.into(), &iv.into());
292
293        // Decrypt blocks.
294        let mut out = Vec::with_capacity(encrypted.len());
295
296        while !encrypted.is_empty() {
297            let mut block = [0u8; 16];
298            encrypted.read_exact(&mut block).unwrap();
299            decryptor.decrypt_block_mut(GenericArray::from_mut_slice(&mut block));
300            out.extend_from_slice(&block);
301        }
302
303        out
304    }
305
306    /// Get key and IV for `entry` using `entry_key3`.
307    fn derive_entry_key3(&self, entry: &PkgEntry) -> ([u8; 16], [u8; 16]) {
308        // Calculate secret.
309        let mut sha256 = sha2::Sha256::new();
310        sha256.update(entry.as_bytes());
311        sha256.update(&self.entry_key3);
312        let secret = sha256.finalize();
313
314        // Extract key and IV.
315        let (iv, key) = secret.split_at(16);
316        (key.try_into().unwrap(), iv.try_into().unwrap())
317    }
318
319    fn load_entry_key3(&mut self) -> Result<(), OpenError> {
320        // Locate entry keys.
321        let (entry, index) = match self.find_entry(EntryId::EntryKeys) {
322            Ok(v) => v,
323            Err(e) => match e {
324                FindEntryError::NotFound => return Err(OpenError::EntryKeyNotFound),
325                _ => return Err(OpenError::FindEntryKeyFailed { source: e }),
326            },
327        };
328
329        // Get raw entry data (not decrypted, as this contains the keys themselves).
330        let offset = entry.data_offset();
331        let size = entry.data_size();
332        let mut data = self
333            .raw
334            .as_ref()
335            .get(offset..(offset + size))
336            .ok_or(OpenError::InvalidEntryOffset { num: index })?;
337
338        // Read seed.
339        let mut seed = [0u8; 32];
340        if data.read_exact(&mut seed).is_err() {
341            return Err(OpenError::InvalidEntryOffset { num: index });
342        };
343
344        // Read digests.
345        let mut digests: [[u8; 32]; 7] = [[0u8; 32]; 7];
346        digests
347            .iter_mut()
348            .try_for_each(|digest| data.read_exact(digest))
349            .map_err(|_| OpenError::InvalidEntryOffset { num: index })?;
350
351        // Read keys.
352        let mut keys: [[u8; 256]; 7] = [[0u8; 256]; 7];
353        keys.iter_mut()
354            .try_for_each(|key| data.read_exact(key))
355            .map_err(|_| OpenError::InvalidEntryOffset { num: index })?;
356
357        // Decrypt key 3.
358        let key3 = pkg_key3();
359        self.entry_key3 = key3
360            .decrypt(rsa::Pkcs1v15Encrypt, &keys[3])
361            .context(DecryptEntryKeyFailedSnafu { key_index: 3usize })?;
362
363        Ok(())
364    }
365}
366
367/// The embedded PFS image and its encryption key, returned by [`Pkg::get_pfs_image()`].
368#[derive(Debug)]
369pub struct PfsImage<'a> {
370    /// The raw PFS image bytes.
371    pub data: &'a [u8],
372    /// The EKPFS key needed to decrypt and open the PFS.
373    pub ekpfs: &'a [u8],
374}
375
376/// Iterator over PKG entries.
377#[must_use = "iterators are lazy and do nothing unless consumed"]
378pub struct PkgEntries<'a> {
379    raw: &'a [u8],
380    table_offset: usize,
381    current: usize,
382    total: usize,
383}
384
385impl std::fmt::Debug for PkgEntries<'_> {
386    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
387        f.debug_struct("PkgEntries")
388            .field("current", &self.current)
389            .field("total", &self.total)
390            .finish_non_exhaustive()
391    }
392}
393
394impl Iterator for PkgEntries<'_> {
395    type Item = Result<(usize, PkgEntry), EntryReadError>;
396
397    fn next(&mut self) -> Option<Self::Item> {
398        if self.current >= self.total {
399            return None;
400        }
401
402        let num = self.current;
403        self.current += 1;
404
405        let offset = self.table_offset + num * PkgEntry::RAW_SIZE;
406        let raw = match self.raw.get(offset..(offset + PkgEntry::RAW_SIZE)) {
407            Some(v) => v,
408            None => return Some(Err(EntryReadError::InvalidOffset { num })),
409        };
410
411        Some(
412            PkgEntry::read(raw)
413                .map_err(|source| EntryReadError::ReadFailed { source })
414                .map(|entry| (num, entry)),
415        )
416    }
417
418    fn size_hint(&self) -> (usize, Option<usize>) {
419        let remaining = self.total - self.current;
420        (remaining, Some(remaining))
421    }
422}
423
424impl ExactSizeIterator for PkgEntries<'_> {}
425
426#[derive(Debug, Snafu)]
427#[snafu(module)]
428#[non_exhaustive]
429pub enum OpenError {
430    #[snafu(display("invalid PKG header"))]
431    ReadHeaderFailed { source: header::ReadError },
432
433    #[snafu(display("no PKG entry key available"))]
434    EntryKeyNotFound,
435
436    #[snafu(display("failed to find entry key"))]
437    FindEntryKeyFailed { source: FindEntryError },
438
439    #[snafu(display("entry #{num} has invalid data offset"))]
440    InvalidEntryOffset { num: usize },
441
442    #[snafu(display("cannot decrypt entry key #{key_index}"))]
443    DecryptEntryKeyFailed {
444        key_index: usize,
445        source: rsa::errors::Error,
446    },
447
448    #[snafu(display("no PFS image key in the PKG"))]
449    PfsImageKeyNotFound,
450
451    #[snafu(display("failed to get PFS image key"))]
452    GetPfsImageKeyFailed { source: EntryDataError },
453
454    #[snafu(display("failed to find PFS image key"))]
455    FindPfsImageKeyFailed { source: FindEntryError },
456
457    #[snafu(display("cannot decrypt EKPFS"))]
458    DecryptEkpfsFailed { source: rsa::errors::Error },
459}
460
461#[derive(Debug, Snafu)]
462#[snafu(module)]
463#[non_exhaustive]
464pub enum FindEntryError {
465    #[snafu(display("failed to read entry"))]
466    ReadFailed { source: entry::EntryError },
467
468    #[snafu(display("entry #{num} has invalid offset"))]
469    InvalidOffset { num: usize },
470
471    #[snafu(display("the specified entry was not found"))]
472    NotFound,
473}
474
475#[derive(Debug, Snafu)]
476#[snafu(module)]
477#[non_exhaustive]
478pub enum EntryReadError {
479    #[snafu(display("entry #{num} has invalid offset"))]
480    InvalidOffset { num: usize },
481
482    #[snafu(display("failed to read entry"))]
483    ReadFailed { source: entry::EntryError },
484}
485
486#[derive(Debug, Snafu)]
487#[snafu(module)]
488#[non_exhaustive]
489pub enum EntryDataError {
490    #[snafu(display("no decryption key available for key index {key_index}"))]
491    NoDecryptionKey { key_index: usize },
492
493    #[snafu(display("entry has invalid data offset"))]
494    InvalidDataOffset,
495
496    #[snafu(display(
497        "encrypted entry data is not block-aligned (size {size} is not a multiple of 16)"
498    ))]
499    MisalignedData { size: usize },
500}