/// <summary> /// Computes the final digests and writes them to the PKG /// </summary> /// <param name="pkgStream">PKG file stream</param> private void FinishPkg(Stream pkgStream) { Logger("Calculating SHA256 of finished outer PFS..."); // Set PFS Image 1st block and full SHA256 hashes (mount image) pkg.Header.pfs_signed_digest = Crypto.Sha256(pkgStream, (long)pkg.Header.pfs_image_offset, 0x10000); pkg.Header.pfs_image_digest = Crypto.Sha256(pkgStream, (long)pkg.Header.pfs_image_offset, (long)pkg.Header.pfs_image_size); foreach (var a in pkg.CalcGeneralDigests()) { pkg.GeneralDigests.Set(a.Key, a.Value); } // Write body now because it will make calculating hashes easier. var writer = new PkgWriter(pkgStream); writer.WriteBody(pkg, project.ContentId, project.Passcode); CalcBodyDigests(pkg, pkgStream); // Now write header pkgStream.Position = 0; writer.WriteHeader(pkg.Header); // Final Pkg digest and signature pkg.HeaderDigest = Crypto.Sha256(pkgStream, 0, 0xFE0); pkgStream.Position = 0xFE0; pkgStream.Write(pkg.HeaderDigest, 0, pkg.HeaderDigest.Length); byte[] header_sha256 = Crypto.Sha256(pkgStream, 0, 0x1000); pkgStream.Position = 0x1000; pkgStream.Write(pkg.HeaderSignature = Crypto.RSA2048EncryptKey(Keys.PkgSignKey, header_sha256), 0, 256); }
/// <summary> /// Writes your PKG to the given stream. /// Assumes exclusive use of the stream (writes are absolute, relative to 0) /// </summary> /// <param name="s"></param> /// <returns>Completed Pkg structure</returns> public Pkg Write(Stream s) { var pkg = BuildPkg(); var writer = new PkgWriter(s); // Write PFS first, to get stream length s.Position = (long)pkg.Header.pfs_image_offset; var pfsStream = new OffsetStream(s, s.Position); new PFS.PfsBuilder().BuildPfs(new PFS.PfsProperties { BlockSize = 65536, output = pfsStream, proj = project, projDir = projectDir, }); var align = s.Length % 65536; if (align != 0) { s.SetLength(s.Length + 65536 - align); } // TODO: Encrypt PFS (could also be done while writing image) // TODO: Generate hashes in Entries (body) // TODO: Calculate keys in entries (image key, etc) // Write body now because it will make calculating hashes easier. writer.WriteBody(pkg); CalcHeaderHashes(pkg, s); // Update header sizes now that we know how big things are... UpdateHeaderInfo(pkg, s.Length, pfsStream.Length); // Now write header s.Position = 0; writer.WritePkg(pkg); return(pkg); }
/// <summary> /// Writes your PKG to the given stream. /// Assumes exclusive use of the stream (writes are absolute, relative to 0) /// </summary> /// <param name="s"></param> /// <returns>Completed Pkg structure</returns> public Pkg Write(Stream s) { var pkg = BuildPkg(); var writer = new PkgWriter(s); // Write PFS first, to get stream length s.Position = (long)pkg.Header.pfs_image_offset; var EKPFS = Crypto.ComputeKeys(project.volume.Package.ContentId, project.volume.Package.Passcode, 1); var pfsStream = new OffsetStream(s, s.Position); Console.WriteLine("Preparing inner PFS..."); var innerPfs = new PFS.PfsBuilder(PFS.PfsProperties.MakeInnerPFSProps(project, projectDir), x => Console.WriteLine($"[innerpfs] {x}")); Console.WriteLine("Preparing outer PFS..."); var outerPfs = new PFS.PfsBuilder(PFS.PfsProperties.MakeOuterPFSProps(project, innerPfs, EKPFS), x => Console.WriteLine($"[outerpfs] {x}")); outerPfs.WriteImage(pfsStream); // Update header sizes now that we know how big things are... UpdateHeaderInfo(pkg, s.Length, pfsStream.Length); pkg.Header.body_size = pkg.Header.pfs_image_offset - pkg.Header.body_offset; // Set PFS Image 1st block and full SHA256 hashes (mount image) pkg.Header.pfs_signed_digest = Crypto.Sha256(s, (long)pkg.Header.pfs_image_offset, 0x10000); pkg.Header.pfs_image_digest = Crypto.Sha256(s, (long)pkg.Header.pfs_image_offset, (long)pkg.Header.pfs_image_size); if (pkg.ParamSfo.ParamSfo.GetValueByName("PUBTOOLINFO") is SFO.Utf8Value v) { v.Value += $",img0_l0_size={pfsStream.Length / (1000 * 1000)}" + $",img0_l1_size=0" + $",img0_sc_ksize=512" + $",img0_pc_ksize=832"; } // TODO: Generate hashes in Entries (body) var majorParamString = "ATTRIBUTE" + pkg.ParamSfo.ParamSfo.GetValueByName("ATTRIBUTE") + "CATEGORY" + pkg.ParamSfo.ParamSfo.GetValueByName("CATEGORY") + "FORMAT" + pkg.ParamSfo.ParamSfo.GetValueByName("FORMAT") + "PUBTOOLVER" + pkg.ParamSfo.ParamSfo.GetValueByName("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())); pkg.ImageKey.FileData = Crypto.RSA2048EncryptKey(RSAKeyset.FakeKeyset.Modulus, EKPFS); // Write body now because it will make calculating hashes easier. writer.WriteBody(pkg, project.volume.Package.ContentId, project.volume.Package.Passcode); CalcHeaderHashes(pkg, s); // Now write header s.Position = 0; writer.WritePkg(pkg); // Pkg Signature byte[] header_sha256 = Crypto.Sha256(s, 0, 0x1000); s.Position = 0x1000; s.Write(Crypto.RSA2048EncryptKey(Keys.PkgSignKey, header_sha256), 0, 256); return(pkg); }