Net database

This commit is contained in:
Alex Barney 2018-07-31 14:14:18 -05:00
parent d8543fff76
commit 6f693ee500
8 changed files with 260 additions and 17 deletions

View file

@ -15,7 +15,9 @@ namespace Net
new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])), new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])),
new CliOption("version", 1, (o, a) => o.Version = ParseVersion(a[0])), new CliOption("version", 1, (o, a) => o.Version = ParseVersion(a[0])),
new CliOption("did", 1, (o, a) => o.DeviceId = ParseTitleId(a[0])), new CliOption("did", 1, (o, a) => o.DeviceId = ParseTitleId(a[0])),
new CliOption("cert", 1, (o, a) => o.CertFile = a[0]) new CliOption("cert", 1, (o, a) => o.CertFile = a[0]),
new CliOption("commoncert", 1, (o, a) => o.CommonCertFile = a[0]),
new CliOption("metadata", 0, (o, a) => o.GetMetadata = true)
}; };
public static Options Parse(string[] args) public static Options Parse(string[] args)

86
Net/Database.cs Normal file
View file

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using libhac;
using Newtonsoft.Json;
namespace Net
{
public class Database
{
public Dictionary<long, TitleMetadata> Titles { get; set; } = new Dictionary<long, TitleMetadata>();
public DateTime VersionListTime { get; set; }
public string Serialize()
{
return JsonConvert.SerializeObject(this);
}
public static Database Deserialize(string filename)
{
var text = File.ReadAllText(filename);
return JsonConvert.DeserializeObject<Database>(text);
}
public bool IsVersionListCurrent()
{
return VersionListTime.AddDays(1) > DateTime.UtcNow;
}
public void ImportVersionList(VersionList list)
{
foreach (var title in list.titles)
{
var mainId = long.Parse(title.id, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
long updateId = 0;
bool isUpdate = (mainId & 0x800) != 0;
if (isUpdate)
{
updateId = mainId;
mainId &= ~0x800;
}
if (!Titles.TryGetValue(mainId, out TitleMetadata titleDb))
{
titleDb = new TitleMetadata();
Titles.Add(mainId, titleDb);
}
titleDb.Id = mainId;
titleDb.UpdateId = updateId;
titleDb.MaxVersion = title.version;
int maxVersionShort = title.version >> 16;
for(int i = 0; i <= maxVersionShort; i++)
{
var version = i << 16;
if (!titleDb.Versions.TryGetValue(version, out TitleVersion versionDb))
{
versionDb = new TitleVersion {Version = version};
titleDb.Versions.Add(version, versionDb);
}
}
}
}
}
public class TitleMetadata
{
public long Id { get; set; }
public long UpdateId { get; set; }
public int MaxVersion { get; set; }
public Dictionary<int, TitleVersion> Versions { get; set; } = new Dictionary<int, TitleVersion>();
}
public class TitleVersion
{
public bool Exists { get; set; } = true;
public int Version { get; set; }
public Cnmt ContentMetadata { get; set; }
public Nacp Control { get; set; }
}
}

32
Net/Json.cs Normal file
View file

@ -0,0 +1,32 @@
// ReSharper disable InconsistentNaming
// ReSharper disable CollectionNeverUpdated.Global
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
namespace Net
{
public static class Json
{
public static VersionList ReadVersionList(string filename)
{
var text = File.ReadAllText(filename);
var versionList = JsonConvert.DeserializeObject<VersionList>(text);
return versionList;
}
}
public class VersionList
{
public List<VersionListTitle> titles { get; set; }
public int format_version { get; set; }
public long last_modified { get; set; }
}
public class VersionListTitle
{
public string id { get; set; }
public int version { get; set; }
public int required_version { get; set; }
}
}

View file

@ -6,6 +6,10 @@
<LangVersion>7.3</LangVersion> <LangVersion>7.3</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\libhac\libhac.csproj" /> <ProjectReference Include="..\libhac\libhac.csproj" />
</ItemGroup> </ItemGroup>

View file

@ -10,11 +10,15 @@ namespace Net
internal class NetContext internal class NetContext
{ {
private X509Certificate2 Certificate { get; set; } private X509Certificate2 Certificate { get; set; }
private X509Certificate2 CertificateCommon { get; set; }
private string Eid { get; } = "lp1"; private string Eid { get; } = "lp1";
private ulong Did { get; } private ulong Did { get; }
private string Firmware { get; } = "5.1.0-3.0"; private string Firmware { get; } = "5.1.0-3.0";
private string CachePath { get; } = "titles"; private string CachePath { get; } = "titles";
private Context ToolCtx { get; } private Context ToolCtx { get; }
public Database Db { get; }
private const string VersionUrl = "https://tagaya.hac.lp1.eshop.nintendo.net/tagaya/hac_versionlist";
public NetContext(Context ctx) public NetContext(Context ctx)
{ {
@ -24,6 +28,25 @@ namespace Net
{ {
SetCertificate(ctx.Options.CertFile); SetCertificate(ctx.Options.CertFile);
} }
if (ctx.Options.CommonCertFile != null)
{
CertificateCommon = new X509Certificate2(ctx.Options.CommonCertFile, "shop");
}
var databaseFile = Path.Combine(CachePath, "database.json");
if (!File.Exists(databaseFile))
{
File.WriteAllText(databaseFile, new Database().Serialize());
}
Db = Database.Deserialize(databaseFile);
}
public void Save()
{
var databaseFile = Path.Combine(CachePath, "database.json");
File.WriteAllText(databaseFile, Db.Serialize());
} }
public void SetCertificate(string filename) public void SetCertificate(string filename)
@ -35,6 +58,8 @@ namespace Net
{ {
using (var stream = GetCnmtFile(titleId, version)) using (var stream = GetCnmtFile(titleId, version))
{ {
if (stream == null) return null;
var nca = new Nca(ToolCtx.Keyset, stream, true); var nca = new Nca(ToolCtx.Keyset, stream, true);
Stream sect = nca.OpenSection(0, false); Stream sect = nca.OpenSection(0, false);
var pfs0 = new Pfs0(sect); var pfs0 = new Pfs0(sect);
@ -68,7 +93,7 @@ namespace Net
if (cnmtFiles.Length > 1) if (cnmtFiles.Length > 1)
{ {
throw new FileNotFoundException($"More than cnmt file exists for {titleId:x16}v{version}"); throw new FileNotFoundException($"More than 1 cnmt file exists for {titleId:x16}v{version}");
} }
return null; return null;
@ -77,7 +102,7 @@ namespace Net
public Nacp GetControl(ulong titleId, int version) public Nacp GetControl(ulong titleId, int version)
{ {
var cnmt = GetCnmt(titleId, version); var cnmt = GetCnmt(titleId, version);
var controlEntry = cnmt.ContentEntries.FirstOrDefault(x => x.Type == CnmtContentType.Control); var controlEntry = cnmt?.ContentEntries.FirstOrDefault(x => x.Type == CnmtContentType.Control);
if (controlEntry == null) return null; if (controlEntry == null) return null;
var controlNca = GetNcaFile(titleId, version, controlEntry.NcaId.ToHexString()); var controlNca = GetNcaFile(titleId, version, controlEntry.NcaId.ToHexString());
@ -109,6 +134,12 @@ namespace Net
var titleDir = GetTitleDir(titleId, version); var titleDir = GetTitleDir(titleId, version);
var ncaId = GetMetadataNcaId(titleId, version); var ncaId = GetMetadataNcaId(titleId, version);
if (ncaId == null)
{
Console.WriteLine($"Could not get {titleId:x16}v{version} metadata");
return;
}
var filename = $"{ncaId.ToLower()}.cnmt.nca"; var filename = $"{ncaId.ToLower()}.cnmt.nca";
var filePath = Path.Combine(titleDir, filename); var filePath = Path.Combine(titleDir, filename);
DownloadFile(GetMetaUrl(ncaId), filePath); DownloadFile(GetMetaUrl(ncaId), filePath);
@ -117,6 +148,7 @@ namespace Net
public void DownloadFile(string url, string filePath) public void DownloadFile(string url, string filePath)
{ {
var response = Request("GET", url); var response = Request("GET", url);
if (response == null) return;
using (var responseStream = response.GetResponseStream()) using (var responseStream = response.GetResponseStream())
using (var outStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite)) using (var outStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{ {
@ -151,10 +183,34 @@ namespace Net
using (WebResponse response = Request("HEAD", url)) using (WebResponse response = Request("HEAD", url))
{ {
return response.Headers.Get("X-Nintendo-Content-ID"); return response?.Headers.Get("X-Nintendo-Content-ID");
} }
} }
public VersionList GetVersionList()
{
var filename = Path.Combine(CachePath, "hac_versionlist");
VersionList list = null;
if (Db.IsVersionListCurrent() && File.Exists(filename))
{
return Json.ReadVersionList(filename);
}
DownloadVersionList();
if (File.Exists(filename))
{
list = Json.ReadVersionList(filename);
}
return list;
}
public void DownloadVersionList()
{
DownloadFile(VersionUrl, Path.Combine(CachePath, "hac_versionlist"));
Db.VersionListTime = DateTime.UtcNow;
}
private string GetAtumUrl() private string GetAtumUrl()
{ {
return $"https://atum.hac.{Eid}.d4c.nintendo.net"; return $"https://atum.hac.{Eid}.d4c.nintendo.net";
@ -164,11 +220,22 @@ namespace Net
{ {
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.ClientCertificates.Add(Certificate); request.ClientCertificates.Add(Certificate);
request.UserAgent = string.Format("NintendoSDK Firmware/{0} (platform:NX; did:{1}; eid:{2})", Firmware, Did, Eid); request.UserAgent = $"NintendoSDK Firmware/{Firmware} (platform:NX; did:{Did}; eid:{Eid})";
request.Method = method; request.Method = method;
ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true); ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
if (((HttpWebResponse)request.GetResponse()).StatusCode != HttpStatusCode.OK) { Console.WriteLine("http error"); return null; }
return request.GetResponse(); try
{
if (((HttpWebResponse)request.GetResponse()).StatusCode == HttpStatusCode.OK)
return request.GetResponse();
}
catch (WebException ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine("http error");
return null;
} }
} }
} }

View file

@ -11,6 +11,8 @@ namespace Net
public int Version; public int Version;
public ulong DeviceId; public ulong DeviceId;
public string CertFile; public string CertFile;
public string CommonCertFile;
public bool GetMetadata;
} }
internal class Context internal class Context

View file

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using libhac; using libhac;
@ -26,32 +27,73 @@ namespace Net
private static void ProcessNet(Context ctx) private static void ProcessNet(Context ctx)
{ {
if (ctx.Options.DeviceId == 0) if (ctx.Options.DeviceId == 0)
{ {
CliParser.PrintWithUsage("A non-zero Device ID must be set."); CliParser.PrintWithUsage("A non-zero Device ID must be set.");
return; return;
} }
if (ctx.Options.GetMetadata)
{
GetMetadata(new NetContext(ctx), ctx.Logger);
return;
}
if (ctx.Options.TitleId == 0)
{
CliParser.PrintWithUsage("A non-zero Title ID must be set.");
return;
}
var tid = ctx.Options.TitleId; var tid = ctx.Options.TitleId;
var ver = ctx.Options.Version; var ver = ctx.Options.Version;
var net = new NetContext(ctx); var net = new NetContext(ctx);
//GetControls(net);
var cnmt = net.GetCnmt(tid, ver); var cnmt = net.GetCnmt(tid, ver);
if (cnmt == null) return;
ctx.Logger.LogMessage($"Title is of type {cnmt.Type} and has {cnmt.ContentEntries.Length} content entries");
var control = net.GetControl(tid, ver);
if (control != null)
{
ctx.Logger.LogMessage($"Title has name {control.Languages[0].Title}");
}
foreach (var entry in cnmt.ContentEntries) foreach (var entry in cnmt.ContentEntries)
{ {
Console.WriteLine($"{entry.NcaId.ToHexString()} {entry.Type}"); ctx.Logger.LogMessage($"{entry.NcaId.ToHexString()} {entry.Type}");
net.GetNcaFile(tid, ver, entry.NcaId.ToHexString()); net.GetNcaFile(tid, ver, entry.NcaId.ToHexString());
} }
var control = net.GetControl(tid, ver);
;
} }
private static void GetControls(NetContext net) private static void GetMetadata(NetContext net, IProgressReport logger = null)
{ {
var versionList = net.GetVersionList();
net.Db.ImportVersionList(versionList);
net.Save();
foreach (var title in net.Db.Titles.Values)
{
foreach (var version in title.Versions.Values.Where(x => x.Exists))
{
var titleId = version.Version == 0 ? title.Id : title.UpdateId;
try
{
var control = net.GetControl((ulong)titleId, version.Version);
version.Control = control;
if (control == null) version.Exists = false;
logger?.LogMessage($"{titleId}v{version.Version}");
//Thread.Sleep(300);
}
catch (Exception ex)
{
Console.WriteLine($"Failed getting {titleId}v{version.Version}\n{ex.Message}");
}
}
// net.Save();
}
net.Save();
return;
var titles = GetTitleIds("titles.txt"); var titles = GetTitleIds("titles.txt");
foreach (var title in titles) foreach (var title in titles)
@ -108,4 +150,3 @@ namespace Net
} }
} }
} }

9
Net/Util.cs Normal file
View file

@ -0,0 +1,9 @@
using System;
namespace Net
{
public static class Util
{
public static long ToUnixTime(this DateTime inputTime) => (long)(inputTime - new DateTime(1970, 1, 1)).TotalSeconds;
}
}