/// <summary> /// Check the updates PSN servers and insert/update the relevant DB records. /// </summary> /// <param name="db">The database context.</param> /// <param name="title_id">The TITLE_ID to search updates for.</param> public static void Check(Database db, string title_id) { var document = Update.GetUpdateData(title_id); if (document == null) { return; } TitlePatch patch = DeserializeFromXML <TitlePatch>(document); if (patch.Tag != null) { foreach (var update_pkg in Nullable(patch.Tag.Packages)) { Update update = new Update { CONTENT_ID = update_pkg.ContentId, VERSION = GetVersionFromString(update_pkg.Version), TYPE = db.Type[update_pkg.Type ?? "cumulative"], URL = update_pkg.Url, Size = update_pkg.Size, Sha1Sum = HexStringToByteArray(update_pkg.Sha1Sum), SysVer = GetVersionFromBcd(update_pkg.SysVer), }; var app = db.Apps.Where(x => x.CONTENT_ID == update.CONTENT_ID).FirstOrDefault(); if (app == null) { app = new App { CONTENT_ID = update.CONTENT_ID, TITLE_ID = update.CONTENT_ID.Substring(7, 9), NAME = document.Descendants("title").FirstOrDefault().Value }; Console.WriteLine($"[NOTE] Adding new App {app.CONTENT_ID}: {app.NAME}"); db.Apps.Add(app); db.SaveChanges(); } var pkg = db.Pkgs.Where(x => x.URL == update.URL).FirstOrDefault(); if (pkg == null) { pkg = Pkg.CreatePkg(update.URL); if (pkg == null) { continue; } db.Pkgs.Add(pkg); db.SaveChanges(); } update.PKG_ID = pkg.ID; update.Upsert(db, true); if (update_pkg.HybridPackage != null) { // Hybrid packages are derived from parent update.CONTENT_ID = update_pkg.HybridPackage.ContentId; update.TYPE = db.Type["hybrid"]; update.URL = update_pkg.HybridPackage.Url; update.Size = update_pkg.HybridPackage.Size; update.Sha1Sum = HexStringToByteArray(update_pkg.HybridPackage.Sha1Sum); pkg = db.Pkgs.Where(x => x.URL == update.URL).FirstOrDefault(); if (pkg == null) { pkg = Pkg.CreatePkg(update.URL); db.Pkgs.Add(pkg); db.SaveChanges(); } update.PKG_ID = pkg.ID; update.Upsert(db, true); } } } }
/// <summary> /// Create a new Pkg object from a PKG URL. /// </summary> /// <param name="url">The source URL.</param> /// <returns>A new Pkg instance, or null on error.</returns> public static Pkg CreatePkg(string url) { int i; for (i = 0; i < pkg_start.Count; i++) { if (url.StartsWith(pkg_start[i])) { break; } } if (i >= pkg_start.Count) { Console.Error.WriteLine($"[ERROR] '{url}' does not match known PSN URLs"); return(null); } // Trim extra data after pkg (such as "?country=...") url = url.Split('?').First(); if (!url.EndsWith(".pkg")) { Console.Error.WriteLine($"[ERROR] '{url}' does not end with '.pkg'"); return(null); } byte[] pkg_header = GetPkgData(url, 0, (UInt32)(PKG_HEADER_SIZE + PKG_HEADER_EXT_SIZE)); if (pkg_header == null) { Console.Error.WriteLine($"[ERROR] Could not read PKG header from '{url}'"); return(null); } if (!MemCmp(pkg_header, pkg_magic, 0)) { Console.WriteLine("[ERROR] '{url}' is not a PKG file"); return(null); } var pkg = new Pkg { URL = url, CONTENT_ID = GetContentIdFromPkg(url, pkg_header) }; if (!App.ValidateContentID(pkg.CONTENT_ID)) { Console.WriteLine("[ERROR] Could not get a valid CONTENT_ID from PKG URL '{url}'"); return(null); } // http://www.psdevwiki.com/ps3/PKG_files UInt32 info_offset = GetBe32(pkg_header, 0x08); UInt32 info_count = GetBe32(pkg_header, 0x0c); UInt32 item_count = GetBe32(pkg_header, 0x14); pkg.SIZE = GetBe64(pkg_header, 0x18); UInt64 data_offset = GetBe64(pkg_header, 0x20); byte[] iv = MemCpy(pkg_header, 0x70, 0x10); int key_type = pkg_header[0xe7] & 0x07; byte[] pkg_info = GetPkgData(url, info_offset, (UInt32)(data_offset - info_offset)); if (pkg_info != null) { byte[] sfo_data = null; UInt32 type, size; string default_category = null; for (int count = 0, pos = 0; (count < info_count) && (pos < pkg_info.Length); count++, pos += (int)size) { type = GetBe32(pkg_info, pos); size = GetBe32(pkg_info, pos + 4); pos += 8; if (type == 0x02) { content_type.TryGetValue(GetBe32(pkg_info, pos), out default_category); } else if (type == 0x0E) { UInt32 sfo_pos = GetBe32(pkg_info, pos); UInt32 sfo_size = GetBe32(pkg_info, pos + 4); // Avoid a server round trip, as the SFO should be part of pkg_info if ((sfo_pos >= info_offset) && (sfo_pos - info_offset + sfo_size <= pkg_info.Length)) { sfo_data = MemCpy(pkg_info, (int)(sfo_pos - info_offset), (int)sfo_size); } else { sfo_data = GetPkgData(url, sfo_pos, sfo_size); } break; } } if (sfo_data != null) { var sfo = new Sfo(sfo_data); pkg.APP_VER = sfo.AppVer; pkg.CATEGORY = sfo.Category ?? default_category; pkg.C_DATE = sfo.CDate; pkg.SYS_VER = sfo.SysVer; } else { pkg.CATEGORY = default_category; } } pkg.SHA1 = ByteArrayToHexString(GetPkgSha1(url)); pkg.V_DATE = ((UInt32)DateTime.Now.Year) * 10000 + ((UInt32)DateTime.Now.Month) * 100 + ((UInt32)DateTime.Now.Day); return(pkg); }