Skip to main content

orbis_pkg/
entry.rs

1use snafu::Snafu;
2use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
3
4use zerocopy::byteorder::big_endian::{U32, U64};
5
6use std::path::{Path, PathBuf};
7
8#[derive(Debug, Snafu)]
9#[non_exhaustive]
10pub enum EntryError {
11    #[snafu(display("source buffer is too short"))]
12    SourceTooShort,
13}
14
15type Result<T, E = EntryError> = std::result::Result<T, E>;
16
17#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
18#[repr(C)]
19pub(crate) struct PkgEntryRaw {
20    id: U32,
21    filename_offset: U32,
22    flags1: U32,
23    flags2: U32,
24    data_offset: U32,
25    data_size: U32,
26    padding: U64,
27}
28
29#[derive(Debug, Clone, Copy)]
30#[must_use]
31pub struct PkgEntry {
32    raw_entry: PkgEntryRaw,
33}
34
35impl PkgEntry {
36    pub const RAW_SIZE: usize = size_of::<PkgEntryRaw>();
37
38    /// Reads an entry from raw bytes.
39    pub fn read(raw: &[u8]) -> Result<Self> {
40        let (raw_entry, _) =
41            PkgEntryRaw::read_from_prefix(raw).map_err(|_| SourceTooShortSnafu.build())?;
42
43        Ok(Self { raw_entry })
44    }
45
46    /// Returns the entry ID.
47    #[must_use]
48    pub const fn id(&self) -> u32 {
49        self.raw_entry.id.get()
50    }
51
52    /// Returns the parsed entry identifier.
53    #[must_use]
54    pub const fn entry_id(&self) -> EntryId {
55        EntryId::from_u32(self.id())
56    }
57
58    /// Returns `true` if this entry is encrypted.
59    #[must_use]
60    pub const fn is_encrypted(&self) -> bool {
61        self.raw_entry.flags1.get() & 0x80000000 != 0
62    }
63
64    /// Returns the key index used for encryption.
65    #[must_use]
66    pub const fn key_index(&self) -> usize {
67        ((self.raw_entry.flags2.get() & 0xf000) >> 12) as _
68    }
69
70    /// Returns the data offset within the PKG file.
71    #[must_use]
72    pub const fn data_offset(&self) -> usize {
73        self.raw_entry.data_offset.get() as _
74    }
75
76    /// Returns the data size in bytes.
77    #[must_use]
78    pub const fn data_size(&self) -> usize {
79        self.raw_entry.data_size.get() as _
80    }
81
82    /// Converts the entry to its raw byte representation.
83    #[must_use]
84    pub fn as_bytes(&self) -> &[u8] {
85        self.raw_entry.as_bytes()
86    }
87
88    /// Converts the entry ID to a filesystem path relative to the given base.
89    ///
90    /// Returns `None` if the entry ID is not recognized.
91    #[must_use]
92    pub fn to_path<B: AsRef<Path>>(&self, base: B) -> Option<PathBuf> {
93        self.entry_id().to_path(base)
94    }
95}
96
97/// Known PKG entry identifiers.
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
99#[non_exhaustive]
100pub enum EntryId {
101    // Metadata entries (0x0001 - 0x0200)
102    Digests,
103    EntryKeys,
104    PfsImageKey,
105    GeneralDigests,
106    Metas,
107    EntryNames,
108
109    // License and system entries (0x0400 - 0x0409)
110    LicenseDat,
111    LicenseInfo,
112    NptitleDat,
113    NpbindDat,
114    SelfinfoDat,
115    ImageinfoDat,
116    TargetDeltainfoDat,
117    OriginDeltainfoDat,
118    PsreservedDat,
119
120    // Content entries (0x1000 - 0x100E)
121    ParamSfo,
122    PlaygoChunkDat,
123    PlaygoChunkSha,
124    PlaygoManifestXml,
125    PronunciationXml,
126    PronunciationSig,
127    Pic1Png,
128    PubtoolinfoDat,
129    AppPlaygoChunkDat,
130    AppPlaygoChunkSha,
131    AppPlaygoManifestXml,
132    ShareparamJson,
133    ShareoverlayimagePng,
134    SaveDataPng,
135    ShareprivacyguardimagePng,
136
137    // Icon entries (0x1200 - 0x121F)
138    Icon0Png,
139    /// `icon0_00.png` .. `icon0_30.png`
140    Icon0PngIndexed(u8),
141
142    // Picture entries (0x1220 - 0x125F)
143    Pic0Png,
144    Snd0At9,
145    /// `pic1_00.png` .. `pic1_30.png`
146    Pic1PngIndexed(u8),
147
148    // Changeinfo entries (0x1260 - 0x127F)
149    ChangeinfoXml,
150    /// `changeinfo_00.xml` .. `changeinfo_30.xml`
151    ChangeinfoXmlIndexed(u8),
152
153    // DDS entries (0x1280 - 0x12DF)
154    Icon0Dds,
155    /// `icon0_00.dds` .. `icon0_30.dds`
156    Icon0DdsIndexed(u8),
157    Pic0Dds,
158    Pic1Dds,
159    /// `pic1_00.dds` .. `pic1_30.dds`
160    Pic1DdsIndexed(u8),
161
162    // Trophy entries (0x1400 - 0x1463)
163    /// `trophy00.trp` .. `trophy99.trp`
164    Trophy(u8),
165
166    /// Unrecognized entry ID.
167    Unknown(u32),
168}
169
170impl EntryId {
171    /// Returns the raw numeric entry ID.
172    #[must_use]
173    pub const fn as_u32(self) -> u32 {
174        match self {
175            // Metadata entries
176            Self::Digests => 0x00000001,
177            Self::EntryKeys => 0x00000010,
178            Self::PfsImageKey => 0x00000020,
179            Self::GeneralDigests => 0x00000080,
180            Self::Metas => 0x00000100,
181            Self::EntryNames => 0x00000200,
182
183            // License and system entries
184            Self::LicenseDat => 0x00000400,
185            Self::LicenseInfo => 0x00000401,
186            Self::NptitleDat => 0x00000402,
187            Self::NpbindDat => 0x00000403,
188            Self::SelfinfoDat => 0x00000404,
189            Self::ImageinfoDat => 0x00000406,
190            Self::TargetDeltainfoDat => 0x00000407,
191            Self::OriginDeltainfoDat => 0x00000408,
192            Self::PsreservedDat => 0x00000409,
193
194            // Content entries
195            Self::ParamSfo => 0x00001000,
196            Self::PlaygoChunkDat => 0x00001001,
197            Self::PlaygoChunkSha => 0x00001002,
198            Self::PlaygoManifestXml => 0x00001003,
199            Self::PronunciationXml => 0x00001004,
200            Self::PronunciationSig => 0x00001005,
201            Self::Pic1Png => 0x00001006,
202            Self::PubtoolinfoDat => 0x00001007,
203            Self::AppPlaygoChunkDat => 0x00001008,
204            Self::AppPlaygoChunkSha => 0x00001009,
205            Self::AppPlaygoManifestXml => 0x0000100a,
206            Self::ShareparamJson => 0x0000100b,
207            Self::ShareoverlayimagePng => 0x0000100c,
208            Self::SaveDataPng => 0x0000100d,
209            Self::ShareprivacyguardimagePng => 0x0000100e,
210
211            // Icon entries
212            Self::Icon0Png => 0x00001200,
213            Self::Icon0PngIndexed(idx) => 0x00001201 + idx as u32,
214
215            // Picture entries
216            Self::Pic0Png => 0x00001220,
217            Self::Snd0At9 => 0x00001240,
218            Self::Pic1PngIndexed(idx) => 0x00001241 + idx as u32,
219
220            // Changeinfo entries
221            Self::ChangeinfoXml => 0x00001260,
222            Self::ChangeinfoXmlIndexed(idx) => 0x00001261 + idx as u32,
223
224            // DDS entries
225            Self::Icon0Dds => 0x00001280,
226            Self::Icon0DdsIndexed(idx) => 0x00001281 + idx as u32,
227            Self::Pic0Dds => 0x000012a0,
228            Self::Pic1Dds => 0x000012c0,
229            Self::Pic1DdsIndexed(idx) => 0x000012c1 + idx as u32,
230
231            // Trophy entries
232            Self::Trophy(idx) => 0x00001400 + idx as u32,
233
234            Self::Unknown(raw) => raw,
235        }
236    }
237
238    /// Converts a raw numeric entry ID into an [`EntryId`].
239    #[must_use]
240    pub const fn from_u32(raw: u32) -> Self {
241        match raw {
242            // Metadata entries
243            0x00000001 => Self::Digests,
244            0x00000010 => Self::EntryKeys,
245            0x00000020 => Self::PfsImageKey,
246            0x00000080 => Self::GeneralDigests,
247            0x00000100 => Self::Metas,
248            0x00000200 => Self::EntryNames,
249
250            // License and system entries
251            0x00000400 => Self::LicenseDat,
252            0x00000401 => Self::LicenseInfo,
253            0x00000402 => Self::NptitleDat,
254            0x00000403 => Self::NpbindDat,
255            0x00000404 => Self::SelfinfoDat,
256            0x00000406 => Self::ImageinfoDat,
257            0x00000407 => Self::TargetDeltainfoDat,
258            0x00000408 => Self::OriginDeltainfoDat,
259            0x00000409 => Self::PsreservedDat,
260
261            // Content entries
262            0x00001000 => Self::ParamSfo,
263            0x00001001 => Self::PlaygoChunkDat,
264            0x00001002 => Self::PlaygoChunkSha,
265            0x00001003 => Self::PlaygoManifestXml,
266            0x00001004 => Self::PronunciationXml,
267            0x00001005 => Self::PronunciationSig,
268            0x00001006 => Self::Pic1Png,
269            0x00001007 => Self::PubtoolinfoDat,
270            0x00001008 => Self::AppPlaygoChunkDat,
271            0x00001009 => Self::AppPlaygoChunkSha,
272            0x0000100a => Self::AppPlaygoManifestXml,
273            0x0000100b => Self::ShareparamJson,
274            0x0000100c => Self::ShareoverlayimagePng,
275            0x0000100d => Self::SaveDataPng,
276            0x0000100e => Self::ShareprivacyguardimagePng,
277
278            // Icon PNG entries
279            0x00001200 => Self::Icon0Png,
280            0x00001201..=0x0000121F => Self::Icon0PngIndexed((raw - 0x00001201) as u8),
281
282            // Picture entries
283            0x00001220 => Self::Pic0Png,
284            0x00001240 => Self::Snd0At9,
285            0x00001241..=0x0000125F => Self::Pic1PngIndexed((raw - 0x00001241) as u8),
286
287            // Changeinfo entries
288            0x00001260 => Self::ChangeinfoXml,
289            0x00001261..=0x0000127F => Self::ChangeinfoXmlIndexed((raw - 0x00001261) as u8),
290
291            // DDS entries
292            0x00001280 => Self::Icon0Dds,
293            0x00001281..=0x0000129F => Self::Icon0DdsIndexed((raw - 0x00001281) as u8),
294            0x000012a0 => Self::Pic0Dds,
295            0x000012c0 => Self::Pic1Dds,
296            0x000012c1..=0x000012df => Self::Pic1DdsIndexed((raw - 0x000012c1) as u8),
297
298            // Trophy entries
299            0x00001400..=0x00001463 => Self::Trophy((raw - 0x00001400) as u8),
300
301            other => Self::Unknown(other),
302        }
303    }
304
305    /// Converts this entry ID to a filesystem path relative to the given base.
306    ///
307    /// Returns `None` if the entry ID is not recognized (or cannot be represented).
308    #[must_use]
309    pub fn to_path<B: AsRef<Path>>(self, base: B) -> Option<PathBuf> {
310        let base = base.as_ref();
311        Some(match self {
312            // Metadata entries
313            Self::Digests => base.join("digests"),
314            Self::EntryKeys => base.join("entry_keys"),
315            Self::PfsImageKey => base.join("image_key"),
316            Self::GeneralDigests => base.join("general_digests"),
317            Self::Metas => base.join("metas"),
318            Self::EntryNames => base.join("entry_names"),
319
320            // License and system entries
321            Self::LicenseDat => base.join("license.dat"),
322            Self::LicenseInfo => base.join("license.info"),
323            Self::NptitleDat => base.join("nptitle.dat"),
324            Self::NpbindDat => base.join("npbind.dat"),
325            Self::SelfinfoDat => base.join("selfinfo.dat"),
326            Self::ImageinfoDat => base.join("imageinfo.dat"),
327            Self::TargetDeltainfoDat => base.join("target-deltainfo.dat"),
328            Self::OriginDeltainfoDat => base.join("origin-deltainfo.dat"),
329            Self::PsreservedDat => base.join("psreserved.dat"),
330
331            // Content entries
332            Self::ParamSfo => base.join("param.sfo"),
333            Self::PlaygoChunkDat => base.join("playgo-chunk.dat"),
334            Self::PlaygoChunkSha => base.join("playgo-chunk.sha"),
335            Self::PlaygoManifestXml => base.join("playgo-manifest.xml"),
336            Self::PronunciationXml => base.join("pronunciation.xml"),
337            Self::PronunciationSig => base.join("pronunciation.sig"),
338            Self::Pic1Png => base.join("pic1.png"),
339            Self::PubtoolinfoDat => base.join("pubtoolinfo.dat"),
340            Self::AppPlaygoChunkDat => base.join("app").join("playgo-chunk.dat"),
341            Self::AppPlaygoChunkSha => base.join("app").join("playgo-chunk.sha"),
342            Self::AppPlaygoManifestXml => base.join("app").join("playgo-manifest.xml"),
343            Self::ShareparamJson => base.join("shareparam.json"),
344            Self::ShareoverlayimagePng => base.join("shareoverlayimage.png"),
345            Self::SaveDataPng => base.join("save_data.png"),
346            Self::ShareprivacyguardimagePng => base.join("shareprivacyguardimage.png"),
347
348            // Icon PNG entries
349            Self::Icon0Png => base.join("icon0.png"),
350            Self::Icon0PngIndexed(idx) => base.join(format!("icon0_{:02}.png", idx)),
351
352            // Picture entries
353            Self::Pic0Png => base.join("pic0.png"),
354            Self::Snd0At9 => base.join("snd0.at9"),
355            Self::Pic1PngIndexed(idx) => base.join(format!("pic1_{:02}.png", idx)),
356
357            // Changeinfo entries
358            Self::ChangeinfoXml => base.join("changeinfo").join("changeinfo.xml"),
359            Self::ChangeinfoXmlIndexed(idx) => base
360                .join("changeinfo")
361                .join(format!("changeinfo_{:02}.xml", idx)),
362
363            // DDS entries
364            Self::Icon0Dds => base.join("icon0.dds"),
365            Self::Icon0DdsIndexed(idx) => base.join(format!("icon0_{:02}.dds", idx)),
366            Self::Pic0Dds => base.join("pic0.dds"),
367            Self::Pic1Dds => base.join("pic1.dds"),
368            Self::Pic1DdsIndexed(idx) => base.join(format!("pic1_{:02}.dds", idx)),
369
370            // Trophy entries
371            Self::Trophy(idx) => base.join("trophy").join(format!("trophy{:02}.trp", idx)),
372
373            Self::Unknown(_) => return None,
374        })
375    }
376}