Skip to main content

orbis_pfs/
header.rs

1use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
2
3use std::fmt::{Display, Formatter};
4
5use snafu::{OptionExt, Snafu, ensure};
6
7/// Errors when parsing a PFS header from bytes.
8#[derive(Debug, Snafu)]
9#[non_exhaustive]
10pub enum ReadError {
11    #[snafu(display("invalid version"))]
12    InvalidVersion,
13
14    #[snafu(display("invalid format"))]
15    InvalidFormat,
16
17    #[snafu(display("too many blocks for inodes"))]
18    TooManyInodeBlocks,
19
20    #[snafu(display("source buffer is too short to read the header"))]
21    ReadHeaderFailed,
22
23    #[snafu(display("source buffer is too short to read the key seed"))]
24    ReadKeySeedFailed,
25}
26
27use zerocopy::byteorder::little_endian::{U16, U32, U64};
28
29const VERSION: u64 = 1;
30const FORMAT: u64 = 20130315;
31
32/// The size of the full PFS header on disk (includes key seed area).
33pub(crate) const HEADER_SIZE: usize = 0x380;
34
35/// Contains PFS header.
36///
37/// See https://www.psdevwiki.com/ps4/PFS#Header.2FSuperblock for some basic information.
38#[derive(FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
39#[repr(C)]
40pub(crate) struct PfsHeaderRaw {
41    version: U64,       // 0x00
42    format: U64,        // 0x08
43    id: U64,            // 0x10
44    flags: FlagsRaw,    // 0x18
45    mode: Mode,         // 0x1C
46    unknown: U16,       // 0x1E (unknown)
47    block_size: U32,    // 0x20
48    nbackup: U32,       // 0x24
49    nblock: U64,        // 0x28
50    ndinode: U64,       // 0x30 - Number of inodes in the inode blocks
51    ndblock: U64,       // 0x38 - Number of data blocks
52    ndinodeblock: U64,  // 0x40 - Number of inode blocks
53    superroot_ino: U64, // 0x48
54}
55
56#[derive(FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
57#[repr(C)]
58pub(crate) struct FlagsRaw {
59    fmode: u8,
60    clean: u8,
61    ronly: u8,
62    rsv: u8,
63}
64
65pub(crate) struct PfsHeader {
66    raw_header: PfsHeaderRaw,
67    key_seed: [u8; 16],
68}
69
70impl PfsHeader {
71    /// Parses the header directly from a byte slice.
72    ///
73    /// The slice must be at least [`HEADER_SIZE`] bytes.
74    pub(super) fn from_bytes(data: &[u8]) -> Result<Self, ReadError> {
75        let (raw_header, rest) =
76            PfsHeaderRaw::read_from_prefix(data).map_err(|_| ReadHeaderFailedSnafu.build())?;
77
78        // Check version.
79        ensure!(raw_header.version.get() == VERSION, InvalidVersionSnafu);
80
81        // Check format.
82        ensure!(raw_header.format.get() == FORMAT, InvalidFormatSnafu);
83
84        // Usually block will be references by u32. Not sure why ndinodeblock is 64-bits. Design flaws?
85        ensure!(
86            raw_header.ndinodeblock.get() <= (u32::MAX as u64),
87            TooManyInodeBlocksSnafu
88        );
89
90        // Read key seed from the rest of the header.
91        let key_seed_offset = 0x370 - size_of::<PfsHeaderRaw>();
92        let key_seed: [u8; 16] = rest
93            .get(key_seed_offset..key_seed_offset + 16)
94            .context(ReadKeySeedFailedSnafu)?
95            .try_into()
96            .unwrap();
97
98        Ok(Self {
99            raw_header,
100            key_seed,
101        })
102    }
103
104    pub fn mode(&self) -> Mode {
105        self.raw_header.mode
106    }
107
108    pub fn block_size(&self) -> u32 {
109        self.raw_header.block_size.get()
110    }
111
112    /// Gets a number of total inodes.
113    pub fn inode_count(&self) -> usize {
114        self.raw_header.ndinode.get() as usize
115    }
116
117    /// Gets a number of blocks containing inode (not a number of inode).
118    pub fn inode_block_count(&self) -> u32 {
119        self.raw_header.ndinodeblock.get() as u32
120    }
121
122    pub fn super_root_inode(&self) -> usize {
123        self.raw_header.superroot_ino.get() as usize
124    }
125
126    pub fn key_seed(&self) -> &[u8; 16] {
127        &self.key_seed
128    }
129}
130
131/// Contains PFS flags.
132#[derive(
133    Clone,
134    Copy,
135    Debug,
136    Default,
137    PartialEq,
138    Eq,
139    PartialOrd,
140    Ord,
141    Hash,
142    FromBytes,
143    IntoBytes,
144    KnownLayout,
145    Immutable,
146    Unaligned,
147)]
148#[repr(C)]
149pub struct Mode {
150    flags: U16,
151}
152
153impl Mode {
154    /// Returns `true` if the PFS is signed.
155    #[inline]
156    #[must_use]
157    pub const fn is_signed(&self) -> bool {
158        self.flags.get() & 0x1 != 0
159    }
160
161    #[inline]
162    #[must_use]
163    pub const fn is_64bits(&self) -> bool {
164        self.flags.get() & 0x2 != 0
165    }
166
167    /// Returns `true` if the PFS is encrypted.
168    #[inline]
169    #[must_use]
170    pub const fn is_encrypted(&self) -> bool {
171        self.flags.get() & 0x4 != 0
172    }
173}
174
175impl Display for Mode {
176    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
177        write!(f, "{:x}", self.flags.get())?;
178
179        let mut op = false;
180
181        let mut first = true;
182
183        let mut flag = |name: &str| -> std::fmt::Result {
184            if !op {
185                f.write_str(" (")?;
186                op = true;
187            }
188
189            if !first {
190                f.write_str(", ")?;
191            }
192
193            f.write_str(name)?;
194            first = false;
195
196            Ok(())
197        };
198
199        if self.is_signed() {
200            flag("signed")?;
201        }
202
203        if self.is_64bits() {
204            flag("64-bits")?;
205        }
206
207        if self.is_encrypted() {
208            flag("encrypted")?;
209        }
210
211        if op {
212            f.write_str(")")?;
213        }
214
215        Ok(())
216    }
217}