use snafu::Snafu;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
use zerocopy::byteorder::big_endian::{U32, U64};
use std::path::{Path, PathBuf};
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum EntryError {
#[snafu(display("source buffer is too short"))]
SourceTooShort,
}
type Result<T, E = EntryError> = std::result::Result<T, E>;
#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
#[repr(C)]
pub(crate) struct PkgEntryRaw {
id: U32,
filename_offset: U32,
flags1: U32,
flags2: U32,
data_offset: U32,
data_size: U32,
padding: U64,
}
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct PkgEntry {
raw_entry: PkgEntryRaw,
}
impl PkgEntry {
pub const RAW_SIZE: usize = size_of::<PkgEntryRaw>();
pub fn read(raw: &[u8]) -> Result<Self> {
let (raw_entry, _) =
PkgEntryRaw::read_from_prefix(raw).map_err(|_| SourceTooShortSnafu.build())?;
Ok(Self { raw_entry })
}
#[must_use]
pub const fn id(&self) -> u32 {
self.raw_entry.id.get()
}
#[must_use]
pub const fn entry_id(&self) -> EntryId {
EntryId::from_u32(self.id())
}
#[must_use]
pub const fn is_encrypted(&self) -> bool {
self.raw_entry.flags1.get() & 0x80000000 != 0
}
#[must_use]
pub const fn key_index(&self) -> usize {
((self.raw_entry.flags2.get() & 0xf000) >> 12) as _
}
#[must_use]
pub const fn data_offset(&self) -> usize {
self.raw_entry.data_offset.get() as _
}
#[must_use]
pub const fn data_size(&self) -> usize {
self.raw_entry.data_size.get() as _
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
self.raw_entry.as_bytes()
}
#[must_use]
pub fn to_path<B: AsRef<Path>>(&self, base: B) -> Option<PathBuf> {
self.entry_id().to_path(base)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum EntryId {
Digests,
EntryKeys,
PfsImageKey,
GeneralDigests,
Metas,
EntryNames,
LicenseDat,
LicenseInfo,
NptitleDat,
NpbindDat,
SelfinfoDat,
ImageinfoDat,
TargetDeltainfoDat,
OriginDeltainfoDat,
PsreservedDat,
ParamSfo,
PlaygoChunkDat,
PlaygoChunkSha,
PlaygoManifestXml,
PronunciationXml,
PronunciationSig,
Pic1Png,
PubtoolinfoDat,
AppPlaygoChunkDat,
AppPlaygoChunkSha,
AppPlaygoManifestXml,
ShareparamJson,
ShareoverlayimagePng,
SaveDataPng,
ShareprivacyguardimagePng,
Icon0Png,
Icon0PngIndexed(u8),
Pic0Png,
Snd0At9,
Pic1PngIndexed(u8),
ChangeinfoXml,
ChangeinfoXmlIndexed(u8),
Icon0Dds,
Icon0DdsIndexed(u8),
Pic0Dds,
Pic1Dds,
Pic1DdsIndexed(u8),
Trophy(u8),
Unknown(u32),
}
impl EntryId {
#[must_use]
pub const fn as_u32(self) -> u32 {
match self {
Self::Digests => 0x00000001,
Self::EntryKeys => 0x00000010,
Self::PfsImageKey => 0x00000020,
Self::GeneralDigests => 0x00000080,
Self::Metas => 0x00000100,
Self::EntryNames => 0x00000200,
Self::LicenseDat => 0x00000400,
Self::LicenseInfo => 0x00000401,
Self::NptitleDat => 0x00000402,
Self::NpbindDat => 0x00000403,
Self::SelfinfoDat => 0x00000404,
Self::ImageinfoDat => 0x00000406,
Self::TargetDeltainfoDat => 0x00000407,
Self::OriginDeltainfoDat => 0x00000408,
Self::PsreservedDat => 0x00000409,
Self::ParamSfo => 0x00001000,
Self::PlaygoChunkDat => 0x00001001,
Self::PlaygoChunkSha => 0x00001002,
Self::PlaygoManifestXml => 0x00001003,
Self::PronunciationXml => 0x00001004,
Self::PronunciationSig => 0x00001005,
Self::Pic1Png => 0x00001006,
Self::PubtoolinfoDat => 0x00001007,
Self::AppPlaygoChunkDat => 0x00001008,
Self::AppPlaygoChunkSha => 0x00001009,
Self::AppPlaygoManifestXml => 0x0000100a,
Self::ShareparamJson => 0x0000100b,
Self::ShareoverlayimagePng => 0x0000100c,
Self::SaveDataPng => 0x0000100d,
Self::ShareprivacyguardimagePng => 0x0000100e,
Self::Icon0Png => 0x00001200,
Self::Icon0PngIndexed(idx) => 0x00001201 + idx as u32,
Self::Pic0Png => 0x00001220,
Self::Snd0At9 => 0x00001240,
Self::Pic1PngIndexed(idx) => 0x00001241 + idx as u32,
Self::ChangeinfoXml => 0x00001260,
Self::ChangeinfoXmlIndexed(idx) => 0x00001261 + idx as u32,
Self::Icon0Dds => 0x00001280,
Self::Icon0DdsIndexed(idx) => 0x00001281 + idx as u32,
Self::Pic0Dds => 0x000012a0,
Self::Pic1Dds => 0x000012c0,
Self::Pic1DdsIndexed(idx) => 0x000012c1 + idx as u32,
Self::Trophy(idx) => 0x00001400 + idx as u32,
Self::Unknown(raw) => raw,
}
}
#[must_use]
pub const fn from_u32(raw: u32) -> Self {
match raw {
0x00000001 => Self::Digests,
0x00000010 => Self::EntryKeys,
0x00000020 => Self::PfsImageKey,
0x00000080 => Self::GeneralDigests,
0x00000100 => Self::Metas,
0x00000200 => Self::EntryNames,
0x00000400 => Self::LicenseDat,
0x00000401 => Self::LicenseInfo,
0x00000402 => Self::NptitleDat,
0x00000403 => Self::NpbindDat,
0x00000404 => Self::SelfinfoDat,
0x00000406 => Self::ImageinfoDat,
0x00000407 => Self::TargetDeltainfoDat,
0x00000408 => Self::OriginDeltainfoDat,
0x00000409 => Self::PsreservedDat,
0x00001000 => Self::ParamSfo,
0x00001001 => Self::PlaygoChunkDat,
0x00001002 => Self::PlaygoChunkSha,
0x00001003 => Self::PlaygoManifestXml,
0x00001004 => Self::PronunciationXml,
0x00001005 => Self::PronunciationSig,
0x00001006 => Self::Pic1Png,
0x00001007 => Self::PubtoolinfoDat,
0x00001008 => Self::AppPlaygoChunkDat,
0x00001009 => Self::AppPlaygoChunkSha,
0x0000100a => Self::AppPlaygoManifestXml,
0x0000100b => Self::ShareparamJson,
0x0000100c => Self::ShareoverlayimagePng,
0x0000100d => Self::SaveDataPng,
0x0000100e => Self::ShareprivacyguardimagePng,
0x00001200 => Self::Icon0Png,
0x00001201..=0x0000121F => Self::Icon0PngIndexed((raw - 0x00001201) as u8),
0x00001220 => Self::Pic0Png,
0x00001240 => Self::Snd0At9,
0x00001241..=0x0000125F => Self::Pic1PngIndexed((raw - 0x00001241) as u8),
0x00001260 => Self::ChangeinfoXml,
0x00001261..=0x0000127F => Self::ChangeinfoXmlIndexed((raw - 0x00001261) as u8),
0x00001280 => Self::Icon0Dds,
0x00001281..=0x0000129F => Self::Icon0DdsIndexed((raw - 0x00001281) as u8),
0x000012a0 => Self::Pic0Dds,
0x000012c0 => Self::Pic1Dds,
0x000012c1..=0x000012df => Self::Pic1DdsIndexed((raw - 0x000012c1) as u8),
0x00001400..=0x00001463 => Self::Trophy((raw - 0x00001400) as u8),
other => Self::Unknown(other),
}
}
#[must_use]
pub fn to_path<B: AsRef<Path>>(self, base: B) -> Option<PathBuf> {
let base = base.as_ref();
Some(match self {
Self::Digests => base.join("digests"),
Self::EntryKeys => base.join("entry_keys"),
Self::PfsImageKey => base.join("image_key"),
Self::GeneralDigests => base.join("general_digests"),
Self::Metas => base.join("metas"),
Self::EntryNames => base.join("entry_names"),
Self::LicenseDat => base.join("license.dat"),
Self::LicenseInfo => base.join("license.info"),
Self::NptitleDat => base.join("nptitle.dat"),
Self::NpbindDat => base.join("npbind.dat"),
Self::SelfinfoDat => base.join("selfinfo.dat"),
Self::ImageinfoDat => base.join("imageinfo.dat"),
Self::TargetDeltainfoDat => base.join("target-deltainfo.dat"),
Self::OriginDeltainfoDat => base.join("origin-deltainfo.dat"),
Self::PsreservedDat => base.join("psreserved.dat"),
Self::ParamSfo => base.join("param.sfo"),
Self::PlaygoChunkDat => base.join("playgo-chunk.dat"),
Self::PlaygoChunkSha => base.join("playgo-chunk.sha"),
Self::PlaygoManifestXml => base.join("playgo-manifest.xml"),
Self::PronunciationXml => base.join("pronunciation.xml"),
Self::PronunciationSig => base.join("pronunciation.sig"),
Self::Pic1Png => base.join("pic1.png"),
Self::PubtoolinfoDat => base.join("pubtoolinfo.dat"),
Self::AppPlaygoChunkDat => base.join("app").join("playgo-chunk.dat"),
Self::AppPlaygoChunkSha => base.join("app").join("playgo-chunk.sha"),
Self::AppPlaygoManifestXml => base.join("app").join("playgo-manifest.xml"),
Self::ShareparamJson => base.join("shareparam.json"),
Self::ShareoverlayimagePng => base.join("shareoverlayimage.png"),
Self::SaveDataPng => base.join("save_data.png"),
Self::ShareprivacyguardimagePng => base.join("shareprivacyguardimage.png"),
Self::Icon0Png => base.join("icon0.png"),
Self::Icon0PngIndexed(idx) => base.join(format!("icon0_{:02}.png", idx)),
Self::Pic0Png => base.join("pic0.png"),
Self::Snd0At9 => base.join("snd0.at9"),
Self::Pic1PngIndexed(idx) => base.join(format!("pic1_{:02}.png", idx)),
Self::ChangeinfoXml => base.join("changeinfo").join("changeinfo.xml"),
Self::ChangeinfoXmlIndexed(idx) => base
.join("changeinfo")
.join(format!("changeinfo_{:02}.xml", idx)),
Self::Icon0Dds => base.join("icon0.dds"),
Self::Icon0DdsIndexed(idx) => base.join(format!("icon0_{:02}.dds", idx)),
Self::Pic0Dds => base.join("pic0.dds"),
Self::Pic1Dds => base.join("pic1.dds"),
Self::Pic1DdsIndexed(idx) => base.join(format!("pic1_{:02}.dds", idx)),
Self::Trophy(idx) => base.join("trophy").join(format!("trophy{:02}.trp", idx)),
Self::Unknown(_) => return None,
})
}
}