├── dump.cmd ├── Properties └── launchSettings.json ├── VitaDB.sln ├── VitaDB.csproj ├── VitaDB.ini ├── .gitattributes ├── Sfo.cs ├── .gitignore ├── Settings.cs ├── Database.cs ├── Rif.cs ├── README.md ├── Update.cs ├── App.cs ├── Json.cs ├── Utilities.cs ├── Pkg.cs └── LICENSE /dump.cmd: -------------------------------------------------------------------------------- 1 | dotnet bin\Release\net5.0\VitaDB.dll -m 2 | dotnet bin\Release\net5.0\VitaDB.dll -d 3 | -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "VitaDB": { 4 | "commandName": "Project", 5 | "commandLineArgs": "--wait-for-key", 6 | "workingDirectory": "$(SolutionDir)" 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /VitaDB.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26730.16 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VitaDB", "VitaDB.csproj", "{817B273C-6B4C-4BB0-BBF5-0CC8F865F479}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {817B273C-6B4C-4BB0-BBF5-0CC8F865F479}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {817B273C-6B4C-4BB0-BBF5-0CC8F865F479}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {817B273C-6B4C-4BB0-BBF5-0CC8F865F479}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {817B273C-6B4C-4BB0-BBF5-0CC8F865F479}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {922102DA-2F2A-4681-87A5-240A662EA2E0} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /VitaDB.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 0.9 7 | VitaSmith 8 | © 2017-2019 VitaSmith 9 | https://www.gnu.org/licenses/gpl-3.0.en.html 10 | https://github.com/VitaSmith/VitaDB 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /VitaDB.ini: -------------------------------------------------------------------------------- 1 | ; VitaDB INI file 2 | ; Note: This UTF-8 file should be saved with a BOM 3 | 4 | [db] 5 | name = VitaDB.db 6 | local_sql = VitaDB.sql 7 | remote_sql = https://raw.githubusercontent.com/VitaSmith/VitaDB/master/VitaDB.sql 8 | local_cache = PkgCache.json 9 | remote_cache = https://raw.githubusercontent.com/VitaSmith/VitaDB/master/PkgCache.json 10 | 11 | [csv] 12 | ;separator = , 13 | nps_app = http://nopaystation.com/tsv/PSV_GAMES.tsv 14 | nps_demo = http://nopaystation.com/tsv/PSV_DEMOS.tsv 15 | nps_dlc = http://nopaystation.com/tsv/PSV_DLCS.tsv 16 | nps_theme = http://nopaystation.com/tsv/PSV_THEMES.tsv 17 | nps_psm = http://nopaystation.com/tsv/PSM_GAMES.tsv 18 | nps_psp = http://nopaystation.com/tsv/PSP_GAMES.tsv 19 | nps_xlsx = https://docs.google.com/spreadsheets/d/18HHxaQhGgqDjH2mIgC3T1OVWNRr_4HHfuLsYmiKXnfk/export?format=xlsx 20 | 21 | [csv_mapping] 22 | TITLE_ID = Title ID 23 | NAME = Name 24 | PKG_URL = PKG direct link 25 | ZRIF = zRIF 26 | 27 | [regions] 28 | PCSA = USA 29 | PCSB = EUR 30 | PCSC = JPN 31 | PCSD = ASN 32 | PCSE = USA 33 | PCSF = EUR 34 | PCSG = JPN 35 | PCSH = ASN 36 | NPNA = USA 37 | NPOA = EUR 38 | NPPA = JAP 39 | NPQA = ASN 40 | ;PCSI = DEV 41 | ;VCAS = ASN 42 | ;VCJS = JPN 43 | ;VLAS = ASN 44 | ;VLJM = JPN 45 | ;VLJS = JPN 46 | 47 | ; languages to be used when fetching content from the PSN store 48 | [languages] 49 | PCSA = en-us 50 | PCSB = en-ie 51 | PCSC = ja-jp 52 | PCSD = en-hk 53 | PCSE = en-us 54 | PCSF = en-ie 55 | PCSG = ja-jp 56 | PCSH = en-hk 57 | PCSI = en-us 58 | NPNA = en-us 59 | NPOA = en-ie 60 | NPPA = ja-jp 61 | NPQA = en-hk 62 | VCAS = en-hk 63 | VCJS = ja-jp 64 | VLAS = en-hk 65 | VLJM = ja-jp 66 | VLJS = ja-jp 67 | 68 | [limits] 69 | ; Single region to process, or ALL to for all regions 70 | region = ALL 71 | ; Range of IDs to process for each region 72 | range = 1,1300 73 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Sfo.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * VitaDB - Vita DataBase Updater © 2017 VitaSmith 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | */ 9 | 10 | using System; 11 | 12 | using static VitaDB.Utilities; 13 | 14 | namespace VitaDB 15 | { 16 | public class Sfo 17 | { 18 | public string Category; 19 | public UInt32? AppVer; 20 | public UInt32? SysVer; 21 | public UInt32? CDate; 22 | 23 | static readonly byte[] sfo_magic = { 0x00, (byte)'P', (byte)'S', (byte)'F' }; 24 | 25 | /// 26 | /// Generic method to get the value associated with a key from SFO data. 27 | /// 28 | /// The type of the value to be returned. 29 | /// The SFO data. 30 | /// The key. 31 | /// The value associated with they key. 32 | public static T GetValue(byte[] sfo, string key) 33 | { 34 | UInt32 keys = GetLe32(sfo, 0x08); 35 | UInt32 values = GetLe32(sfo, 0x0c); 36 | UInt32 count = GetLe32(sfo, 0x10); 37 | UInt32 value_length = 0; 38 | UInt16 value_type = 0; 39 | 40 | int i; 41 | for (i = 0; i < count; i++) 42 | { 43 | if (MemCpyStr(sfo, (int)keys + GetLe16(sfo, i * 16 + 20)) == key) 44 | { 45 | value_type = GetLe16(sfo, 0x14 + (i * 0x10) + 2); 46 | value_length = GetLe32(sfo, 0x14 + (i * 0x10) + 4); 47 | break; 48 | } 49 | } 50 | if (i >= count) 51 | return default(T); 52 | 53 | int pos = (int)(values + GetLe32(sfo, i * 16 + 20 + 12)); 54 | if (typeof(T) == typeof(string)) 55 | { 56 | string value; 57 | switch (value_type) 58 | { 59 | case 0x0204: 60 | // NUL terminated string 61 | value = MemCpyStr(sfo, pos); 62 | if (value.Length != value_length - 1) 63 | Console.Error.WriteLine($"[WARNING] SFO: Unexpected length {value.Length}, while looking for NUL-terminated key '{key}'"); 64 | break; 65 | case 0x0004: 66 | // non-NUL terminated 67 | value = MemCpyStr(sfo, pos, (int)value_length); 68 | break; 69 | default: 70 | throw new ApplicationException($"SFO: Unexpected type 0x{value_type:X4}, while looking for string key '{key}'."); 71 | } 72 | return (T)Convert.ChangeType(value, typeof(T)); 73 | } 74 | else if (typeof(T) == typeof(UInt32)) 75 | { 76 | if (value_type != 0x0404) 77 | throw new ApplicationException($"SFO: Unexpected type 0x{value_type:X4}, while looking for integer key '{key}'."); 78 | if (value_length != 4) 79 | throw new ApplicationException($"SFO: Unexpected length {value_length}, while looking for for integer key '{key}'."); 80 | var value = GetLe32(sfo, pos); 81 | return (T)Convert.ChangeType(value, typeof(T)); 82 | } 83 | else 84 | { 85 | throw new ApplicationException($"SFO: Unhandled type '{typeof(T)}' requested for key {key}"); 86 | } 87 | } 88 | 89 | public override string ToString() 90 | { 91 | string str = "(Sfo Object)\n"; 92 | foreach (var attr in typeof(Sfo).GetFields()) 93 | str += ($"* {attr.Name} = '{this.GetType().GetField(attr.Name).GetValue(this)}'\n"); 94 | return str; 95 | } 96 | 97 | /// 98 | /// Constructs a new Sfo object, using the SFO data passed as parameter. 99 | /// 100 | /// The SFO data. 101 | public Sfo(byte[] sfo) 102 | { 103 | if (!MemCmp(sfo, sfo_magic, 0)) 104 | { 105 | Console.Error.WriteLine("[ERROR] Not an SFO file"); 106 | return; 107 | } 108 | if (GetLe32(sfo, 4) != 0x00000101) 109 | { 110 | Console.Error.WriteLine("[ERROR] Only SFO version 1.1 is supported"); 111 | return; 112 | } 113 | Category = GetValue(sfo, "CATEGORY"); 114 | // Convert App and Sys versions to (major * 100 + minor) 115 | AppVer = GetVersionFromString(GetValue(sfo, "APP_VER")); 116 | if (AppVer == 0) 117 | AppVer = null; 118 | SysVer = GetVersionFromBcd(GetValue(sfo, "PSP2_SYSTEM_VER")); 119 | if (SysVer == 0) 120 | SysVer = null; 121 | string pubtoolinfo = GetValue(sfo, "PUBTOOLINFO"); 122 | if ((pubtoolinfo != null) && pubtoolinfo.Contains("c_date=")) 123 | { 124 | var date = pubtoolinfo.Substring(pubtoolinfo.IndexOf("c_date=") + "c_date=".Length, 8); 125 | CDate = UInt32.Parse(date); 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | # Custom 264 | *.csv 265 | *.jpg 266 | *.jpeg 267 | *.html 268 | *.htm 269 | *.txt 270 | *.db 271 | *.xml 272 | *.zip 273 | *.7z 274 | *.rar 275 | *.org 276 | *.old 277 | sqlite3* 278 | -------------------------------------------------------------------------------- /Settings.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * VitaDB - Vita DataBase Updater © 2017 VitaSmith 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | */ 9 | 10 | using Microsoft.Extensions.Configuration; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.IO; 14 | using System.Linq; 15 | using System.Reflection; 16 | 17 | namespace VitaDB 18 | { 19 | public sealed class Settings 20 | { 21 | private static Settings instance; 22 | static private IConfigurationRoot config = null; 23 | 24 | public string application_name = Assembly.GetEntryAssembly().GetName().Name; 25 | public string database_name = Assembly.GetEntryAssembly().GetName().Name + ".db"; 26 | public string local_sql = Assembly.GetEntryAssembly().GetName().Name + ".sql"; 27 | public string remote_sql = "https://raw.githubusercontent.com/VitaSmith/VitaDB/master/VitaDB.sql"; 28 | public string local_cache = "PkgCache.json"; 29 | public string remote_cache = "https://raw.githubusercontent.com/VitaSmith/VitaDB/master/PkgCache.json"; 30 | public static readonly string[] nps_type = new string[] { "App", "Demo", "DLC", "Theme", "PSM" }; 31 | public static readonly int[] nps_category = { 1, 3, 101, 201, 601 }; 32 | public string[] nps_url = new string[nps_type.Length]; 33 | public int[] range = { 1, 1300 }; 34 | public string csv_separator = " "; 35 | public bool csv_force_recheck = false; 36 | 37 | public List regions = null; 38 | public List regions_to_check = null; 39 | public Dictionary languages = new Dictionary(); 40 | public Dictionary csv_mapping = new Dictionary(); 41 | 42 | public static Settings Instance 43 | { 44 | get 45 | { 46 | if (instance == null) 47 | instance = new Settings(); 48 | return instance; 49 | } 50 | } 51 | 52 | private static void AssignFromConfigString(ref string member, string value) 53 | { 54 | try 55 | { 56 | if (config[value] != null) 57 | member = config[value]; 58 | } 59 | catch (Exception) { } 60 | } 61 | 62 | private Settings() 63 | { 64 | try 65 | { 66 | config = new ConfigurationBuilder() 67 | .SetBasePath(Directory.GetCurrentDirectory()) 68 | .AddIniFile(application_name + ".ini", false, true) 69 | .Build(); 70 | } 71 | catch (System.IO.FileNotFoundException) 72 | { 73 | Console.WriteLine($"INI file '{application_name}.ini' was not found"); 74 | return; 75 | } 76 | catch (Exception) { }; 77 | if (config == null) 78 | return; 79 | 80 | AssignFromConfigString(ref database_name, "db:name"); 81 | AssignFromConfigString(ref local_sql, "db:local_sql"); 82 | AssignFromConfigString(ref remote_sql, "db:remote_sql"); 83 | AssignFromConfigString(ref local_cache, "db:local_cache"); 84 | AssignFromConfigString(ref remote_cache, "db:remote_cache"); 85 | AssignFromConfigString(ref csv_separator, "csv:separator"); 86 | 87 | for (int i = 0; i < nps_type.Length; i++) 88 | AssignFromConfigString(ref nps_url[i], "csv:nps_" + nps_type[i].ToLower()); 89 | 90 | foreach (var property in typeof(App).GetProperties()) 91 | csv_mapping.Add(property.Name, property.Name); 92 | foreach (var mapping in config.GetSection("csv_mapping").GetChildren()) 93 | { 94 | if (!csv_mapping.Keys.Contains(mapping.Key)) 95 | Console.Error.WriteLine($"[WARNING] Ignoring csv_mapping '{mapping.Key}' as it is not a valid field name"); 96 | else if (!String.IsNullOrEmpty(mapping.Value)) 97 | csv_mapping[mapping.Key] = mapping.Value; 98 | } 99 | 100 | foreach (var region in config.GetSection("regions").GetChildren()) 101 | { 102 | if (regions == null) 103 | regions = new List(); 104 | regions.Add(region.Key); 105 | } 106 | // Set default region list if none were provided 107 | if (regions == null) 108 | regions = new List { "PCSA", "PCSB", "PCSC", "PCSD", "PCSE", "PCSF", "PSCG", "PCSH" }; 109 | 110 | foreach (var region in config.GetSection("languages").GetChildren()) 111 | { 112 | languages.Add(region.Key, region.Value); 113 | } 114 | 115 | try 116 | { 117 | var tmp_range = new int[2]; 118 | var range_list = config["limits:range"].Split(',').ToArray(); 119 | if (range_list.Length == 2) 120 | { 121 | tmp_range[0] = int.Parse(range_list[0]); 122 | tmp_range[1] = int.Parse(range_list[1]); 123 | if ((tmp_range[0] > 0) && (tmp_range[1] > 0) && (tmp_range[0] <= tmp_range[1])) 124 | range = tmp_range; 125 | else 126 | Console.WriteLine("Could not parse title_id range"); 127 | } 128 | } 129 | catch (Exception) { }; 130 | 131 | string r = "ALL"; 132 | try 133 | { 134 | r = config["limits:region"]; 135 | if (!regions.Contains(r)) 136 | r = "ALL"; 137 | } 138 | catch (Exception) { }; 139 | regions_to_check = (r == "ALL") ? regions : new List { r }; 140 | } 141 | 142 | /// 143 | /// Convert a CONTENT_ID to a region. 144 | /// 145 | /// The content ID. 146 | /// A 3 letter region name. 147 | public string GetRegionName(string content_id) 148 | { 149 | if (content_id.Substring(20, 4) == "ASIA") 150 | return "ASN"; 151 | try 152 | { 153 | return config["regions:" + content_id.Substring(7, 4)]; 154 | } 155 | catch (Exception) 156 | { 157 | return "???"; 158 | }; 159 | } 160 | 161 | /// 162 | /// Return the language code associated with a region. 163 | /// 164 | /// A string starting with a Vita region code (e.g. "PCSG"). 165 | /// If the string is longer than 4 characters, only the first are used as the region code. 166 | /// A lowercase language code string (e.g. "ja-jp"). 167 | public string GetLanguage(string region) 168 | { 169 | if (region == null) 170 | return "en-us"; 171 | languages.TryGetValue((region.Length > 4) ? region.Substring(0, 4) : region, out string value); 172 | return value ?? "en-us"; 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Database.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * VitaDB - Vita DataBase Updater © 2017 VitaSmith 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | */ 9 | 10 | using Microsoft.EntityFrameworkCore; 11 | using Microsoft.EntityFrameworkCore.Infrastructure; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using Microsoft.Extensions.Logging; 14 | using System; 15 | using System.Collections.Generic; 16 | using System.IO; 17 | using System.Net; 18 | using System.Reflection; 19 | 20 | /* DB Schema: 21 | 22 | CREATE TABLE IF NOT EXISTS `Apps` ( 23 | `TITLE_ID` TEXT NOT NULL, 24 | `NAME` TEXT, 25 | `ALT_NAME` TEXT, 26 | `CONTENT_ID` TEXT NOT NULL UNIQUE, 27 | `PARENT_ID` TEXT, 28 | `CATEGORY` INTEGER, 29 | `PKG_ID` INTEGER UNIQUE, 30 | `ZRIF` TEXT, 31 | `COMMENTS` TEXT, 32 | `FLAGS` INTEGER NOT NULL DEFAULT 0, 33 | PRIMARY KEY(`CONTENT_ID`), 34 | FOREIGN KEY(`CATEGORY`) REFERENCES `Categories`(`VALUE`) 35 | FOREIGN KEY(`PKG_ID`) REFERENCES `Pkgs`(`ID`), 36 | ); 37 | 38 | CREATE TABLE IF NOT EXISTS `Pkgs` ( 39 | `ID` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, 40 | `URL` TEXT UNIQUE, 41 | `SIZE` INTEGER, 42 | `SHA1` TEXT UNIQUE, 43 | `CATEGORY` TEXT, 44 | `APP_VER` INTEGER, 45 | `SYS_VER` INTEGER, 46 | `C_DATE` INTEGER, 47 | `V_DATE` INTEGER, 48 | `COMMENTS` TEXT 49 | ); 50 | 51 | CREATE TABLE IF NOT EXISTS `Updates` ( 52 | `CONTENT_ID` TEXT NOT NULL, 53 | `VERSION` INTEGER NOT NULL, 54 | `TYPE` INTEGER NOT NULL, 55 | `PKG_ID` INTEGER NOT NULL UNIQUE, 56 | PRIMARY KEY(`PKG_ID`), 57 | FOREIGN KEY(`CONTENT_ID`) REFERENCES `Apps`(`CONTENT_ID`), 58 | FOREIGN KEY(`TYPE`) REFERENCES `Types`(`VALUE`) 59 | FOREIGN KEY(`PKG_ID`) REFERENCES `Pkgs`(`ID`), 60 | ); 61 | 62 | CREATE TABLE IF NOT EXISTS `Categories` ( 63 | `NAME` TEXT NOT NULL UNIQUE, 64 | `VALUE` INTEGER NOT NULL UNIQUE, 65 | PRIMARY KEY(`VALUE`) 66 | ); 67 | 68 | CREATE TABLE IF NOT EXISTS `Flags` ( 69 | `NAME` TEXT NOT NULL UNIQUE, 70 | `VALUE` INTEGER NOT NULL UNIQUE, 71 | PRIMARY KEY(`VALUE`) 72 | ); 73 | 74 | CREATE TABLE IF NOT EXISTS `Types` ( 75 | `NAME` TEXT NOT NULL UNIQUE, 76 | `VALUE` INTEGER NOT NULL UNIQUE, 77 | PRIMARY KEY(`VALUE`) 78 | ); 79 | 80 | */ 81 | 82 | namespace VitaDB 83 | { 84 | // Optional logging to file 85 | public class SQLLoggerProvider : ILoggerProvider 86 | { 87 | public ILogger CreateLogger(string cat_name) 88 | { 89 | File.Delete(Assembly.GetEntryAssembly().GetName().Name + ".log"); 90 | return new SQLLogger(); 91 | } 92 | 93 | public void Dispose() 94 | { } 95 | 96 | private class SQLLogger : ILogger 97 | { 98 | public bool IsEnabled(LogLevel log_level) 99 | { 100 | return true; 101 | } 102 | 103 | public void Log(LogLevel log_level, EventId event_id, TState state, Exception exception, Func formatter) 104 | { 105 | File.AppendAllText(Assembly.GetEntryAssembly().GetName().Name + ".log", formatter(state, exception)); 106 | //Debug.WriteLine(formatter(state, exception)); 107 | } 108 | 109 | public IDisposable BeginScope(TState state) 110 | { 111 | return null; 112 | } 113 | } 114 | } 115 | 116 | public class Database : DbContext 117 | { 118 | public DbSet Apps { get; set; } 119 | private DbSet Categories { get; set; } 120 | private DbSet Flags { get; set; } 121 | public DbSet Pkgs { get; set; } 122 | public DbSet Updates { get; set; } 123 | private DbSet Types { get; set; } 124 | // Shorthands for converting Categories and Flags into a value 125 | public readonly Dictionary Category; 126 | public readonly Dictionary Flag; 127 | public readonly Dictionary Type; 128 | 129 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 130 | { 131 | optionsBuilder.UseSqlite($"Data Source={Settings.Instance.database_name}"); 132 | optionsBuilder.EnableSensitiveDataLogging(); 133 | } 134 | 135 | /// 136 | /// Create a new database context. 137 | /// 138 | /// (Optional) Log Entity Framework data. 139 | public Database(bool initial_setup = false, bool logging = false) : base() 140 | { 141 | if (!initial_setup) 142 | { 143 | Category = new Dictionary(); 144 | foreach (var category in Categories) 145 | Category[category.NAME] = category.VALUE; 146 | Flag = new Dictionary(); 147 | foreach (var flag in Flags) 148 | Flag[flag.NAME] = (UInt16)flag.VALUE; 149 | Type = new Dictionary(); 150 | foreach (var type in Types) 151 | Type[type.NAME] = type.VALUE; 152 | } 153 | if (logging) 154 | { 155 | var sp = this.GetInfrastructure(); 156 | var lf = sp.GetService(); 157 | lf.AddProvider(new SQLLoggerProvider()); 158 | } 159 | } 160 | 161 | /// 162 | /// (Re)create the SQLite Database from an SQL dump. 163 | /// 164 | /// (Optional) If true, this will recreate the database even if it exists. 165 | /// True on success, false on error. 166 | public static bool CreateDB(string sql_file, bool recreate = false) 167 | { 168 | if (recreate) 169 | { 170 | try 171 | { 172 | File.Delete(Settings.Instance.database_name); 173 | } 174 | catch (System.IO.IOException e) 175 | { 176 | Console.Error.WriteLine("[ERROR] " + e.Message); 177 | return false; 178 | } 179 | } 180 | Console.WriteLine($"Creating database '{Settings.Instance.database_name}' from '{sql_file}'..."); 181 | var lines = File.ReadAllLines(sql_file); 182 | using (var db = new Database(true)) 183 | using (var connection = db.Database.GetDbConnection()) 184 | { 185 | connection.Open(); 186 | { 187 | using (var command = connection.CreateCommand()) 188 | { 189 | foreach (var line in lines) 190 | { 191 | command.CommandText += line; 192 | if (line.EndsWith(';')) 193 | { 194 | command.ExecuteNonQuery(); 195 | command.CommandText = ""; 196 | } 197 | } 198 | // Turn FOREIGN KEYS on, just in case... 199 | command.CommandText = "PRAGMA foreign_keys = ON;"; 200 | command.ExecuteNonQuery(); 201 | } 202 | } 203 | connection.Close(); 204 | } 205 | return true; 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /Rif.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * VitaDB - Vita DataBase Updater © 2017 VitaSmith 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | */ 9 | 10 | using ICSharpCode.SharpZipLib.Zip.Compression; 11 | using System; 12 | using System.Linq; 13 | 14 | using static VitaDB.Utilities; 15 | 16 | namespace VitaDB 17 | { 18 | class RIF 19 | { 20 | public static readonly UInt32 ZLIB_DICTIONARY_ID_ZRIF = 0x627d1d5d; 21 | public static readonly byte[] zrif_dict = { 22 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 33 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 35 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 48, 48, 48, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 48, 44 | 48, 48, 54, 48, 48, 48, 48, 55, 48, 48, 48, 48, 56, 0, 48, 48, 48, 48, 51, 48, 48, 48, 48, 52, 48, 48, 48, 48, 45 | 53, 48, 95, 48, 48, 45, 65, 68, 68, 67, 79, 78, 84, 48, 48, 48, 48, 50, 45, 80, 67, 83, 71, 48, 48, 48, 48, 46 | 48, 48, 48, 48, 48, 48, 49, 45, 80, 67, 83, 69, 48, 48, 48, 45, 80, 67, 83, 70, 48, 48, 48, 45, 80, 67, 83, 47 | 67, 48, 48, 48, 45, 80, 67, 83, 68, 48, 48, 48, 45, 80, 67, 83, 65, 48, 48, 48, 45, 80, 67, 83, 66, 48, 48, 48 | 48, 0, 1, 0, 1, 0, 1, 0, 2, 239, 205, 171, 137, 103, 69, 35, 1 49 | }; 50 | public static readonly byte[] rif_name_key = { 51 | 0x19, 0xDD, 0x4F, 0xB9, 0x89, 0x48, 0x2B, 0xD4, 52 | 0xCB, 0x9E, 0xC9, 0xC7, 0x9A, 0x2E, 0xFB, 0xD0 53 | }; 54 | 55 | // https://gist.github.com/TheOfficialFloW/30c90b95c35623dda8a367996b4c08b8 56 | /// 57 | /// Get the RIF name associated with a specific AID. 58 | /// 59 | /// The mode to use (0 = bounded, 1 = fixed). 60 | /// The user's 64-bit PSN Account ID. 61 | /// The RIF name. 62 | public static string GetRifName(UInt64 mode, UInt64 aid) 63 | { 64 | if (!BitConverter.IsLittleEndian) 65 | { 66 | // Values must be copied in little endian mode 67 | aid = SwapBytes(aid); 68 | mode = SwapBytes(mode); 69 | } 70 | byte[] data = new byte[0x10]; 71 | Buffer.BlockCopy(BitConverter.GetBytes(mode), 0, data, 0, 8); 72 | Buffer.BlockCopy(BitConverter.GetBytes(aid), 0, data, 8, 8); 73 | 74 | return BitConverter.ToString(AesEncrypt(data, rif_name_key), 0, 0x10) 75 | .Replace("-", "").ToLower() + ".rif"; 76 | } 77 | 78 | /// 79 | /// Get the AID from a RIF name. 80 | /// 81 | /// The RIF name. 82 | /// The 64-bit AID value (as per ux0's 'id.dat') 83 | public static UInt64 GetAidFromRifName(string rif_name) 84 | { 85 | byte[] data = AesDecrypt(HexStringToByteArray(rif_name.Split('.').First()), rif_name_key); 86 | if (!MemCmp(data, new byte[7], 1)) 87 | throw new ApplicationException($"Decoded RIF mode 0x'{GetLe64(data):X8}' does not match 0 or 1"); 88 | return GetLe64(data, 8); 89 | } 90 | 91 | /// 92 | /// Decode a zRIF encoded string to a RIF byte array. 93 | /// 94 | /// The zRIF string 95 | /// The RIF byte array, or null on error 96 | public static byte[] Decode(string zrif) 97 | { 98 | byte[] input, output = new byte[1024]; 99 | 100 | try 101 | { 102 | // Pad string if not a multiple of 4 103 | if (zrif.Length % 4 != 0) 104 | zrif += new string('=', 4 - (zrif.Length % 4)); 105 | input = Convert.FromBase64String(zrif); 106 | } 107 | catch (Exception e) when (e is System.FormatException || e is System.NullReferenceException) 108 | { 109 | Console.Error.WriteLine($"[ERROR] {e.Message}"); 110 | return null; 111 | } 112 | 113 | if (input.Length < 6) 114 | { 115 | Console.Error.WriteLine("[ERROR] zRIF length too short"); 116 | return null; 117 | } 118 | if (((input[0] << 8) + input[1]) % 31 != 0) 119 | { 120 | Console.Error.WriteLine("[ERROR] zRIF header is corrupted"); 121 | return null; 122 | } 123 | var inflater = new Inflater(); 124 | inflater.SetInput(input); 125 | inflater.Inflate(output); 126 | if (inflater.IsNeedingDictionary) 127 | inflater.SetDictionary(zrif_dict); 128 | switch(inflater.Inflate(output)) 129 | { 130 | case 1024: 131 | return output; 132 | case 512: 133 | return MemCpy(output, 0, 512); 134 | default: 135 | return null; 136 | } 137 | } 138 | 139 | /// 140 | /// Encode a RIF byte array to zRIF string. 141 | /// 142 | /// The zRIF string. 143 | /// The zRIF string or null on error. 144 | public static string Encode(byte[] rif) 145 | { 146 | byte[] output = new byte[128 + 2]; 147 | 148 | if ((rif.Length != 512) && (rif.Length != 1024)) 149 | { 150 | Console.Error.WriteLine("[ERROR] invalid RIF length"); 151 | return null; 152 | } 153 | var deflater = new Deflater(); 154 | deflater.SetDictionary(zrif_dict); 155 | deflater.SetInput(rif); 156 | deflater.SetLevel(Deflater.BEST_COMPRESSION); 157 | deflater.SetStrategy(DeflateStrategy.Default); 158 | deflater.Finish(); 159 | int size = deflater.Deflate(output, 0, output.Length - 2); 160 | if (!deflater.IsFinished) 161 | { 162 | Console.Error.WriteLine("[ERROR] Deflate error"); 163 | return null; 164 | } 165 | // Don't have that much control over Window size so our header needs to be adjusted. 166 | if ((output[0] == 0x78) && (output[1] == 0xF9)) 167 | { 168 | output[0] = 0x28; 169 | output[1] = 0xEE; 170 | } 171 | // Adjust the size to be a multiple of 3 so that we don't get padding 172 | return Convert.ToBase64String(output, 0, ((size + 2) / 3) * 3); 173 | } 174 | 175 | /// 176 | /// Retrieve the CONTENT_ID field from a zRIF encoded key. 177 | /// 178 | /// The 512-byte RIF byte array. 179 | /// The CONTENT_ID string or null on error. 180 | public static string GetContentId(string zrif) 181 | { 182 | if (zrif == null) 183 | return null; 184 | var rif = RIF.Decode(zrif); 185 | if (rif == null) 186 | return null; 187 | // PSM and regular RIFs have their CONTENT_ID at different offsets 188 | var content_id = MemCpy(rif, MemCmp(rif, new byte[8]) ? 0x50 : 0x10, 0x24); 189 | try 190 | { 191 | return System.Text.ASCIIEncoding.Default.GetString(content_id); 192 | } 193 | catch (Exception) 194 | { 195 | return null; 196 | } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | VitaDB - A Database for PS Vita content 2 | ======================================= 3 | 4 | Description 5 | ----------- 6 | 7 | This is a database of Vita content, that includes pretty much of all the games, applications, DLCs, updates, etc. that 8 | are currently publicly known, and that is provided in the easy-to-work-with and universally supported SQLite format, as 9 | well as a Windows/Linux/MacOS .NET Core 2.0 application that demonstrates how the database can be manipulated. 10 | 11 | ### Target Audience 12 | 13 | This project is intended for developers or web site creators, who may be interested in building applications that 14 | revolve around the provision of Vita content. Like a library (`.dll`, `.so`) it is not meant to be used directly by 15 | end-users. 16 | 17 | ### Purpose 18 | 19 | The availability of this database can, we hope, help with many new possibilities. 20 | 21 | One would be the recreation of one's legally owned content in a manner that is both a lot more convenient and much much 22 | faster than what can be achieved through Sony's very limited content management application as well as its annoyingly 23 | non-user friendly and __sloooooow__ proprietary memory card format. 24 | 25 | Another would be with the cataloguing and updating of public Vita content, using PSDLE data, through a web service. 26 | 27 | Yet another possibility would be cataloguing applications that can either run on the Vita itself or from a PC with 28 | access to the Vita memory card. 29 | 30 | All in all, we hope that this database can become the de-facto, up-to-date repository for any application that needs to 31 | process Vita content. 32 | 33 | ### About NoNpDrm and zRIF content 34 | 35 | You will __NOT__ find any zRIFs (licenses) in the data provided in this project because its goal is to keep track of 36 | official PS Vita content only. 37 | 38 | Still, for people who might want to back up their own content licenses using NoNpDrm, the database does provides a 39 | placeholder for zRIFs, and the application also demonstrates how zRIF data can be imported. But that's about it. 40 | 41 | Usage 42 | ----- 43 | 44 | You must have sorted out to run .NET Core 2.0 applications on your machine. If you don't know how, please google it. 45 | 46 | ``` 47 | Usage: VitaDB [OPTIONS] 48 | 49 | Options: 50 | -m, --maintenance perform database maintenance 51 | -i, --input=VALUE name of the input file or URL 52 | -o, --output=VALUE name of the output file 53 | -c, --csv import/export CSV (Content type is deduced from filename) 54 | -n, --nps import data from NoPayStation online spreadsheet 55 | --chihiro refresh db from Chihiro 56 | --psn refresh db from PSN 57 | --region internal region check 58 | -d, --dump dump database to SQL (requires sqlite3.exe) 59 | -p, --purge purge/create a new PKG cache dictionary 60 | -u, --url=VALUE update DB from PSN Store/Pkg URL(s) 61 | --version display version and exit 62 | -v increase verbosity 63 | -z, --zrif import/export zRIFs 64 | -w, --wait-for-key wait for keypress before exiting 65 | -h, --help show this message and exit 66 | ``` 67 | 68 | Examples: 69 | 70 | ``` 71 | dotnet VitaDB.dll -n 72 | dotnet VitaDB.dll -c -o apps.csv 73 | dotnet VitaDB.dll -c -o dlc.csv 74 | dotnet VitaDB.dll -u url_list.txt 75 | dotnet VitaDB.dll --psn 76 | dotnet VitaDB.dll -m 77 | ``` 78 | 79 | Once the database has been created/updated, you can use an SQLite local browser, such as the multiplatform one from http://sqlitebrowser.org/, 80 | to view the data. 81 | 82 | You can also edit the content of the `.ini` file according to your requirements. 83 | 84 | Database structure 85 | ------------------ 86 | 87 | ### Apps 88 | 89 | * `TITLE_ID` The 9 alphanumeric character identifier of a title. 90 | Note that the first 4 characters also identify the region (you can see the region table in the `ini` file). 91 | Because `TITLE_ID` is always contained in `CONTENT_ID`, this column is redundant, but provided for convenience. 92 | * `NAME` The __official__ name of the game, as per the Sony servers (including any typos and ® or ™, but trimmed 93 | of leading or trailing white spaces and LFs) 94 | * `ALT_NAME` A user defined alternate name. This can be used, for instance, for the English translation of a 95 | Japanese title. 96 | * `CONTENT_ID` __THE__ identifier of a published App/Game/Addon/Theme/Avatar. This is what Sony uses to __uniquely__ 97 | identify content, so we do too. As such, this is the table's _primary key_. As mentioned earlier, `CONTENT_ID` 98 | always contains the `TITLE_ID`. 99 | * `PARENT_ID` The `CONTENT_ID` of any parent application. For instance, `PARENT_ID` can be used with an App/Game to 100 | link to the `CONTENT_ID` of a bundle (e.g a PS3 or PS4 multiplatform purchase) or, for DLCs, to the `CONTENT_ID` 101 | of the App the DLC applies to. 102 | * `CATEGORY` An integer representing the application category. See the __Categories__ table below. 103 | * `PKG_ID` The optional `ID` of a PKG entry from the Pkgs table. This is basically used to access the official URL 104 | where one can download content from the Sony servers. 105 | * `ZRIF` A __PLACEHOLDER__ field for zRIFs. This is provided as a placeholder so that applications that use this DB 106 | can use this field to store your __legally owned__ licenses, in your personal copy of the DB. 107 | * `COMMENTS` A placeholder for comments. 108 | * `FLAGS` A set of binary flags, that are used to set specific fields to read-only, so that they don't get altered 109 | when importing data, or to indicate if an application is a free. See the __Flags__ table below for details. 110 | 111 | ### Pkgs 112 | 113 | * `ID` An auto incremented integer ID to uniquely identify each pkg link. This is done so that we don't have to 114 | use the very long URLs as primary keys. 115 | * `URL` A unique PKG URl. The URLs can indiscriminately be for games, apps, DLC, themes, patches, etc. 116 | * `SIZE` The size of the PKG as read from the servers. You should use a 64-bit integer to map this, as it can be more 117 | than 4 GB. 118 | * `SHA1` The hex representation of the Pkg SHA1 (last 20 bytes from end-0x20). For convenience, this is being stored 119 | as a string (that needs to be converted) rather than a byte array. 120 | * `CATEGORY` A short sequence of letters describing the pkg category. This data mostly comes from the SFO. 121 | * `APP_VER` The application version, represented as the decimal number major *100 + minor (e.g. `100` for `v1.0`) 122 | * `SYS_VER` The minimum required system version, represented as the decimal number major *100 + minor (e.g. `355` for `v3.55`) 123 | * `C_DATE` The date when the application was created, in `YYYYMMDD` format. This data comes from the SFO. 124 | * `V_DATE` The date when the PKG was last retrieved, in `YYYYMMDD` format. 125 | * `COMMENTS` A placeholder for comments. 126 | 127 | ### Updates 128 | 129 | * `CONTENT_ID` The application this update applies to. 130 | * `VERSION` The application version, represented as the decimal number major *100 + minor (e.g. `210` for `v2.10`) 131 | * `TYPE` The type of update. See the __Types__ table below. 132 | * `PKG_ID` The `ID` of a update PKG entry in the Pkgs table. This is the primary key. 133 | 134 | ## Categories 135 | 136 | This table contains the values used for `CATEGORY` used by the __Apps__ table. 137 | 138 | The values are organized so that Games/Apps should have a value that is < 100, DLC < 200, Themes < 300, etc. 139 | This is done so that searching for Games/Apps, DLC and so on, in the Apps table, can be carried out very easily. 140 | 141 | ## Flags 142 | 143 | This table contains the values used for `FLAGS` in the __Apps__ table. Each value is a power of 2 that, if 144 | found in the `FLAGS` column, indicates whether a specific Flag is active or not. The `_RO` values are for 145 | flags indicating whether a column should be read-only, whereas `FREE_APP` indicates applications that don't 146 | need a license to run. 147 | 148 | ### Types 149 | 150 | This is used by the __Updates__ table to denote the type of update. There are currently only 3 types: `cumulative`, 151 | `incremental` and `hybrid`, which are pretty much a direct mapping of the types reported by the Sony update XML data. 152 | 153 | FAQ 154 | --- 155 | 156 | ### Why store the SHA-1? 157 | 158 | Besides being useful to check for pkg corruption, this can also be done to detect any silent changes that Sony may 159 | apply to PKG data. For instance, it is not difficult to imagine that, should Sony decide they've had enough of 160 | Henkaku/Enso 3.60 users who download pkg data off their servers, they could relatively easily modify all or a choice 161 | set of pkg they serve with an `eboot` to force a silent system upgrade to 3.61+. 162 | By storing a copy of the PKG SHA-1, along with the date when that SHA-1 was retrieved, it becomes possible to detect 163 | this kind of "silent upgrade" scenario. 164 | 165 | ### Why is `CONTENT_ID` used as primary key, rather than `TITLE_ID`? 166 | 167 | That's because you can have both a DEMO and FULL game served from same TITLE_ID, with 168 | different PKG urls and CONTENT_ID. For instance: 169 | * 漢字 点つなぎセット (DEMO) = `JP9000-PCSC00038_00-PP2JP00000000001` 170 | * 漢字 点つなぎセット (FULL) = `JP9000-PCSC00038_00-KANJITEN00000001` 171 | 172 | ### What's the purpose of `PkgCache.json`? 173 | 174 | `PkgCache.json` is a locally cached version of a PKG URL to `CONTENT_ID` dictionary. 175 | This avoids having to perform a time consuming round trip to the Sony PKG servers every time we want to validate the 176 | `CONTENT_ID` of a PKG, which is a very frequent operation. The JSON data is simply the serialized version of internal 177 | dictionary we use. 178 | 179 | One may consider that this duplicates data that is already present in the database, but it's really the other way 180 | around: this is __external__ data that is needed to validate the consistency of the database, and that we duplicate 181 | locally to avoid having to query the PKG servers. 182 | 183 | Note that you can use the `-p` option if you want to recreate your own `PkgCache.json` instead of reusing the one from 184 | the gtihub project. 185 | 186 | ### Why isn't the SHA-1 column set to be UNIQUE? 187 | 188 | Because Sony has found nothing better than provide the exact same content (a 10 GB... patch?!?) for Phantasy Star Online 189 | under 2 different URLs: 190 | * `http://gs.ww.np.dl.playstation.net/ppkg/np/PCSG00141/PCSG00141_T175/7e215919b18a27eb/JP0177-PCSG00141_00-PHANTASYSTARONL2-A0500-V0100-b501028c7b01305693f0b5768dadf2ef71c553b6-PE.pkg` 191 | * `http://gs.ww.np.dl.playstation.net/ppkg/np/PCSG00141/PCSG00141_T176/4f153d40849e6856/JP0177-PCSG00141_00-PHANTASYSTARONL2-A0500-V0100-313ed2cf66018279e89ba59c44db9a7de1e4b287-PE.pkg` 192 | -------------------------------------------------------------------------------- /Update.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * VitaDB - Vita DataBase Updater © 2017 VitaSmith 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | */ 9 | 10 | using Microsoft.EntityFrameworkCore; 11 | using System; 12 | using System.ComponentModel.DataAnnotations; 13 | using System.ComponentModel.DataAnnotations.Schema; 14 | using System.Linq; 15 | using System.Net; 16 | using System.Security.Cryptography; 17 | using System.Text; 18 | using System.Xml; 19 | using System.Xml.Linq; 20 | using System.Xml.Serialization; 21 | 22 | using static VitaDB.Utilities; 23 | 24 | namespace VitaDB 25 | { 26 | // XML Elements for PCS######-ver.xml deserialization 27 | [Serializable, XmlType("titlepatch")] 28 | public class TitlePatch 29 | { 30 | [XmlAttribute(AttributeName = "status")] 31 | public string Status { get; set; } 32 | 33 | [XmlAttribute(AttributeName = "titleid")] 34 | public string TitleId { get; set; } 35 | 36 | [XmlElement(typeof(Tag), ElementName = "tag")] 37 | public Tag Tag { get; set; } 38 | 39 | [XmlAnyAttribute] 40 | public XmlAttribute[] AnyAttributes; 41 | 42 | [XmlAnyElement] 43 | public XmlElement[] AnyElements; 44 | } 45 | 46 | public class Tag 47 | { 48 | [XmlAttribute(AttributeName = "name")] 49 | public string Name { get; set; } 50 | 51 | [XmlAttribute(AttributeName = "signoff")] 52 | public bool Signoff { get; set; } 53 | 54 | [XmlElement(typeof(Package), ElementName = "package")] 55 | public Package[] Packages { get; set; } 56 | 57 | [XmlAnyAttribute] 58 | public XmlAttribute[] AnyAttributes; 59 | 60 | [XmlAnyElement] 61 | public XmlElement[] AnyElements; 62 | } 63 | 64 | public class Package 65 | { 66 | [XmlAttribute(AttributeName = "version")] 67 | public string Version { get; set; } 68 | 69 | [XmlAttribute(AttributeName = "type")] 70 | public string Type { get; set; } 71 | 72 | [XmlAttribute(AttributeName = "size")] 73 | public UInt64 Size { get; set; } 74 | 75 | [XmlAttribute(AttributeName = "sha1sum")] 76 | public string Sha1Sum { get; set; } 77 | 78 | [XmlAttribute(AttributeName = "url")] 79 | public string Url { get; set; } 80 | 81 | [XmlAttribute(AttributeName = "psp2_system_ver")] 82 | public UInt32 SysVer { get; set; } 83 | 84 | [XmlAttribute(AttributeName = "content_id")] 85 | public string ContentId { get; set; } 86 | 87 | [XmlElement(ElementName = "paramsfo")] 88 | public ParamSfo Sfo { get; set; } 89 | 90 | [XmlElement(ElementName = "changeinfo")] 91 | public ChangeInfo Info { get; set; } 92 | 93 | [XmlElement(typeof(Package), ElementName = "hybrid_package")] 94 | public Package HybridPackage { get; set; } 95 | 96 | [XmlAnyAttribute] 97 | public XmlAttribute[] AnyAttributes; 98 | 99 | [XmlAnyElement] 100 | public XmlElement[] AnyElements; 101 | } 102 | 103 | public class ParamSfo 104 | { 105 | [XmlElement(ElementName = "title")] 106 | public string Title { get; set; } 107 | 108 | [XmlAnyAttribute] 109 | public XmlAttribute[] AnyAttributes; 110 | 111 | [XmlAnyElement] 112 | public XmlElement[] AnyElements; 113 | } 114 | 115 | public class ChangeInfo 116 | { 117 | [XmlAttribute(AttributeName = "url")] 118 | public string Url { get; set; } 119 | 120 | [XmlAnyAttribute] 121 | public XmlAttribute[] AnyAttributes; 122 | 123 | [XmlAnyElement] 124 | public XmlElement[] AnyElements; 125 | } 126 | 127 | public class Update 128 | { 129 | public string CONTENT_ID { get; set; } 130 | public UInt32 VERSION { get; set; } 131 | public int TYPE { get; set; } 132 | [Key] 133 | public UInt32 PKG_ID { get; set; } 134 | 135 | [NotMapped] 136 | public string URL { get; set; } 137 | [NotMapped] 138 | public UInt64 Size; 139 | [NotMapped] 140 | public byte[] Sha1Sum; 141 | [NotMapped] 142 | public UInt32 SysVer; 143 | 144 | // The SHA-256 key used to convert a TITLE_ID to an update hash 145 | private static readonly byte[] hmac_sha256_key = 146 | { 147 | 0xE5, 0xE2, 0x78, 0xAA, 0x1E, 0xE3, 0x40, 0x82, 0xA0, 0x88, 0x27, 0x9C, 0x83, 0xF9, 0xBB, 0xC8, 148 | 0x06, 0x82, 0x1C, 0x52, 0xF2, 0xAB, 0x5D, 0x2B, 0x4A, 0xBD, 0x99, 0x54, 0x50, 0x35, 0x51, 0x14 149 | }; 150 | private static HMACSHA256 hmac = new HMACSHA256(hmac_sha256_key); 151 | private static readonly string base_url = "https://gs-sec.ww.np.dl.playstation.net/pl/np/"; 152 | 153 | /// 154 | /// Upsert an entry into the DB. 155 | /// 156 | /// The database context. 157 | /// (Optional) Detach the entry after upsert. 158 | public void Upsert(Database db, bool detach = false) 159 | { 160 | if (db.Updates.Any(x => x.PKG_ID == PKG_ID)) 161 | db.Entry(this).State = EntityState.Modified; 162 | else 163 | db.Updates.Add(this); 164 | db.SaveChanges(); 165 | if (detach) 166 | db.Entry(this).State = EntityState.Detached; 167 | } 168 | 169 | /// 170 | /// Fetch XML update data from the PSN update servers. 171 | /// 172 | /// The TITLE_ID to look for update data. 173 | /// An XML document with the update data, or null if no update is avaialble. 174 | private static XDocument GetUpdateData(string title_id) 175 | { 176 | byte[] hash = hmac.ComputeHash(new ASCIIEncoding().GetBytes("np_" + title_id)); 177 | var url = base_url + title_id + "/" + 178 | BitConverter.ToString(hash).ToLower().Replace("-", "") + "/" + title_id + "-ver.xml"; 179 | 180 | // Required to ignore Sony's self-signed certificate errors 181 | ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; 182 | try 183 | { 184 | return XDocument.Load(url); 185 | } 186 | catch (System.Net.WebException) 187 | { 188 | // URL does not exist 189 | return null; 190 | } 191 | catch (System.Xml.XmlException) 192 | { 193 | // XML is empty 194 | return null; 195 | } 196 | } 197 | 198 | /// 199 | /// Check the updates PSN servers and insert/update the relevant DB records. 200 | /// 201 | /// The database context. 202 | /// The TITLE_ID to search updates for. 203 | public static void Check(Database db, string title_id) 204 | { 205 | var document = Update.GetUpdateData(title_id); 206 | if (document == null) 207 | return; 208 | 209 | TitlePatch patch = DeserializeFromXML(document); 210 | if (patch.Tag != null) 211 | { 212 | foreach (var update_pkg in Nullable(patch.Tag.Packages)) 213 | { 214 | Update update = new Update 215 | { 216 | CONTENT_ID = update_pkg.ContentId, 217 | VERSION = GetVersionFromString(update_pkg.Version), 218 | TYPE = db.Type[update_pkg.Type ?? "cumulative"], 219 | URL = update_pkg.Url, 220 | Size = update_pkg.Size, 221 | Sha1Sum = HexStringToByteArray(update_pkg.Sha1Sum), 222 | SysVer = GetVersionFromBcd(update_pkg.SysVer), 223 | }; 224 | 225 | var app = db.Apps.Where(x => x.CONTENT_ID == update.CONTENT_ID).FirstOrDefault(); 226 | if (app == null) 227 | { 228 | app = new App 229 | { 230 | CONTENT_ID = update.CONTENT_ID, 231 | TITLE_ID = update.CONTENT_ID.Substring(7, 9), 232 | NAME = document.Descendants("title").FirstOrDefault().Value 233 | }; 234 | Console.WriteLine($"[NOTE] Adding new App {app.CONTENT_ID}: {app.NAME}"); 235 | db.Apps.Add(app); 236 | db.SaveChanges(); 237 | } 238 | 239 | var pkg = db.Pkgs.Where(x => x.URL == update.URL).FirstOrDefault(); 240 | if (pkg == null) 241 | { 242 | pkg = Pkg.CreatePkg(update.URL); 243 | if (pkg == null) 244 | continue; 245 | db.Pkgs.Add(pkg); 246 | db.SaveChanges(); 247 | } 248 | update.PKG_ID = pkg.ID; 249 | update.Upsert(db, true); 250 | 251 | if (update_pkg.HybridPackage != null) 252 | { 253 | // Hybrid packages are derived from parent 254 | update.CONTENT_ID = update_pkg.HybridPackage.ContentId; 255 | update.TYPE = db.Type["hybrid"]; 256 | update.URL = update_pkg.HybridPackage.Url; 257 | update.Size = update_pkg.HybridPackage.Size; 258 | update.Sha1Sum = HexStringToByteArray(update_pkg.HybridPackage.Sha1Sum); 259 | 260 | pkg = db.Pkgs.Where(x => x.URL == update.URL).FirstOrDefault(); 261 | if (pkg == null) 262 | { 263 | pkg = Pkg.CreatePkg(update.URL); 264 | db.Pkgs.Add(pkg); 265 | db.SaveChanges(); 266 | } 267 | update.PKG_ID = pkg.ID; 268 | update.Upsert(db, true); 269 | } 270 | } 271 | } 272 | } 273 | } 274 | 275 | // DB entries for Update.TYPE 276 | public class Type 277 | { 278 | public string NAME { get; set; } 279 | [Key] 280 | public int VALUE { get; set; } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /App.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * VitaDB - Vita DataBase Updater © 2017 VitaSmith 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | */ 9 | 10 | using Microsoft.EntityFrameworkCore; 11 | using System; 12 | using System.ComponentModel.DataAnnotations; 13 | using System.ComponentModel.DataAnnotations.Schema; 14 | using System.Linq; 15 | using System.Text.RegularExpressions; 16 | 17 | using static VitaDB.Utilities; 18 | 19 | namespace VitaDB 20 | { 21 | public class App 22 | { 23 | public string TITLE_ID { get; set; } 24 | public string NAME { get; set; } 25 | public string ALT_NAME { get; set; } 26 | [Key] 27 | public string CONTENT_ID { get; set; } 28 | public string PARENT_ID { get; set; } 29 | public int? CATEGORY { get; set; } 30 | public UInt32? PKG_ID { get; set; } 31 | public string ZRIF { get; set; } 32 | public string COMMENTS { get; set; } 33 | public UInt16 FLAGS { get; set; } 34 | 35 | [NotMapped] 36 | public string PKG_URL { get; set; } 37 | 38 | static private UInt32 count = 0; 39 | 40 | /// 41 | /// Set a flag or a set of flags. 42 | /// 43 | /// The database context. 44 | /// One or more names of flags to set. 45 | public void SetFlag(Database db, params string[] flag_names) 46 | { 47 | for (int i = 0; i < flag_names.Length; i++) 48 | this.FLAGS |= (UInt16)db.Flag[flag_names[i]]; 49 | } 50 | 51 | /// 52 | /// Set an attribute to read-only. 53 | /// 54 | /// The database context. 55 | /// The names of one or more attrinbutes to set to read-only. 56 | public void SetReadOnly(Database db, params string[] attr_names) 57 | { 58 | for (int i = 0; i < attr_names.Length; i++) 59 | this.FLAGS |= (UInt16)db.Flag[attr_names[i] + "_RO"]; 60 | } 61 | 62 | /// 63 | /// Add a new PARENT_ID entry. 64 | /// 65 | /// The ID to add. 66 | public void AddParent(string parent_id) 67 | { 68 | if (String.IsNullOrEmpty(parent_id)) 69 | return; 70 | if (String.IsNullOrEmpty(this.PARENT_ID)) 71 | this.PARENT_ID = parent_id; 72 | else if (!this.PARENT_ID.Contains(parent_id)) 73 | this.PARENT_ID += " " + parent_id; 74 | } 75 | 76 | /// 77 | /// Insert or update a new application entry into the Apps database. 78 | /// This method preserves attributes that have been flagged to read-only. 79 | /// Note: This method only saves changes to the databse every 100 records. 80 | /// 81 | /// The database context. 82 | public void Upsert(Database db) 83 | { 84 | var app = db.Apps.Find(CONTENT_ID); 85 | if (app == null) 86 | { 87 | db.Apps.Add(this); 88 | } 89 | else 90 | { 91 | var org_app = db.Apps 92 | .AsNoTracking() 93 | .FirstOrDefault(x => x.CONTENT_ID == this.CONTENT_ID); 94 | if (org_app == null) 95 | { 96 | // Changes need to be applied 97 | db.SaveChanges(); 98 | org_app = db.Apps 99 | .AsNoTracking() 100 | .FirstOrDefault(x => x.CONTENT_ID == this.CONTENT_ID); 101 | if (org_app == null) 102 | throw new ApplicationException("Tracked App found, but database changes were not applied."); 103 | } 104 | var entry = db.Entry(this); 105 | 106 | foreach (var attr in typeof(App).GetProperties()) 107 | { 108 | if ((attr.Name == nameof(PKG_URL)) || (attr.Name == nameof(CONTENT_ID))) 109 | continue; 110 | 111 | // Manually Check for values that have been modified 112 | var new_value = attr.GetValue(this, null); 113 | var org_value = attr.GetValue(org_app, null); 114 | if ((new_value == null) || new_value.Equals(org_value)) 115 | { 116 | entry.Property(attr.Name).IsModified = false; 117 | continue; 118 | } 119 | 120 | // Set modified attribute according to the read-only flags 121 | if (db.Flag.ContainsKey(attr.Name + "_RO")) 122 | { 123 | entry.Property(attr.Name).IsModified = 124 | ((org_app.FLAGS & db.Flag[attr.Name + "_RO"]) == 0); 125 | } 126 | else 127 | { 128 | entry.Property(attr.Name).IsModified = true; 129 | } 130 | } 131 | 132 | // Flags can only be added in this method, never removed 133 | if (this.FLAGS != org_app.FLAGS) 134 | { 135 | this.FLAGS |= org_app.FLAGS; 136 | entry.Property(nameof(FLAGS)).IsModified = true; 137 | } 138 | } 139 | if (++count % 100 == 0) 140 | db.SaveChanges(); 141 | } 142 | 143 | /// 144 | /// Validate that TITLE_ID matches the expected format. 145 | /// 146 | /// The TITLE_ID string. 147 | /// true if TITLE_ID is valid, false otherwise. 148 | public static bool ValidateTitleID(string title_id) 149 | { 150 | Regex regexp = new Regex(@"^[A-Z]{4}\d{5}$"); 151 | if (title_id == null) 152 | return false; 153 | return regexp.IsMatch(title_id); 154 | } 155 | 156 | /// 157 | /// Check if a CONTENT_ID is a Vita title. 158 | /// 159 | /// The CONTENT_ID. 160 | /// True if Vita CONTENT_ID, false otherwise. 161 | public static bool IsVitaContentID(string content_id) 162 | { 163 | if (!ValidateContentID(content_id)) 164 | return false; 165 | return (content_id[7] == 'P') || (content_id[7] == 'V'); 166 | } 167 | 168 | /// 169 | /// Check if a CONTENT_ID is a Bundle. 170 | /// 171 | /// The CONTENT_ID. 172 | /// True if bundle, false otherwise. 173 | public static bool IsBundleContentID(string content_id) 174 | { 175 | if (!ValidateContentID(content_id)) 176 | return false; 177 | return content_id.Substring(7, 4) == "CUSA"; 178 | } 179 | 180 | /// 181 | /// Validate that CONTENT_ID matches the expected format. 182 | /// 183 | /// The CONTENT_ID string. 184 | /// true if CONTENT_ID is valid, false otherwise. 185 | public static bool ValidateContentID(string content_id) 186 | { 187 | Regex regexp = new Regex(@"^[A-Z\?]{2}[\d\?]{4}-[A-Z]{4}\d{5}_[\d\?]{2}-[A-Z0-9_\?]{16}$"); 188 | if (content_id == null) 189 | return false; 190 | return regexp.IsMatch(content_id); 191 | } 192 | 193 | /// 194 | /// Update or add an App to the DB. 195 | /// 196 | /// The database context. 197 | /// The name the App. 198 | /// The content ID. 199 | /// The content ID of the parent. 200 | /// The category. 201 | /// (Optional) A comment string. 202 | static void AddApp(Database db, string name, string content_id, string parent_id, int? category, string comments = null) 203 | { 204 | if (parent_id == content_id) 205 | parent_id = null; 206 | var app = db.Apps.Find(content_id); 207 | if (app == null) 208 | { 209 | app = new App 210 | { 211 | NAME = name, 212 | TITLE_ID = content_id.Substring(7, 9), 213 | CONTENT_ID = content_id, 214 | PARENT_ID = parent_id, 215 | CATEGORY = category, 216 | COMMENTS = comments 217 | }; 218 | } 219 | else 220 | { 221 | app.NAME = name; 222 | app.TITLE_ID = content_id.Substring(7, 9); 223 | app.AddParent(parent_id); 224 | app.CATEGORY = category; 225 | app.COMMENTS = comments; 226 | } 227 | app.SetReadOnly(db, nameof(App.NAME), nameof(App.CATEGORY)); 228 | if (comments != null) 229 | app.SetReadOnly(db, nameof(App.COMMENTS)); 230 | app.Upsert(db); 231 | } 232 | 233 | /// 234 | /// Update an App entry by querying Chihiro. 235 | /// 236 | /// The database context. 237 | /// (Optional) The language settings to use when querying Chihiro. 238 | /// (Optional) If true, add to the COMMENTS field. 239 | public void UpdateFromChihiro(Database db, string lang = null, bool add_lang = false) 240 | { 241 | var data = Chihiro.GetData(CONTENT_ID, lang); 242 | if (data == null) 243 | return; 244 | 245 | // Process root data 246 | NAME = data.name; 247 | CATEGORY = db.Category[data.top_category]; 248 | if (IsVitaContentID(CONTENT_ID)) 249 | { 250 | Console.WriteLine($"{TITLE_ID}: {data.name}"); 251 | AddApp(db, NAME, CONTENT_ID, PARENT_ID, CATEGORY, add_lang ? lang : null); 252 | } 253 | 254 | // Process entitlements 255 | if (data.default_sku != null) 256 | { 257 | foreach (var ent in Nullable(data.default_sku.entitlements)) 258 | { 259 | if (!IsVitaContentID(ent.id) || (ent.id == CONTENT_ID)) 260 | continue; 261 | Console.WriteLine($"* {ent.id}: {ent.name}"); 262 | AddApp(db, ent.name, ent.id, CONTENT_ID, CATEGORY, add_lang ? lang : null); 263 | } 264 | } 265 | 266 | // Process links 267 | foreach (var link in Nullable(data.links)) 268 | { 269 | // May get mixed DLC content 270 | if (!IsVitaContentID(link.id)) 271 | continue; 272 | Console.WriteLine($"* {link.id}: {link.top_category}"); 273 | AddApp(db, link.name, link.id, CONTENT_ID, db.Category[link.top_category], add_lang ? lang : null); 274 | 275 | // Process sub-entitlements 276 | if (link.default_sku != null) 277 | { 278 | foreach (var ent in Nullable(link.default_sku.entitlements)) 279 | { 280 | if (!IsVitaContentID(ent.id) || (ent.id == link.id)) 281 | continue; 282 | Console.WriteLine($" * {ent.id}: {ent.name}"); 283 | AddApp(db, ent.name, ent.id, CONTENT_ID, db.Category[link.top_category], add_lang ? lang : null); 284 | } 285 | } 286 | } 287 | } 288 | } 289 | 290 | // DB entries for App.CATEGORY 291 | public class Category 292 | { 293 | public string NAME { get; set; } 294 | [Key] 295 | public int VALUE { get; set; } 296 | } 297 | 298 | // DB entries for App.FLAGS 299 | public class Flag 300 | { 301 | public string NAME { get; set; } 302 | [Key] 303 | public int VALUE { get; set; } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /Json.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * VitaDB - Vita DataBase Updater © 2017 VitaSmith 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | */ 9 | 10 | using Newtonsoft.Json; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Net; 14 | 15 | namespace VitaDB 16 | { 17 | // This is the JSON descriptor for the data we get from Chihiro 18 | public class Chihiro 19 | { 20 | public class SubCategory 21 | { 22 | public string name { get; set; } 23 | public int count { get; set; } 24 | public string key { get; set; } 25 | } 26 | 27 | public class Price 28 | { 29 | public string name { get; set; } 30 | public int count { get; set; } 31 | public string key { get; set; } 32 | } 33 | 34 | public class TopCategory 35 | { 36 | public string name { get; set; } 37 | public int count { get; set; } 38 | public string key { get; set; } 39 | } 40 | 41 | public class Relationship 42 | { 43 | public string name { get; set; } 44 | public int count { get; set; } 45 | public string key { get; set; } 46 | } 47 | 48 | public class Facets 49 | { 50 | public List sub_category { get; set; } 51 | public List price { get; set; } 52 | public List top_category { get; set; } 53 | public List relationship { get; set; } 54 | } 55 | 56 | public class Attributes 57 | { 58 | public Facets facets { get; set; } 59 | public List next { get; set; } 60 | } 61 | 62 | public class ContentDescriptor 63 | { 64 | public string description { get; set; } 65 | public string url { get; set; } 66 | public string name { get; set; } 67 | } 68 | 69 | public class ContentRating 70 | { 71 | public string description { get; set; } 72 | public string rating_system { get; set; } 73 | public string url { get; set; } 74 | } 75 | 76 | public class MediaProp 77 | { 78 | } 79 | 80 | public class Drm 81 | { 82 | public int drm_category_type { get; set; } 83 | public string id { get; set; } 84 | public int is_streamable { get; set; } 85 | public MediaProp media_prop { get; set; } 86 | public long size { get; set; } 87 | public int type { get; set; } 88 | } 89 | 90 | public class Entitlement 91 | { 92 | public string description { get; set; } 93 | public List drms { get; set; } 94 | public int duration { get; set; } 95 | public string durationOverrideTypeId { get; set; } 96 | public int exp_after_first_use { get; set; } 97 | public int feature_type_id { get; set; } 98 | public string id { get; set; } 99 | public int license_type { get; set; } 100 | public object metadata { get; set; } 101 | public string name { get; set; } 102 | public string packageType { get; set; } 103 | public List packages { get; set; } 104 | public bool preorder_placeholder_flag { get; set; } 105 | public int size { get; set; } 106 | public int subType { get; set; } 107 | public List subtitle_language_codes { get; set; } 108 | public int type { get; set; } 109 | public int use_count { get; set; } 110 | public List voice_language_codes { get; set; } 111 | } 112 | 113 | public class Sku 114 | { 115 | public bool amortizeFlag { get; set; } 116 | public bool bundleExclusiveFlag { get; set; } 117 | public bool chargeImmediatelyFlag { get; set; } 118 | public int charge_type_id { get; set; } 119 | public int credit_card_required_flag { get; set; } 120 | public bool defaultSku { get; set; } 121 | public string display_price { get; set; } 122 | public List eligibilities { get; set; } 123 | public List entitlements { get; set; } 124 | public string id { get; set; } 125 | public bool is_original { get; set; } 126 | public string name { get; set; } 127 | public List platforms { get; set; } 128 | public int price { get; set; } 129 | public List rewards { get; set; } 130 | public bool seasonPassExclusiveFlag { get; set; } 131 | public bool skuAvailabilityOverrideFlag { get; set; } 132 | public int sku_type { get; set; } 133 | public string type { get; set; } 134 | } 135 | 136 | public class GameContentTypesList 137 | { 138 | public string name { get; set; } 139 | public string key { get; set; } 140 | } 141 | 142 | public class Image 143 | { 144 | public int type { get; set; } 145 | public string url { get; set; } 146 | } 147 | 148 | public class Metadata 149 | { 150 | public List packageSubType { get; set; } 151 | } 152 | 153 | public class Link 154 | { 155 | public string bucket { get; set; } 156 | public List cloud_only_platform { get; set; } 157 | public string container_type { get; set; } 158 | public string content_type { get; set; } 159 | public Sku default_sku { get; set; } 160 | public string id { get; set; } 161 | public List images { get; set; } 162 | public string name { get; set; } 163 | public string parent_name { get; set; } 164 | public List playable_platform { get; set; } 165 | public string provider_name { get; set; } 166 | public DateTime release_date { get; set; } 167 | public bool restricted { get; set; } 168 | public int revision { get; set; } 169 | public string short_name { get; set; } 170 | public UInt64 timestamp { get; set; } 171 | public string top_category { get; set; } 172 | public string url { get; set; } 173 | public string sub_category { get; set; } 174 | } 175 | 176 | public class Screenshot 177 | { 178 | public string type { get; set; } 179 | public int typeId { get; set; } 180 | public string source { get; set; } 181 | public string url { get; set; } 182 | public int order { get; set; } 183 | } 184 | 185 | public class MediaList 186 | { 187 | public List screenshots { get; set; } 188 | } 189 | 190 | public class MediaLayout 191 | { 192 | public string type { get; set; } 193 | public int height { get; set; } 194 | public int width { get; set; } 195 | } 196 | 197 | public class CnRemotePlay 198 | { 199 | public string name { get; set; } 200 | public List values { get; set; } 201 | } 202 | 203 | public class CloudOnlyPlatform 204 | { 205 | public string name { get; set; } 206 | public List values { get; set; } 207 | } 208 | 209 | public class CnVrEnabled 210 | { 211 | public string name { get; set; } 212 | public List values { get; set; } 213 | } 214 | 215 | public class SecondaryClassification 216 | { 217 | public string name { get; set; } 218 | public List values { get; set; } 219 | } 220 | 221 | public class CnVrRequired 222 | { 223 | public string name { get; set; } 224 | public List values { get; set; } 225 | } 226 | 227 | public class GameGenre 228 | { 229 | public string name { get; set; } 230 | public List values { get; set; } 231 | } 232 | 233 | public class PlayablePlatform 234 | { 235 | public string name { get; set; } 236 | public List values { get; set; } 237 | } 238 | 239 | public class CnDualshockVibration 240 | { 241 | public string name { get; set; } 242 | public List values { get; set; } 243 | } 244 | 245 | public class TertiaryClassification 246 | { 247 | public string name { get; set; } 248 | public List values { get; set; } 249 | } 250 | 251 | public class Genre 252 | { 253 | public string name { get; set; } 254 | public List values { get; set; } 255 | } 256 | 257 | public class CnPsVrAimRequired 258 | { 259 | public string name { get; set; } 260 | public List values { get; set; } 261 | } 262 | 263 | public class CnCrossPlatformPSVita 264 | { 265 | public string name { get; set; } 266 | public List values { get; set; } 267 | } 268 | 269 | public class CnPsVrAimEnabled 270 | { 271 | public string name { get; set; } 272 | public List values { get; set; } 273 | } 274 | 275 | public class PrimaryClassification 276 | { 277 | public string name { get; set; } 278 | public List values { get; set; } 279 | } 280 | 281 | public class Country 282 | { 283 | public int agelimit { get; set; } 284 | public int uagelimit { get; set; } 285 | public string country { get; set; } 286 | } 287 | 288 | public class Url 289 | { 290 | public int type { get; set; } 291 | public string url { get; set; } 292 | } 293 | 294 | public class Material 295 | { 296 | public string anno { get; set; } 297 | public List countries { get; set; } 298 | public DateTime from { get; set; } 299 | public int id { get; set; } 300 | public List lang { get; set; } 301 | public DateTime lastm { get; set; } 302 | public DateTime until { get; set; } 303 | public List urls { get; set; } 304 | } 305 | 306 | public class Promomedia 307 | { 308 | public string anno { get; set; } 309 | public int id { get; set; } 310 | public string key { get; set; } 311 | public List materials { get; set; } 312 | public string multi { get; set; } 313 | public string rep { get; set; } 314 | public int type { get; set; } 315 | } 316 | 317 | public class Count 318 | { 319 | public int star { get; set; } 320 | public int count { get; set; } 321 | } 322 | 323 | public class StarRating 324 | { 325 | public string total { get; set; } 326 | public string score { get; set; } 327 | public List count { get; set; } 328 | } 329 | 330 | public class Data 331 | { 332 | public int age_limit { get; set; } 333 | public Attributes attributes { get; set; } 334 | public string bucket { get; set; } 335 | public List cloud_only_platform { get; set; } 336 | public string container_type { get; set; } 337 | public List content_descriptors { get; set; } 338 | public int content_origin { get; set; } 339 | public ContentRating content_rating { get; set; } 340 | public string content_type { get; set; } 341 | public Sku default_sku { get; set; } 342 | public bool dob_required { get; set; } 343 | public List gameContentTypesList { get; set; } 344 | public string game_contentType { get; set; } 345 | public string id { get; set; } 346 | public List images { get; set; } 347 | public List links { get; set; } 348 | public string long_desc { get; set; } 349 | public MediaList mediaList { get; set; } 350 | public List media_layouts { get; set; } 351 | public Metadata metadata { get; set; } 352 | public string name { get; set; } 353 | public int pageTypeId { get; set; } 354 | public List parent_links { get; set; } 355 | public List playable_platform { get; set; } 356 | public List promomedia { get; set; } 357 | public string provider_name { get; set; } 358 | public List relationships { get; set; } 359 | public DateTime release_date { get; set; } 360 | public bool restricted { get; set; } 361 | public int revision { get; set; } 362 | public string short_name { get; set; } 363 | public int size { get; set; } 364 | public List sku_links { get; set; } 365 | public List skus { get; set; } 366 | public string sort { get; set; } 367 | public StarRating star_rating { get; set; } 368 | public int start { get; set; } 369 | public UInt64 timestamp { get; set; } 370 | public string title_name { get; set; } 371 | public string top_category { get; set; } 372 | public int total_results { get; set; } 373 | } 374 | 375 | /// 376 | /// Fetch JSON data for a specific CONTENT_ID from Sony's Chihiro servers. 377 | /// 378 | /// The CONTENT_ID to look up. 379 | /// (Optional) A region string, to use with the Chihiro server. 380 | /// If this parameter is not provided, the language to use is deduced from content_id 381 | /// (Optional) If true, content_id will not be validated. 382 | /// A Chihiro.Data JSON object. 383 | public static Data GetData(string content_id, string region = null, bool no_validation = false) 384 | { 385 | if (!no_validation && !App.ValidateContentID(content_id)) 386 | { 387 | Console.Error.WriteLine($"[ERROR] '{content_id}' is not valid"); 388 | return null; 389 | } 390 | if (String.IsNullOrEmpty(region)) 391 | { 392 | // Asian content is a mess... 393 | if (content_id.Substring(20).Contains("ASIA")) 394 | region = "en-hk"; 395 | else 396 | switch (content_id[0]) 397 | { 398 | case 'E': 399 | region = "en-ie"; 400 | break; 401 | case 'H': 402 | region = "en-hk"; 403 | break; 404 | case 'J': 405 | region = "ja-jp"; 406 | break; 407 | default: 408 | region = "en-us"; 409 | break; 410 | } 411 | } 412 | string chihiro_region = region.Substring(3, 2).ToUpper() + "/" + region.Substring(0, 2); 413 | var url = "https://store.playstation.com/store/api/chihiro/00_09_000/container/" + 414 | chihiro_region + "/999/" + content_id + "?sort=name&direction=asc&size=10000"; 415 | using (WebClient wc = new WebClient()) 416 | { 417 | try 418 | { 419 | var json = wc.DownloadString(url); 420 | return String.IsNullOrEmpty(json) ? null : JsonConvert.DeserializeObject(json); 421 | } 422 | catch (System.Net.WebException) { } 423 | } 424 | return null; 425 | } 426 | } 427 | } 428 | -------------------------------------------------------------------------------- /Utilities.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * VitaDB - Vita DataBase Updater © 2017 VitaSmith 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | */ 9 | 10 | using System; 11 | using System.Collections.Generic; 12 | using System.IO; 13 | using System.Linq; 14 | using System.Runtime.CompilerServices; 15 | using System.Security.Cryptography; 16 | using System.Text; 17 | using System.Xml.Linq; 18 | using System.Xml.Serialization; 19 | 20 | namespace VitaDB 21 | { 22 | static class Utilities 23 | { 24 | public static UInt16 SwapBytes(UInt16 x) 25 | { 26 | return (UInt16)((UInt16)((x & 0xff) << 8) | ((x >> 8) & 0xff)); 27 | } 28 | 29 | public static UInt32 SwapBytes(UInt32 x) 30 | { 31 | return ((x & 0x000000ff) << 24) + 32 | ((x & 0x0000ff00) << 8) + 33 | ((x & 0x00ff0000) >> 8) + 34 | ((x & 0xff000000) >> 24); 35 | } 36 | 37 | public static UInt64 SwapBytes(UInt64 value) 38 | { 39 | return ((0x00000000000000FF) & (value >> 56) 40 | | (0x000000000000FF00) & (value >> 40) 41 | | (0x0000000000FF0000) & (value >> 24) 42 | | (0x00000000FF000000) & (value >> 8) 43 | | (0x000000FF00000000) & (value << 8) 44 | | (0x0000FF0000000000) & (value << 24) 45 | | (0x00FF000000000000) & (value << 40) 46 | | (0xFF00000000000000) & (value << 56)); 47 | } 48 | 49 | public static UInt64 GetBe64(byte[] array, int pos = 0) 50 | { 51 | if (BitConverter.IsLittleEndian) 52 | return SwapBytes(BitConverter.ToUInt64(array, pos)); 53 | else 54 | return BitConverter.ToUInt64(array, pos); 55 | } 56 | 57 | public static UInt32 GetBe32(byte[] array, int pos = 0) 58 | { 59 | if (BitConverter.IsLittleEndian) 60 | return SwapBytes(BitConverter.ToUInt32(array, pos)); 61 | else 62 | return BitConverter.ToUInt32(array, pos); 63 | } 64 | 65 | public static UInt16 GetBe16(byte[] array, int pos = 0) 66 | { 67 | if (BitConverter.IsLittleEndian) 68 | return SwapBytes(BitConverter.ToUInt16(array, pos)); 69 | else 70 | return BitConverter.ToUInt16(array, pos); 71 | } 72 | 73 | public static UInt64 GetLe64(byte[] array, int pos = 0) 74 | { 75 | if (!BitConverter.IsLittleEndian) 76 | return SwapBytes(BitConverter.ToUInt64(array, pos)); 77 | else 78 | return BitConverter.ToUInt64(array, pos); 79 | } 80 | 81 | public static UInt32 GetLe32(byte[] array, int pos = 0) 82 | { 83 | if (!BitConverter.IsLittleEndian) 84 | return SwapBytes(BitConverter.ToUInt32(array, pos)); 85 | else 86 | return BitConverter.ToUInt32(array, pos); 87 | } 88 | 89 | public static UInt16 GetLe16(byte[] array, int pos = 0) 90 | { 91 | if (!BitConverter.IsLittleEndian) 92 | return SwapBytes(BitConverter.ToUInt16(array, pos)); 93 | else 94 | return BitConverter.ToUInt16(array, pos); 95 | } 96 | 97 | /// 98 | /// Returns a copy of a region of a byte array. 99 | /// 100 | /// The source byte array. 101 | /// The position at which to start the copy. 102 | /// The length to copy. 103 | /// A copy of the requested section, or null on error. 104 | public static byte[] MemCpy(byte[] array, int pos, int length) 105 | { 106 | if (pos + length > array.Length) 107 | return null; 108 | byte[] ret = new byte[length]; 109 | Buffer.BlockCopy(array, pos, ret, 0, length); 110 | return ret; 111 | } 112 | 113 | /// 114 | /// Returns a copy of a region of a byte array, as a string. 115 | /// 116 | /// The source byte array. 117 | /// The position at which to start the copy. 118 | /// (Optional) The number of character to copy for non NUL-terminated strings. 119 | /// If this parameter is omitted or zero, the string is assumed to be NUL-terminated. 120 | /// The string content. 121 | public static string MemCpyStr(byte[] array, int pos, int length = 0) 122 | { 123 | string str = ""; 124 | for (int count = 0; 125 | ((length <= 0) && (array[pos + count] != 0)) || ((length > 0) && (count < length)); 126 | count++) 127 | { 128 | str += (char)array[pos + count]; 129 | } 130 | return str; 131 | } 132 | 133 | /// 134 | /// Compares 2 byte arrays, starting at an optional offset. 135 | /// 136 | /// The array to perform the lookup on. 137 | /// The array containing the values to look for. 138 | /// (Optional) The position at which to start the lookup. 139 | /// true if a match is found, false otherwise. 140 | public static bool MemCmp(byte[] array, byte[] constant, int start = 0) 141 | { 142 | if ((array == null) || (constant == null)) 143 | return false; 144 | if (start + constant.Length >= array.Length) 145 | return false; 146 | for (int i = 0; i < constant.Length; i++) 147 | { 148 | if (array[start + i] != constant[i]) 149 | return false; 150 | } 151 | return true; 152 | } 153 | 154 | /// 155 | /// Convert a BCD encoded version to a base 10 integer one (major * 100 + minor). 156 | /// 157 | /// The BCD encoded version. 158 | /// The base 10 version. 159 | public static UInt32 GetVersionFromBcd(UInt32 version) 160 | { 161 | version /= 0x10000; 162 | version = (version / 0x1000 * 1000) 163 | + ((version & 0x0F00) / 0x100 * 100) 164 | + ((version & 0x00F0) / 0x10 * 10) 165 | + (version & 0x000F); 166 | return version; 167 | } 168 | 169 | /// 170 | /// Convert a string version to a base 10 integer one (major * 100 + minor). 171 | /// 172 | /// The version string. 173 | /// The base 10 version. 174 | public static UInt32 GetVersionFromString(string version) 175 | { 176 | if (version == null) 177 | return 0; 178 | if ((version.Length < 5) || (version[2] != '.')) 179 | { 180 | Console.Error.WriteLine($"[WARNING] Unexpected version format for '{version}'"); 181 | return 0; 182 | } 183 | return UInt32.Parse(version.Substring(0, 2)) * 100 + UInt32.Parse(version.Substring(3, 2)); 184 | } 185 | 186 | public static int HexVal(char hex) 187 | { 188 | int val = (int)hex; 189 | return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); 190 | } 191 | 192 | /// 193 | /// Convert an hex string to a byte array. 194 | /// 195 | /// The string to convert. 196 | /// The cmnverted byet array. 197 | public static byte[] HexStringToByteArray(string hex_string) 198 | { 199 | if (hex_string == null) 200 | return new byte[0]; 201 | 202 | if (hex_string.Length % 2 == 1) 203 | hex_string = "0" + hex_string; 204 | 205 | byte[] array = new byte[hex_string.Length >> 1]; 206 | for (int i = 0; i < hex_string.Length >> 1; ++i) 207 | array[i] = (byte)((HexVal(hex_string[i << 1]) << 4) + (HexVal(hex_string[(i << 1) + 1]))); 208 | return array; 209 | } 210 | 211 | /// 212 | /// Convert a byte array to an hex string. 213 | /// 214 | /// The byte array. 215 | /// The hex string. 216 | public static string ByteArrayToHexString(byte[] array) 217 | { 218 | if (Nullable(array).Length == 0) 219 | return null; 220 | return BitConverter.ToString(array).Replace("-", ""); 221 | } 222 | 223 | /// 224 | /// Hex dump a byte array as a string. 225 | /// 226 | /// The array to dump. 227 | /// The number of bytes that should be displayed per line. 228 | /// A string containing the hex dump. 229 | public static string HexDump(byte[] array, int bytes_per_line = 16) 230 | { 231 | if (array == null) return ""; 232 | int bytesLength = array.Length; 233 | 234 | char[] HexChars = "0123456789ABCDEF".ToCharArray(); 235 | 236 | int firstHexColumn = 8 + 3; 237 | 238 | int firstCharColumn = firstHexColumn + bytes_per_line * 3 + (bytes_per_line - 1) / 8 + 2; 239 | 240 | int lineLength = firstCharColumn + bytes_per_line + Environment.NewLine.Length; 241 | 242 | char[] line = (new String(' ', lineLength - Environment.NewLine.Length) + Environment.NewLine).ToCharArray(); 243 | int expectedLines = (bytesLength + bytes_per_line - 1) / bytes_per_line; 244 | StringBuilder result = new StringBuilder(expectedLines * lineLength); 245 | 246 | for (int i = 0; i < bytesLength; i += bytes_per_line) 247 | { 248 | line[0] = HexChars[(i >> 28) & 0xF]; 249 | line[1] = HexChars[(i >> 24) & 0xF]; 250 | line[2] = HexChars[(i >> 20) & 0xF]; 251 | line[3] = HexChars[(i >> 16) & 0xF]; 252 | line[4] = HexChars[(i >> 12) & 0xF]; 253 | line[5] = HexChars[(i >> 8) & 0xF]; 254 | line[6] = HexChars[(i >> 4) & 0xF]; 255 | line[7] = HexChars[(i >> 0) & 0xF]; 256 | 257 | int hexColumn = firstHexColumn; 258 | int charColumn = firstCharColumn; 259 | 260 | for (int j = 0; j < bytes_per_line; j++) 261 | { 262 | if (j > 0 && (j & 7) == 0) hexColumn++; 263 | if (i + j >= bytesLength) 264 | { 265 | line[hexColumn] = ' '; 266 | line[hexColumn + 1] = ' '; 267 | line[charColumn] = ' '; 268 | } 269 | else 270 | { 271 | byte b = array[i + j]; 272 | line[hexColumn] = HexChars[(b >> 4) & 0xF]; 273 | line[hexColumn + 1] = HexChars[b & 0xF]; 274 | line[charColumn] = (b < 32 ? '·' : (char)b); 275 | } 276 | hexColumn += 3; 277 | charColumn++; 278 | } 279 | result.Append(line); 280 | } 281 | return result.ToString(); 282 | } 283 | 284 | /// 285 | /// An alternative to Count() that returns 0 if the collection is null. 286 | /// 287 | /// The type of the collection to count elements on. 288 | /// The collection 289 | /// The number or elements. 290 | public static int MyCount(this IEnumerable collection) 291 | { 292 | return (collection == null) ? 0 : collection.Count(); 293 | } 294 | 295 | /// 296 | /// Deserialize an XML Document into an object instance. 297 | /// 298 | /// The type of the object to deserialize to. 299 | /// The XML document. 300 | /// An instance of the deserialized object. 301 | public static T DeserializeFromXML(XDocument xml_doc) 302 | { 303 | XmlSerializer xml_serializer = new XmlSerializer(typeof(T)); 304 | return (T)xml_serializer.Deserialize(xml_doc.CreateReader()); 305 | } 306 | 307 | /// 308 | /// Serialize an object instance to an XML Document. 309 | /// 310 | /// The type of the object to serialize from. 311 | /// The instance of the object to serialize. 312 | /// An XML document with the object data. 313 | public static XDocument SerializeToXML(T instance) 314 | { 315 | XmlSerializer xml_serializer = new XmlSerializer(typeof(T)); 316 | 317 | XDocument xml_doc = new XDocument(); 318 | using (var writer = xml_doc.CreateWriter()) 319 | { 320 | xml_serializer.Serialize(writer, instance); 321 | } 322 | 323 | return xml_doc; 324 | } 325 | 326 | /// 327 | /// Identity method for an array, that accepts null as parameters. 328 | /// 329 | /// The type of the array. 330 | /// The array. 331 | /// The source array or the empty array if the source array was null. 332 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 333 | public static T[] Nullable(T[] array) 334 | { 335 | return array ?? Enumerable.Empty().ToArray(); 336 | } 337 | 338 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 339 | public static List Nullable(List list) 340 | { 341 | return list ?? new List(); 342 | } 343 | 344 | /// 345 | /// Encrypt data through AES using a specific key and optional IV. 346 | /// 347 | /// The data to encrypt. 348 | /// The AES key to encrypt the data with. 349 | /// (Optional) The IV to use. If not provided EBC mode will be used. 350 | /// The encrypted data. 351 | public static byte[] AesEncrypt(byte[] data, byte[] key, byte[] iv = null) 352 | { 353 | using (MemoryStream mem_stream = new MemoryStream()) 354 | using (AesCryptoServiceProvider aes_provider = new AesCryptoServiceProvider()) 355 | { 356 | aes_provider.Padding = PaddingMode.Zeros; 357 | aes_provider.Mode = (iv == null) ? CipherMode.ECB : CipherMode.CBC; 358 | using (CryptoStream crypt_stream = new CryptoStream(mem_stream, 359 | aes_provider.CreateEncryptor(key, iv ?? new byte[key.Length]), 360 | CryptoStreamMode.Write)) 361 | { 362 | crypt_stream.Write(data, 0, data.Length); 363 | } 364 | return mem_stream.ToArray(); 365 | } 366 | } 367 | 368 | /// 369 | /// Decrypt data through AES using a specific key and optional IV. 370 | /// 371 | /// The data to encrypt. 372 | /// The AES key to encrypt the data with. 373 | /// (Optional) The IV to use. If not provided EBC mode is used. 374 | /// The decrypted data. 375 | public static byte[] AesDecrypt(byte[] data, byte[] key, byte[] iv = null) 376 | { 377 | using (MemoryStream mem_stream = new MemoryStream()) 378 | using (AesCryptoServiceProvider aes_provider = new AesCryptoServiceProvider()) 379 | { 380 | aes_provider.Padding = PaddingMode.Zeros; 381 | aes_provider.Mode = (iv == null) ? CipherMode.ECB : CipherMode.CBC; 382 | using (CryptoStream crypt_stream = new CryptoStream(mem_stream, 383 | aes_provider.CreateDecryptor(key, iv ?? new byte[key.Length]), 384 | CryptoStreamMode.Write)) 385 | { 386 | crypt_stream.Write(data, 0, data.Length); 387 | } 388 | return mem_stream.ToArray(); 389 | } 390 | } 391 | 392 | /// 393 | /// Wait for a key to be pressed. 394 | /// 395 | public static void WaitForKey() 396 | { 397 | // Flush the input buffer 398 | while (Console.KeyAvailable) 399 | Console.ReadKey(true); 400 | Console.WriteLine(""); 401 | Console.WriteLine("Press any key to exit..."); 402 | Console.ReadKey(true); 403 | } 404 | } 405 | } 406 | -------------------------------------------------------------------------------- /Pkg.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * VitaDB - Vita DataBase Updater © 2017 VitaSmith 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | */ 9 | 10 | using Newtonsoft.Json; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.ComponentModel.DataAnnotations; 14 | using System.ComponentModel.DataAnnotations.Schema; 15 | using System.Diagnostics; 16 | using System.IO; 17 | using System.Linq; 18 | using System.Net; 19 | 20 | using static VitaDB.Utilities; 21 | 22 | namespace VitaDB 23 | { 24 | public class Pkg 25 | { 26 | [Key] 27 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 28 | public UInt32 ID { get; set; } 29 | public string URL { get; set; } 30 | public UInt64 SIZE { get; set; } 31 | public string SHA1 { get; set; } 32 | public string CATEGORY { get; set; } 33 | public UInt32? APP_VER { get; set; } 34 | public UInt32? SYS_VER { get; set; } 35 | public UInt32? C_DATE { get; set; } 36 | public UInt32? V_DATE { get; set; } 37 | public string COMMENTS { get; set; } 38 | 39 | [NotMapped] 40 | public string CONTENT_ID { get; set; } 41 | 42 | static readonly int PKG_HEADER_SIZE = 0xC0; 43 | static readonly int PKG_HEADER_EXT_SIZE = 0x40; 44 | static readonly byte[] pkg_magic = { 0x7f, (byte)'P', (byte)'K', (byte)'G' }; 45 | static readonly byte[] ext_magic = { 0x7f, (byte)'e', (byte)'x', (byte)'t' }; 46 | static readonly byte[] pkg_psp = { 0x07, 0xf2, 0xc6, 0x82, 0x90, 0xb5, 0x0d, 0x2c, 0x33, 0x81, 0x8d, 0x70, 0x9b, 0x60, 0xe6, 0x2b }; 47 | static readonly byte[] pkg_vita2 = { 0xe3, 0x1a, 0x70, 0xc9, 0xce, 0x1d, 0xd7, 0x2b, 0xf3, 0xc0, 0x62, 0x29, 0x63, 0xf2, 0xec, 0xcb }; 48 | static readonly byte[] pkg_vita3 = { 0x42, 0x3a, 0xca, 0x3a, 0x2b, 0xd5, 0x64, 0x9f, 0x96, 0x86, 0xab, 0xad, 0x6f, 0xd8, 0x80, 0x1f }; 49 | static readonly byte[] pkg_vita4 = { 0xaf, 0x07, 0xfd, 0x59, 0x65, 0x25, 0x27, 0xba, 0xf1, 0x33, 0x89, 0x66, 0x8b, 0x17, 0xd9, 0xea }; 50 | 51 | private static Dictionary pkg_cache_dict = null; 52 | // Converts between content type and category, for cases where we can't get it from SFO 53 | private static readonly Dictionary content_type = new Dictionary 54 | { 55 | { 0x06, "ps1" }, 56 | { 0x07, "psp" }, // Also PC Engine 57 | { 0x09, "th" }, 58 | { 0x0a, "wdg" }, 59 | { 0x0b, "lic" }, 60 | { 0x0c, "vsh" }, 61 | { 0x0d, "av" }, 62 | { 0x0e, "go" }, 63 | { 0x0f, "min" }, 64 | { 0x10, "neo" }, 65 | { 0x11, "vmc" }, 66 | { 0x12, "ps2" }, 67 | { 0x14, "psp" }, 68 | { 0x15, "gd" }, 69 | { 0x16, "ac" }, 70 | { 0x17, "la" }, 71 | { 0x18, "psm" }, 72 | { 0x1D, "psm" }, 73 | { 0x1f, "th" } 74 | }; 75 | // The only URLs we allow are the ones from known Sony servers 76 | private static readonly List pkg_start = new List 77 | { 78 | "http://zeus.dl.playstation.net/", 79 | "http://ares.dl.playstation.net/", 80 | "http://gs.ww.np.dl.playstation.net/", 81 | "http://psm-runtime.np.dl.playstation.net/" 82 | }; 83 | 84 | /// 85 | /// Create a new Pkg object from a PKG URL. 86 | /// 87 | /// The source URL. 88 | /// A new Pkg instance, or null on error. 89 | public static Pkg CreatePkg(string url) 90 | { 91 | int i; 92 | for (i = 0; i < pkg_start.Count; i++) 93 | if (url.StartsWith(pkg_start[i])) 94 | break; 95 | if (i >= pkg_start.Count) 96 | { 97 | Console.Error.WriteLine($"[ERROR] '{url}' does not match known PSN URLs"); 98 | return null; 99 | } 100 | // Trim extra data after pkg (such as "?country=...") 101 | url = url.Split('?').First(); 102 | if (!url.EndsWith(".pkg")) 103 | { 104 | Console.Error.WriteLine($"[ERROR] '{url}' does not end with '.pkg'"); 105 | return null; 106 | } 107 | 108 | byte[] pkg_header = GetPkgData(url, 0, (UInt32)(PKG_HEADER_SIZE + PKG_HEADER_EXT_SIZE)); 109 | if (pkg_header == null) 110 | { 111 | Console.Error.WriteLine($"[ERROR] Could not read PKG header from '{url}'"); 112 | return null; 113 | } 114 | if (!MemCmp(pkg_header, pkg_magic, 0)) 115 | { 116 | Console.WriteLine("[ERROR] '{url}' is not a PKG file"); 117 | return null; 118 | } 119 | 120 | var pkg = new Pkg { 121 | URL = url, 122 | CONTENT_ID = GetContentIdFromPkg(url, pkg_header) 123 | }; 124 | if (!App.ValidateContentID(pkg.CONTENT_ID)) 125 | { 126 | Console.WriteLine("[ERROR] Could not get a valid CONTENT_ID from PKG URL '{url}'"); 127 | return null; 128 | } 129 | 130 | // http://www.psdevwiki.com/ps3/PKG_files 131 | UInt32 info_offset = GetBe32(pkg_header, 0x08); 132 | UInt32 info_count = GetBe32(pkg_header, 0x0c); 133 | UInt32 item_count = GetBe32(pkg_header, 0x14); 134 | pkg.SIZE = GetBe64(pkg_header, 0x18); 135 | UInt64 data_offset = GetBe64(pkg_header, 0x20); 136 | byte[] iv = MemCpy(pkg_header, 0x70, 0x10); 137 | int key_type = pkg_header[0xe7] & 0x07; 138 | 139 | byte[] pkg_info = GetPkgData(url, info_offset, (UInt32)(data_offset - info_offset)); 140 | if (pkg_info != null) 141 | { 142 | byte[] sfo_data = null; 143 | UInt32 type, size; 144 | string default_category = null; 145 | for (int count = 0, pos = 0; (count < info_count) && (pos < pkg_info.Length); count++, pos += (int)size) 146 | { 147 | type = GetBe32(pkg_info, pos); 148 | size = GetBe32(pkg_info, pos + 4); 149 | pos += 8; 150 | if (type == 0x02) 151 | { 152 | content_type.TryGetValue(GetBe32(pkg_info, pos), out default_category); 153 | } 154 | else if (type == 0x0E) 155 | { 156 | UInt32 sfo_pos = GetBe32(pkg_info, pos); 157 | UInt32 sfo_size = GetBe32(pkg_info, pos + 4); 158 | // Avoid a server round trip, as the SFO should be part of pkg_info 159 | if ((sfo_pos >= info_offset) && (sfo_pos - info_offset + sfo_size <= pkg_info.Length)) 160 | sfo_data = MemCpy(pkg_info, (int)(sfo_pos - info_offset), (int)sfo_size); 161 | else 162 | sfo_data = GetPkgData(url, sfo_pos, sfo_size); 163 | break; 164 | } 165 | } 166 | if (sfo_data != null) 167 | { 168 | var sfo = new Sfo(sfo_data); 169 | pkg.APP_VER = sfo.AppVer; 170 | pkg.CATEGORY = sfo.Category ?? default_category; 171 | pkg.C_DATE = sfo.CDate; 172 | pkg.SYS_VER = sfo.SysVer; 173 | } 174 | else 175 | { 176 | pkg.CATEGORY = default_category; 177 | } 178 | } 179 | pkg.SHA1 = ByteArrayToHexString(GetPkgSha1(url)); 180 | pkg.V_DATE = ((UInt32)DateTime.Now.Year) * 10000 181 | + ((UInt32)DateTime.Now.Month) * 100 182 | + ((UInt32)DateTime.Now.Day); 183 | return pkg; 184 | } 185 | 186 | /// 187 | /// Convert a Pkg cateory to App category. 188 | /// 189 | /// The database context. 190 | /// The Pkg category string. 191 | /// The integer category or null. 192 | public static int? PkgCatToAppCat(Database db, string cat) 193 | { 194 | switch (cat) 195 | { 196 | case "gd": 197 | return db.Category["downloadable_game"]; 198 | case "gdc": 199 | return db.Category["application"]; 200 | case "ac": 201 | return db.Category["add_on"]; 202 | case "th": 203 | return db.Category["theme"]; 204 | case "psm": 205 | return db.Category["psm"]; 206 | default: 207 | return null; 208 | } 209 | } 210 | 211 | // Convenience method 212 | public override string ToString() 213 | { 214 | string str = "(Pkg Object)\n"; 215 | foreach (var attr in typeof(Pkg).GetProperties()) 216 | { 217 | if (attr.PropertyType == typeof(byte[])) 218 | str += $"* {attr.Name} =\n" 219 | + HexDump((byte[])this.GetType().GetProperty(attr.Name).GetValue(this)); 220 | else 221 | str += ($"* {attr.Name} = '{this.GetType().GetProperty(attr.Name).GetValue(this)}'\n"); 222 | } 223 | return str; 224 | } 225 | 226 | /// 227 | /// Create a new instance of a Pkg from update data. 228 | /// 229 | /// The database context. 230 | /// The Update data to create the Pkg from. 231 | /// (Optional) Force the Pkg to be recreated by processing the remote data. 232 | /// A new Pkg instance. 233 | public static Pkg GetPkgFromUpdate(Database db, Update update, bool recheck = false) 234 | { 235 | var pkg = db.Pkgs.Where(x => x.URL == update.URL).FirstOrDefault(); 236 | if (pkg == null) 237 | { 238 | pkg = CreatePkg(update.URL); 239 | if (pkg == null) 240 | return null; 241 | db.Add(pkg); 242 | // Needed so that ID becomes available for reference 243 | db.SaveChanges(); 244 | } 245 | else if (recheck) 246 | { 247 | db.Attach(pkg); 248 | pkg = CreatePkg(update.URL); 249 | if (pkg == null) 250 | return null; 251 | } 252 | // Now compare the update data with our package data 253 | if (db.Apps.Where(x => x.PKG_ID == pkg.ID).Select(x => x.CONTENT_ID).FirstOrDefault() != update.CONTENT_ID) 254 | Console.Error.WriteLine($"[WARNING] CONTENT_ID of pkg {pkg.ID} is different from update CONTENT_ID!"); 255 | if (!MemCmp(HexStringToByteArray(pkg.SHA1), update.Sha1Sum)) 256 | Console.Error.WriteLine($"[WARNING] SHA1 of pkg {pkg.ID} is different from update SHA1!"); 257 | if (pkg.SIZE != update.Size) 258 | Console.Error.WriteLine($"[WARNING] Size of pkg {pkg.ID} is different from update size!"); 259 | return pkg; 260 | } 261 | 262 | /// 263 | /// Flushes the PKG cache dictionary to file. 264 | /// 265 | public static void FlushPkgCache() 266 | { 267 | if (pkg_cache_dict != null) 268 | SerializePkgCache(Settings.Instance.local_cache); 269 | } 270 | 271 | /// 272 | /// Serializes the PKG URL -> CONTENT_ID Dictionary to file. 273 | /// 274 | /// The path of the JSON file to save to. 275 | private static void SerializePkgCache(string file_path) 276 | { 277 | JsonSerializer serializer = new JsonSerializer(); 278 | serializer.Formatting = Formatting.Indented; 279 | using (StreamWriter sw = new StreamWriter(file_path)) 280 | using (JsonWriter writer = new JsonTextWriter(sw)) 281 | { 282 | Debug.WriteLine($"Saving PKG cache to '{file_path}'..."); 283 | // Might as well sort our saved output... 284 | serializer.Serialize(writer, new SortedDictionary(pkg_cache_dict)); 285 | } 286 | } 287 | 288 | /// 289 | /// Deserializes the PKG URL -> CONTENT_ID Dictionary to file. 290 | /// 291 | /// The path of the JSON file to read the cache from. 292 | /// The PKG URL cache Dictionary or null on error. 293 | private static Dictionary DeserializePkgCache(string file_path) 294 | { 295 | if (!File.Exists(file_path)) 296 | return null; 297 | JsonSerializer serializer = new JsonSerializer(); 298 | try 299 | { 300 | using (StreamReader sr = new StreamReader(file_path)) 301 | using (JsonReader reader = new JsonTextReader(sr)) 302 | { 303 | Debug.WriteLine($"Reading PKG cache from '{file_path}'..."); 304 | return serializer.Deserialize>(reader); 305 | } 306 | } 307 | catch (Exception e) 308 | { 309 | Console.Error.WriteLine($"[WARNING] Could not restore PKG cache from '{file_path}': {e.Message}"); 310 | return null; 311 | } 312 | } 313 | 314 | /// 315 | /// Fetch data from a PKG. 316 | /// 317 | /// The PKG url. 318 | /// (Optional) The start position to read from. Default is 0. 319 | /// (Optional) The number of bytes to read. Default is 0x60. 320 | /// A byte array with the content the header, or null on error. 321 | private static byte[] GetPkgData(string url, UInt64 start = 0, UInt32 size = 0x60) 322 | { 323 | byte[] result = null; 324 | byte[] buf = new byte[65536]; 325 | ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; 326 | HttpWebRequest request; 327 | request = WebRequest.Create(url) as HttpWebRequest; 328 | request.AddRange((long)start, (long)start + size - 1); 329 | request.ReadWriteTimeout = 30000; 330 | 331 | try 332 | { 333 | using (WebResponse resp = request.GetResponse()) 334 | using (Stream resp_stream = resp.GetResponseStream()) 335 | using (MemoryStream ms = new MemoryStream()) 336 | { 337 | result = new byte[size]; 338 | int read; 339 | while ((read = resp_stream.Read(buf, 0, buf.Length)) > 0) 340 | { 341 | ms.Write(buf, 0, read); 342 | } 343 | result = ms.ToArray(); 344 | } 345 | if (result.Length < size) 346 | { 347 | Console.Error.WriteLine($"[ERROR] Only {result.Length} bytes of PKG header were read"); 348 | return null; 349 | } 350 | } 351 | catch (Exception e) 352 | { 353 | Console.Error.WriteLine($"[ERROR] {e.Message} - {url}"); 354 | return null; 355 | } 356 | return result; 357 | } 358 | 359 | /// 360 | /// Fetch the PKG SHA-1. 361 | /// 362 | /// The PKG url. 363 | /// The 20-byte SHA-1 value from the PKG, or null on error. 364 | private static byte[] GetPkgSha1(string url) 365 | { 366 | byte[] data = GetPkgData(url, 0x18, 8); 367 | if (data == null) 368 | return null; 369 | UInt64 length = GetBe64(data); 370 | return GetPkgData(url, length - 0x20, 0x14); 371 | } 372 | 373 | /// 374 | /// Fetch the CONTENT_ID from a PKG (with caching) 375 | /// 376 | /// The PKG url. 377 | /// (Optional) The header data if already cached locally. 378 | /// A 36 character string containing the CONTENT_ID, or null on error. 379 | public static string GetContentIdFromPkg(string url, byte[] header = null) 380 | { 381 | if (String.IsNullOrEmpty(url)) 382 | return null; 383 | // Trim unneeded query content 384 | url = url.Split('?').First(); 385 | 386 | // Try to get the value from our cache where possible 387 | if (pkg_cache_dict == null) 388 | pkg_cache_dict = DeserializePkgCache(Settings.Instance.local_cache); 389 | if (pkg_cache_dict == null) 390 | pkg_cache_dict = new Dictionary(); 391 | if (pkg_cache_dict.TryGetValue(url, out string value)) 392 | return value; 393 | 394 | byte[] content_id = null; 395 | if (header == null) 396 | content_id = GetPkgData(url, 0x30, 0x24); 397 | else 398 | content_id = MemCpy(header, 0x30, 0x24); 399 | if (content_id == null) 400 | return null; 401 | try 402 | { 403 | value = System.Text.Encoding.Default.GetString(content_id); 404 | pkg_cache_dict[url] = value; 405 | return value; 406 | } 407 | catch (Exception e) 408 | { 409 | Console.Error.WriteLine("[WARNING] Could not convert PKG header to CONTENT_ID: " + e); 410 | return null; 411 | } 412 | } 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------