public Pkg ReadPkg() { var header = ReadHeader(); s.Position = 0xFE0; var headerDigest = s.ReadBytes(32); var headerSignature = s.ReadBytes(256); s.Position = header.entry_table_offset; var metasEntry = new MetasEntry(); for (var i = 0; i < header.entry_count; i++) { metasEntry.Metas.Add(MetaEntry.Read(s)); } var pkg = new Pkg { Header = header, HeaderDigest = headerDigest, HeaderSignature = headerSignature, Metas = metasEntry, }; foreach (var entry in pkg.Metas.Metas) { switch (entry.id) { case EntryId.PARAM_SFO: s.Position = entry.DataOffset; pkg.ParamSfo = new SfoEntry(SFO.ParamSfo.FromStream(s)); break; case EntryId.ENTRY_KEYS: pkg.EntryKeys = KeysEntry.Read(entry, s); break; case EntryId.IMAGE_KEY: s.Position = entry.DataOffset; pkg.ImageKey = new GenericEntry(EntryId.IMAGE_KEY) { FileData = s.ReadBytes((int)entry.DataSize), meta = entry }; break; } } return(pkg); }
/// <summary> /// Calculates and writes the digests for the body (entries / SC filesystem) /// </summary> /// <param name="pkg"></param> /// <param name="s"></param> private static void CalcBodyDigests(Pkg pkg, Stream s) { // Entry digests var digests = pkg.Digests; var digestsOffset = pkg.Metas.Metas.Where(m => m.id == EntryId.DIGESTS).First().DataOffset; for (var i = 1; i < pkg.Metas.Metas.Count; i++) { var meta = pkg.Metas.Metas[i]; var hash = Crypto.Sha256(s, meta.DataOffset, meta.DataSize); Buffer.BlockCopy(hash, 0, digests.FileData, 32 * i, 32); s.Position = digestsOffset + 32 * i; s.Write(hash, 0, 32); } // Body Digest: SHA256 hash of entire body segment pkg.Header.body_digest = Crypto.Sha256(s, (long)pkg.Header.body_offset, (long)pkg.Header.body_size); // Digest table hash: SHA256 hash of digest table pkg.Header.digest_table_hash = Crypto.Sha256(pkg.Digests.FileData); using (var ms = new MemoryStream()) { // SC Entries Hash 1: Hash of 5 SC entries foreach (var entry in new Entry[] { pkg.EntryKeys, pkg.ImageKey, pkg.GeneralDigests, pkg.Metas, pkg.Digests }) { new SubStream(s, entry.meta.DataOffset, entry.meta.DataSize).CopyTo(ms); } pkg.Header.sc_entries1_hash = Crypto.Sha256(ms); pkg.Header.main_ent_data_size = (uint)ms.Length; // SC Entries Hash 2: Hash of 4 SC entries ms.SetLength(0); foreach (var entry in new Entry[] { pkg.EntryKeys, pkg.ImageKey, pkg.GeneralDigests, pkg.Metas }) { long size = entry.meta.DataSize; if (entry.Id == EntryId.METAS) { size = pkg.Header.sc_entry_count * 0x20; } new SubStream(s, entry.meta.DataOffset, size).CopyTo(ms); } pkg.Header.sc_entries2_hash = Crypto.Sha256(ms); } }
/// <summary> /// Calculates the digests for the GeneralDigests entry /// </summary> /// <param name="pkg"></param> private static void CalcGeneralDigests(Pkg pkg) { var sfo = pkg.ParamSfo.ParamSfo; var majorParamString = "ATTRIBUTE" + sfo["ATTRIBUTE"] + "CATEGORY" + sfo["CATEGORY"] + "FORMAT" + sfo["FORMAT"] + "PUBTOOLVER" + sfo["PUBTOOLVER"]; pkg.GeneralDigests.Set(GeneralDigest.MajorParamDigest, Crypto.Sha256(Encoding.ASCII.GetBytes(majorParamString))); using (var ms = new MemoryStream()) { ms.Write(Encoding.ASCII.GetBytes(pkg.Header.content_id), 0, 36); ms.Write(new byte[12], 0, 12); ms.WriteInt32BE((int)pkg.Header.drm_type); ms.WriteInt32BE((int)pkg.Header.content_type); if (pkg.Header.content_type == ContentType.AC || pkg.Header.content_type == ContentType.GD || pkg.Header.content_flags.HasFlag(ContentFlags.GD_AC)) { ms.Write(pkg.Header.pfs_image_digest, 0, 32); } ms.Write(pkg.GeneralDigests.Digests[GeneralDigest.MajorParamDigest], 0, 32); pkg.GeneralDigests.Set(GeneralDigest.ContentDigest, Crypto.Sha256(ms)); } pkg.GeneralDigests.Set(GeneralDigest.GameDigest, pkg.Header.pfs_image_digest); using (var ms = new MemoryStream()) { new PkgWriter(ms).WriteHeader(pkg.Header); using (var hash = System.Security.Cryptography.SHA256.Create()) { ms.Position = 0; hash.TransformBlock(ms.ReadBytes(64), 0, 64, null, 0); ms.Position = 0x400; hash.TransformFinalBlock(ms.ReadBytes(128), 0, 128); pkg.GeneralDigests.Set(GeneralDigest.HeaderDigest, hash.Hash); } } pkg.GeneralDigests.Set(GeneralDigest.ParamDigest, Crypto.Sha256(pkg.ParamSfo.ParamSfo.Serialize())); }
//private static void CalcPlaygoDigests(Pkg pkg, MemoryMappedFile file) //{ // const int CHUNK_SIZE = 0x10000; // int totalChunks = (int)(pkg.Header.pfs_image_size / CHUNK_SIZE); // int chunkOffset = (int)(pkg.Header.pfs_image_offset / CHUNK_SIZE); // var FileData = pkg.ChunkSha.FileData; // using (var view = file.CreateViewAccessor(0, (long)pkg.Header.package_size)) // { // //Parallel.ForEach( // // Enumerable.Range(chunkOffset, totalChunks), // // () => Tuple.Create(SHA256.Create(), new byte[CHUNK_SIZE]), // // (chunk, _, local) => // // { // // var(sha, buffer) = local; // // view.ReadArray(chunk * CHUNK_SIZE, buffer, 0, CHUNK_SIZE); // // Buffer.BlockCopy(sha.ComputeHash(buffer), 0, FileData, chunk * 4, 4); // // return local; // // }, // // buffer => { buffer.Item1.Dispose(); }); // } //} /// <summary> /// Creates the Pkg object. Initializes the header and body. /// </summary> public void BuildPkg(long pfsSize) { pkg = new Pkg(); var volType = project.VolumeType; pkg.Header = new Header { CNTMagic = "\u007fCNT", flags = PKGFlags.Unknown, unk_0x08 = 0, unk_0x0C = 0xF, entry_count = 6, sc_entry_count = 6, entry_count_2 = 6, entry_table_offset = 0x2A80, main_ent_data_size = 0xD00, body_offset = 0x2000, body_size = 0x7E000, content_id = project.ContentId, drm_type = DrmType.PS4, content_type = VolTypeToContentType(volType), content_flags = ContentFlags.Unk_x8000000 | VolTypeToContentFlags(volType), // TODO promote_size = 0, version_date = 0x20161020, version_hash = 0x1738551, unk_0x88 = 0, unk_0x8C = 0, unk_0x90 = 0, unk_0x94 = 0, iro_tag = IROTag.None, ekc_version = 1, sc_entries1_hash = new byte[32], sc_entries2_hash = new byte[32], digest_table_hash = new byte[32], body_digest = new byte[32], unk_0x400 = 1, pfs_image_count = 1, pfs_flags = 0x80000000000003CC, pfs_image_offset = 0x80000, pfs_image_size = (ulong)pfsSize, mount_image_offset = 0, mount_image_size = 0, package_size = 0, pfs_signed_size = 0x10000, pfs_cache_size = 0xD0000, pfs_image_digest = new byte[32], pfs_signed_digest = new byte[32], pfs_split_size_nth_0 = 0, pfs_split_size_nth_1 = 0 }; pkg.HeaderDigest = new byte[32]; pkg.HeaderSignature = new byte[0x100]; pkg.EntryKeys = new KeysEntry( project.ContentId, project.Passcode); pkg.ImageKey = new GenericEntry(EntryId.IMAGE_KEY) { FileData = Crypto.RSA2048EncryptKey(RSAKeyset.FakeKeyset.Modulus, EKPFS) }; pkg.GeneralDigests = new GeneralDigestsEntry(); pkg.Metas = new MetasEntry(); pkg.Digests = new GenericEntry(EntryId.DIGESTS); pkg.EntryNames = new NameTableEntry(); pkg.LicenseDat = new GenericEntry(EntryId.LICENSE_DAT) { FileData = GenLicense() }; pkg.LicenseInfo = new GenericEntry(EntryId.LICENSE_INFO) { FileData = GenLicenseInfo() }; var paramSfoFile = project.RootDir.GetFile("sce_sys/param.sfo"); if (paramSfoFile == null) { throw new Exception("Missing param.sfo!"); } using (var paramSfo = new MemoryStream()) { paramSfoFile.Write(paramSfo); paramSfo.Position = 0; var sfo = SFO.ParamSfo.FromStream(paramSfo); pkg.ParamSfo = new SfoEntry(sfo); string date = "", time = ""; if (project.CreationDate == new DateTime()) { date = "c_date=" + DateTime.UtcNow.ToString("yyyyMMdd"); if (project.UseCreationTime) { time = ",c_time=" + DateTime.UtcNow.ToString("HHmmss"); } } else { date = "c_date=" + project.CreationDate.ToString("yyyyMMdd"); if (project.UseCreationTime) { time = ",c_time=" + project.CreationDate.ToString("HHmmss"); } } var sizeInfo = $",img0_l0_size={pkg.Header.pfs_image_size / (1000 * 1000)}" + $",img0_l1_size=0" + $",img0_sc_ksize=512" + $",img0_pc_ksize=832"; sfo["PUBTOOLINFO"] = new SFO.Utf8Value("PUBTOOLINFO", date + time + sizeInfo, 0x200); sfo["PUBTOOLVER"] = new SFO.IntegerValue("PUBTOOLVER", 0x02890000); } pkg.PsReservedDat = new GenericEntry(EntryId.PSRESERVED_DAT) { FileData = new byte[0x2000] }; pkg.Entries = new List <Entry> { pkg.EntryKeys, pkg.ImageKey, pkg.GeneralDigests, pkg.Metas, pkg.Digests, pkg.EntryNames }; if (pkg.Header.content_type == ContentType.GD) { pkg.ChunkDat = PlayGo.ChunkDat.FromProject(project.ContentId); pkg.ChunkSha = new GenericEntry(EntryId.PLAYGO_CHUNK_SHA, "playgo-chunk.sha"); pkg.ChunkXml = new GenericEntry(EntryId.PLAYGO_MANIFEST_XML, "playgo-manifest.xml") { FileData = PlayGo.Manifest.Default }; // Add playgo entries for GD PKGs pkg.Entries.AddRange(new Entry[] { pkg.ChunkDat, pkg.ChunkSha, pkg.ChunkXml }); } pkg.Entries.AddRange(new Entry[] { pkg.LicenseDat, pkg.LicenseInfo, pkg.ParamSfo, pkg.PsReservedDat }); foreach (var file in project.RootDir.Dirs.Where(f => f.name == "sce_sys").First().Files.Where(f => EntryNames.NameToId.ContainsKey(f.name))) { var name = file.name; if (name == "param.sfo") { continue; } var entry = new FileEntry(EntryNames.NameToId[name], file.Write, (uint)file.Size); pkg.Entries.Add(entry); } pkg.Digests.FileData = new byte[pkg.Entries.Count * Pkg.HASH_SIZE]; // 1st pass: set names foreach (var entry in pkg.Entries.OrderBy(e => e.Name)) { pkg.EntryNames.GetOffset(entry.Name); } // estimate size for playgo if (pkg.Header.content_type == ContentType.GD) { long bodySize = 0; foreach (var e in pkg.Entries) { bodySize += (e.Length + 15) & ~15; // round up to nearest 16 } bodySize += 32 * pkg.Entries.Count; // metas bodySize += 4 * (pfsSize / 0x10000); // playgo hashes of pfs if (bodySize + (long)pkg.Header.body_offset >= (long)pkg.Header.pfs_image_offset) { pkg.Header.pfs_image_offset = (ulong)((bodySize + (long)pkg.Header.body_offset + 0xFFFF) & ~0xFFFFL); } pkg.ChunkSha.FileData = new byte[4 * ((pkg.Header.pfs_image_offset + pkg.Header.pfs_image_size) / 0x10000)]; } // 2nd pass: set sizes, offsets in meta table var dataOffset = 0x2000u; var flagMap = new Dictionary <EntryId, uint>() { { EntryId.DIGESTS, 0x40000000 }, { EntryId.ENTRY_KEYS, 0x60000000 }, { EntryId.IMAGE_KEY, 0xE0000000 }, { EntryId.GENERAL_DIGESTS, 0x60000000 }, { EntryId.METAS, 0x60000000 }, { EntryId.ENTRY_NAMES, 0x40000000 }, { EntryId.LICENSE_DAT, 0x80000000 }, { EntryId.LICENSE_INFO, 0x80000000 }, }; var keyMap = new Dictionary <EntryId, uint> { { EntryId.IMAGE_KEY, 3u << 12 }, { EntryId.LICENSE_DAT, 3u << 12 }, { EntryId.LICENSE_INFO, 2u << 12 }, }; foreach (var entry in pkg.Entries) { var e = new MetaEntry { id = entry.Id, NameTableOffset = pkg.EntryNames.GetOffset(entry.Name), DataOffset = dataOffset, DataSize = entry.Length, // TODO Flags1 = flagMap.GetOrDefault(entry.Id), Flags2 = keyMap.GetOrDefault(entry.Id), }; pkg.Metas.Metas.Add(e); if (entry == pkg.Metas) { e.DataSize = (uint)pkg.Entries.Count * 32; } dataOffset += e.DataSize; var align = dataOffset % 16; if (align != 0) { dataOffset += 16 - align; } entry.meta = e; } pkg.Metas.Metas.Sort((e1, e2) => e1.id.CompareTo(e2.id)); pkg.Header.entry_count = (uint)pkg.Entries.Count; pkg.Header.entry_count_2 = (ushort)pkg.Entries.Count; pkg.Header.body_size = pkg.Header.pfs_image_offset - pkg.Header.body_offset; pkg.Header.package_size = pkg.Header.mount_image_size = pkg.Header.pfs_image_offset + pkg.Header.pfs_image_size; if (pkg.Header.content_type == ContentType.GD) { // Important sizes for PlayGo ChunkDat pkg.ChunkDat.MchunkAttrs[0].size = pkg.Header.package_size; pkg.ChunkDat.InnerMChunkAttrs[0].size = (ulong)innerPfs.CalculatePfsSize(); // GD pkgs set promote_size to the size of the PKG before the PFS image? pkg.Header.promote_size = (uint)(pkg.Header.body_size + pkg.Header.body_offset); } }