private static ValidateResult checkCategoryMatchesPkgType(Gp4Project proj, string dir, ParamSfo sfo) { var sfoCategory = sfo["CATEGORY"].ToString(); var pkgType = proj.volume.Type; bool ok = true; switch (pkgType) { case VolumeType.pkg_ps4_app: if (!sfoCategory.StartsWith("g")) { ok = false; } break; case VolumeType.pkg_ps4_ac_data: case VolumeType.pkg_ps4_ac_nodata: case VolumeType.pkg_ps4_theme: case VolumeType.pkg_ps4_sf_theme: if (sfoCategory != "ac") { ok = false; } break; } if (!ok) { return(ValidateResult.Warning( $"SFO CATEGORY {sfoCategory} is not valid for PKG volume type {pkgType}.")); } return(null); }
private static ValidateResult checkContentIdLength(Gp4Project proj, string dir) { if (proj.volume.Package.ContentId.Length != 36) { return(ValidateResult.Fatal("PKG Content ID must be 36 characters long.")); } return(null); }
private static ValidateResult checkPasscode(Gp4Project proj, string dir) { if (proj.volume.Package.Passcode.Length != 32) { return(ValidateResult.Fatal("Passcode must be 32 characters long.")); } return(null); }
private static ValidateResult checkAllFilesExist(Gp4Project proj, string dir) { var missingFiles = proj.files.Items .Where(f => Path.Combine(dir, f.OrigPath) is string filePath && !File.Exists(filePath)) .Aggregate((string)null, (s, f) => s == null ? f.OrigPath : (s + ", " + f.OrigPath)); if (missingFiles != null) { return(ValidateResult.Fatal("Could not find source file(s): " + missingFiles)); } return(null); }
private static ValidateResult checkContentIdMatchesTitleIdSfo(Gp4Project proj, string dir, ParamSfo sfo) { var sfoContentId = sfo["CONTENT_ID"].ToString(); var sfoTitleId = sfo["TITLE_ID"].ToString(); if (sfoContentId.Substring(7).StartsWith(sfoTitleId)) { return(null); } return(ValidateResult.Warning( $"SFO TITLE_ID {sfoTitleId} does not match the CONTENT_ID {sfoContentId}.")); }
private static ValidateResult checkContentIdMatchesSfo(Gp4Project proj, string dir, ParamSfo sfo) { var pkgContentId = proj.volume.Package.ContentId; var sfoContentId = sfo["CONTENT_ID"].ToString(); if (pkgContentId != sfoContentId) { return(ValidateResult.Warning( $"PKG Content ID {pkgContentId} does not match CONTENT_ID {sfoContentId} in param.sfo.")); } return(null); }
private static ValidateResult checkContentIdFormat(Gp4Project proj, string dir) { var pkgContentId = proj.volume.Package.ContentId; Regex contentIdReg = new Regex("^[A-Z]{2}[0-9]{4}-[A-Z]{4}[0-9]{5}_00-[A-Z0-9]{16}$"); if (contentIdReg.IsMatch(pkgContentId)) { return(null); } return(ValidateResult.Warning( "PKG Content ID is the wrong format. " + "Format should be XXYYYY-XXXXYYYYY_00-ZZZZZZZZZZZZZZZZ, where X is a letter, Y is a number, and Z is either.")); }
private static ValidateResult checkDuplicateFilenames(Gp4Project proj, string dir) { var dupeFiles = proj.files.Items .GroupBy(f => f.TargetPath) .Where(g => g.Count() > 1) .Select(g => g.Key) .Aggregate((string)null, (s, f) => s == null ? f : (s + ", " + f)); if (dupeFiles != null) { return(ValidateResult.Fatal("PKG has duplicate filename(s): " + dupeFiles)); } return(null); }
public static List <ValidateResult> ValidateProject(Gp4Project proj, string projDir) { var ret = new List <ValidateResult>(); foreach (var check in commonChecks) { var result = check(proj, projDir); if (result != null) { ret.Add(result); } } // Checks with project and SFO file if (proj.files.Items.Where(f => f.TargetPath == "sce_sys/param.sfo").FirstOrDefault() is Gp4File sfoFile && Path.Combine(projDir, sfoFile.OrigPath) is string sfoPath && File.Exists(sfoPath)) { ParamSfo sfoObject = null; try { using (var f = File.OpenRead(sfoPath)) { sfoObject = ParamSfo.FromStream(f); } } catch (Exception e) { ret.Add(ValidateResult.Fatal("Could not load param.sfo file: " + e.Message)); } if (sfoObject != null) { foreach (var check in sfoChecks) { var result = check(proj, projDir, sfoObject); if (result != null) { ret.Add(result); } } } }
private static ValidateResult checkPkgVolumeType(Gp4Project proj, string dir) { var pkgType = proj.volume.Type; switch (pkgType) { case VolumeType.pkg_ps4_app: break; case VolumeType.pkg_ps4_ac_data: break; case VolumeType.pkg_ps4_ac_nodata: break; default: return(ValidateResult.Fatal( "Unsupported PKG volume type: " + pkgType)); } return(null); }
/// <summary> /// Creates a new, empty GP4 project with the given VolumeType. /// </summary> /// <param name="type">The type of project to make</param> /// <returns>A new blank project with defaults for the given VolumeType</returns> public static Gp4Project Create(VolumeType type) { var proj = new Gp4Project { files = new Files(), Format = "gp4", RootDir = new List <Dir>(), version = 1000, volume = new Volume { volume_ts = DateTime.UtcNow.ToString("s").Replace('T', ' '), Package = new PackageInfo { ContentId = "XXXXXX-CUSA00000_00-ZZZZZZZZZZZZZZZZ", Passcode = "00000000000000000000000000000000" } } }; proj.SetType(type); return(proj); }
/// <summary> /// Writes the GP4 project xml to the given stream at the stream's current position. /// </summary> /// <param name="proj">The project to write</param> /// <param name="s">The stream to write the project to</param> public static void WriteTo(Gp4Project proj, System.IO.Stream s) { XmlSerializer mySerializer = new XmlSerializer(typeof(Gp4Project)); mySerializer.Serialize(s, proj); }
public static void CreateProjectFromPKG(string outputDir, MemoryMappedFile pkgFile, string passcode = null) { Directory.CreateDirectory(outputDir); Pkg pkg; using (var f = pkgFile.CreateViewStream(0, 0, MemoryMappedFileAccess.Read)) pkg = new PkgReader(f).ReadPkg(); passcode = passcode ?? "00000000000000000000000000000000"; // Initialize project parameters var project = Gp4Project.Create(ContentTypeToVolumeType(pkg.Header.content_type)); project.volume.Package.Passcode = passcode; project.volume.Package.ContentId = pkg.Header.content_id; project.volume.Package.AppType = project.volume.Type == VolumeType.pkg_ps4_app ? "full" : null; project.volume.Package.StorageType = project.volume.Type == VolumeType.pkg_ps4_app ? "digital50" : null; if (pkg.Header.content_type == ContentType.AC || pkg.Header.content_type == ContentType.AL) { pkg.LicenseDat.DecryptSecretWithDebugKey(); var entitlementKey = new byte[16]; Buffer.BlockCopy(pkg.LicenseDat.Secret, 0x70, entitlementKey, 0, 16); pkg.LicenseDat.EncryptSecretWithDebugKey(); project.volume.Package.EntitlementKey = entitlementKey.ToHexCompact(); } // Extract entry filesystem var sys_dir = Path.Combine(outputDir, "sce_sys"); var sys_projdir = project.AddDir(null, "sce_sys"); Directory.CreateDirectory(sys_dir); foreach (var meta in pkg.Metas.Metas) { // Skip entries that are auto-generated or that we don't know the filenames for if (GeneratedEntries.Contains(meta.id)) { continue; } if (!EntryNames.IdToName.ContainsKey(meta.id)) { continue; } var entryName = EntryNames.IdToName[meta.id]; var filename = Path.Combine(sys_dir, entryName); // Create directories for entries within directories if (entryName.Contains('/')) { var entryDir = entryName.Substring(0, entryName.LastIndexOf('/')); Directory.CreateDirectory(Path.Combine(sys_dir, entryDir)); Dir d = sys_projdir; foreach (var breadcrumb in entryDir.Split('/')) { d = project.AddDir(d, breadcrumb); } } // Add the entry to the project project.files.Items.Add(new Gp4File() { OrigPath = "sce_sys/" + entryName, TargetPath = "sce_sys/" + entryName }); // Save to the filesystem using (var s = pkgFile.CreateViewStream(meta.DataOffset, meta.DataSize, MemoryMappedFileAccess.Read)) using (var entryFile = File.Create(filename)) { s.CopyTo(entryFile); } } // Fixup the param.sfo using (var f = File.Open(Path.Combine(outputDir, "sce_sys/param.sfo"), FileMode.Open)) { var sfo = SFO.ParamSfo.FromStream(f); var pubtoolinfo = (sfo["PUBTOOLINFO"] as SFO.Utf8Value).Value; var c_date = ""; var c_time = ""; foreach (var info in pubtoolinfo.Split(',')) { var info2 = info.Split('='); switch (info2[0]) { case "c_date": c_date = info2[1].Substring(0, 4) + "-" + info2[1].Substring(4, 2) + "-" + info2[1].Substring(6, 2); break; case "c_time": c_time = " " + info2[1].Substring(0, 2) + ":" + info2[1].Substring(2, 2) + ":" + info2[1].Substring(4, 2); break; } } project.volume.Package.CreationDate = c_date + c_time; sfo["PUBTOOLVER"] = null; sfo["PUBTOOLINFO"] = null; sfo.Write(f); } // Extract files from the PFS filesystem byte[] ekpfs; if (pkg.CheckPasscode(passcode)) { ekpfs = Crypto.ComputeKeys(pkg.Header.content_id, passcode, 1); } else { ekpfs = pkg.GetEkpfs(); } using (var va = pkgFile.CreateViewAccessor((long)pkg.Header.pfs_image_offset, (long)pkg.Header.pfs_image_size, MemoryMappedFileAccess.Read)) { var outerPfs = new PfsReader(va, pkg.Header.pfs_flags, ekpfs); var inner = new PfsReader(new PFSCReader(outerPfs.GetFile("pfs_image.dat").GetView())); // Convert PFS image timestamp from UNIX time and save it in the project project.volume.TimeStamp = new DateTime(1970, 1, 1) .AddSeconds(inner.Header.InodeBlockSig.Time1_sec); var uroot = inner.GetURoot(); Dir dir = null; var projectDirs = new Queue <Dir>(); var pfsDirs = new Queue <PfsReader.Dir>(); pfsDirs.Enqueue(uroot); projectDirs.Enqueue(dir); while (pfsDirs.Count > 0) { dir = projectDirs.Dequeue(); if (dir != null) { Directory.CreateDirectory(Path.Combine(outputDir, dir.Path)); } foreach (var f in pfsDirs.Dequeue().children) { if (f is PfsReader.Dir d) { pfsDirs.Enqueue(d); projectDirs.Enqueue(project.AddDir(dir, d.name)); } else if (f is PfsReader.File file) { // Remove "/uroot/" var path = file.FullName.Substring(7); project.files.Items.Add(new Gp4File() { OrigPath = path, TargetPath = path }); file.Save(Path.Combine(outputDir, path)); } } } } // Last step: save the project file using (var f = File.Create(Path.Combine(outputDir, "Project.gp4"))) { Gp4Project.WriteTo(project, f); } }