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 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 #[must_use]
48 pub const fn id(&self) -> u32 {
49 self.raw_entry.id.get()
50 }
51
52 #[must_use]
54 pub const fn entry_id(&self) -> EntryId {
55 EntryId::from_u32(self.id())
56 }
57
58 #[must_use]
60 pub const fn is_encrypted(&self) -> bool {
61 self.raw_entry.flags1.get() & 0x80000000 != 0
62 }
63
64 #[must_use]
66 pub const fn key_index(&self) -> usize {
67 ((self.raw_entry.flags2.get() & 0xf000) >> 12) as _
68 }
69
70 #[must_use]
72 pub const fn data_offset(&self) -> usize {
73 self.raw_entry.data_offset.get() as _
74 }
75
76 #[must_use]
78 pub const fn data_size(&self) -> usize {
79 self.raw_entry.data_size.get() as _
80 }
81
82 #[must_use]
84 pub fn as_bytes(&self) -> &[u8] {
85 self.raw_entry.as_bytes()
86 }
87
88 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
99#[non_exhaustive]
100pub enum EntryId {
101 Digests,
103 EntryKeys,
104 PfsImageKey,
105 GeneralDigests,
106 Metas,
107 EntryNames,
108
109 LicenseDat,
111 LicenseInfo,
112 NptitleDat,
113 NpbindDat,
114 SelfinfoDat,
115 ImageinfoDat,
116 TargetDeltainfoDat,
117 OriginDeltainfoDat,
118 PsreservedDat,
119
120 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 Icon0Png,
139 Icon0PngIndexed(u8),
141
142 Pic0Png,
144 Snd0At9,
145 Pic1PngIndexed(u8),
147
148 ChangeinfoXml,
150 ChangeinfoXmlIndexed(u8),
152
153 Icon0Dds,
155 Icon0DdsIndexed(u8),
157 Pic0Dds,
158 Pic1Dds,
159 Pic1DdsIndexed(u8),
161
162 Trophy(u8),
165
166 Unknown(u32),
168}
169
170impl EntryId {
171 #[must_use]
173 pub const fn as_u32(self) -> u32 {
174 match self {
175 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 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 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 Self::Icon0Png => 0x00001200,
213 Self::Icon0PngIndexed(idx) => 0x00001201 + idx as u32,
214
215 Self::Pic0Png => 0x00001220,
217 Self::Snd0At9 => 0x00001240,
218 Self::Pic1PngIndexed(idx) => 0x00001241 + idx as u32,
219
220 Self::ChangeinfoXml => 0x00001260,
222 Self::ChangeinfoXmlIndexed(idx) => 0x00001261 + idx as u32,
223
224 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 Self::Trophy(idx) => 0x00001400 + idx as u32,
233
234 Self::Unknown(raw) => raw,
235 }
236 }
237
238 #[must_use]
240 pub const fn from_u32(raw: u32) -> Self {
241 match raw {
242 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 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 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 0x00001200 => Self::Icon0Png,
280 0x00001201..=0x0000121F => Self::Icon0PngIndexed((raw - 0x00001201) as u8),
281
282 0x00001220 => Self::Pic0Png,
284 0x00001240 => Self::Snd0At9,
285 0x00001241..=0x0000125F => Self::Pic1PngIndexed((raw - 0x00001241) as u8),
286
287 0x00001260 => Self::ChangeinfoXml,
289 0x00001261..=0x0000127F => Self::ChangeinfoXmlIndexed((raw - 0x00001261) as u8),
290
291 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 0x00001400..=0x00001463 => Self::Trophy((raw - 0x00001400) as u8),
300
301 other => Self::Unknown(other),
302 }
303 }
304
305 #[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 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 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 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 Self::Icon0Png => base.join("icon0.png"),
350 Self::Icon0PngIndexed(idx) => base.join(format!("icon0_{:02}.png", idx)),
351
352 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 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 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 Self::Trophy(idx) => base.join("trophy").join(format!("trophy{:02}.trp", idx)),
372
373 Self::Unknown(_) => return None,
374 })
375 }
376}