mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Net database
This commit is contained in:
parent
d8543fff76
commit
6f693ee500
8 changed files with 260 additions and 17 deletions
|
@ -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
86
Net/Database.cs
Normal 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
32
Net/Json.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
9
Net/Util.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue