/// <summary> /// Sets up the PKG header, body, and PFS images /// </summary> private void InitPkg() { // Write PFS first, to get stream length Logger("Preparing inner PFS..."); innerPfs = new PFS.PfsBuilder(PFS.PfsProperties.MakeInnerPFSProps(project), x => Logger($" [innerpfs] {x}")); Logger("Preparing outer PFS..."); outerPfs = new PFS.PfsBuilder(PFS.PfsProperties.MakeOuterPFSProps(project, innerPfs, EKPFS), x => Logger($" [outerpfs] {x}")); Logger("Preparing PKG header and body..."); BuildPkg(outerPfs.CalculatePfsSize()); }
/// <summary> /// Sets up the PKG header, body, and PFS images /// </summary> private void InitPkg() { if (project.VolumeType == GP4.VolumeType.pkg_ps4_ac_nodata) { Logger("Preparing PKG header and body..."); BuildPkg(0); } else { // Write PFS first, to get stream length Logger("Preparing inner PFS..."); innerPfs = new PFS.PfsBuilder(PFS.PfsProperties.MakeInnerPFSProps(project), x => Logger($" [innerpfs] {x}")); Logger("Preparing outer PFS..."); outerPfs = new PFS.PfsBuilder(PFS.PfsProperties.MakeOuterPFSProps(project, innerPfs, EKPFS), x => Logger($" [outerpfs] {x}")); Logger("Preparing PKG header and body..."); BuildPkg(outerPfs.CalculatePfsSize()); } }
//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); } }