mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Net project
This commit is contained in:
parent
7a5bd2347d
commit
b19db45c6c
6 changed files with 379 additions and 0 deletions
|
@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NandReader", "NandReader\Na
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NandReaderGui", "NandReaderGui\NandReaderGui.csproj", "{3CBD38B0-6575-4768-8E94-A8AF2D2C9F43}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Net", "Net\Net.csproj", "{3398A7B0-E962-4D3D-9B64-0D5F90E56F16}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -37,6 +39,11 @@ Global
|
|||
{9889C467-284F-4061-B4DB-EC94051C29C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3CBD38B0-6575-4768-8E94-A8AF2D2C9F43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3CBD38B0-6575-4768-8E94-A8AF2D2C9F43}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3CBD38B0-6575-4768-8E94-A8AF2D2C9F43}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3398A7B0-E962-4D3D-9B64-0D5F90E56F16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3398A7B0-E962-4D3D-9B64-0D5F90E56F16}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3398A7B0-E962-4D3D-9B64-0D5F90E56F16}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3398A7B0-E962-4D3D-9B64-0D5F90E56F16}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
130
Net/CliParser.cs
Normal file
130
Net/CliParser.cs
Normal file
|
@ -0,0 +1,130 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Net
|
||||
{
|
||||
internal static class CliParser
|
||||
{
|
||||
private static readonly CliOption[] CliOptions =
|
||||
{
|
||||
new CliOption("keyset", 'k', 1, (o, a) => o.Keyfile = a[0]),
|
||||
new CliOption("titlekeys", 1, (o, a) => o.TitleKeyFile = a[0]),
|
||||
new CliOption("consolekeys", 1, (o, a) => o.ConsoleKeyFile = 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("did", 1, (o, a) => o.DeviceId = ParseTitleId(a[0])),
|
||||
new CliOption("cert", 1, (o, a) => o.CertFile = a[0])
|
||||
};
|
||||
|
||||
public static Options Parse(string[] args)
|
||||
{
|
||||
var options = new Options();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
string arg;
|
||||
|
||||
if (args[i].Length == 2 && (args[i][0] == '-' || args[i][0] == '/'))
|
||||
{
|
||||
arg = args[i][1].ToString().ToLower();
|
||||
}
|
||||
else if (args[i].Length > 2 && args[i].Substring(0, 2) == "--")
|
||||
{
|
||||
arg = args[i].Substring(2).ToLower();
|
||||
}
|
||||
else
|
||||
{
|
||||
PrintWithUsage($"Unable to parse option {args[i]}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var option = CliOptions.FirstOrDefault(x => x.Long == arg || x.Short == arg);
|
||||
if (option == null)
|
||||
{
|
||||
PrintWithUsage($"Unknown option {args[i]}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (i + option.ArgsNeeded >= args.Length)
|
||||
{
|
||||
PrintWithUsage($"Need {option.ArgsNeeded} parameter{(option.ArgsNeeded == 1 ? "" : "s")} after {args[i]}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var optionArgs = new string[option.ArgsNeeded];
|
||||
Array.Copy(args, i + 1, optionArgs, 0, option.ArgsNeeded);
|
||||
|
||||
option.Assigner(options, optionArgs);
|
||||
i += option.ArgsNeeded;
|
||||
}
|
||||
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private static ulong ParseTitleId(string input)
|
||||
{
|
||||
if (input.Length != 16)
|
||||
{
|
||||
PrintWithUsage("Title ID must be 16 hex characters long");
|
||||
}
|
||||
|
||||
if (!ulong.TryParse(input, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var id))
|
||||
{
|
||||
PrintWithUsage("Could not parse title ID");
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private static int ParseVersion(string input)
|
||||
{
|
||||
if (!int.TryParse(input, out var version))
|
||||
{
|
||||
PrintWithUsage("Could not parse version");
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
internal static void PrintWithUsage(string toPrint)
|
||||
{
|
||||
Console.WriteLine(toPrint);
|
||||
Console.WriteLine(GetUsage());
|
||||
// PrintUsage();
|
||||
}
|
||||
|
||||
private static string GetUsage()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("Usage: Don't");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private class CliOption
|
||||
{
|
||||
public CliOption(string longName, char shortName, int argsNeeded, Action<Options, string[]> assigner)
|
||||
{
|
||||
Long = longName;
|
||||
Short = shortName.ToString();
|
||||
ArgsNeeded = argsNeeded;
|
||||
Assigner = assigner;
|
||||
}
|
||||
public CliOption(string longName, int argsNeeded, Action<Options, string[]> assigner)
|
||||
{
|
||||
Long = longName;
|
||||
ArgsNeeded = argsNeeded;
|
||||
Assigner = assigner;
|
||||
}
|
||||
|
||||
public string Long { get; }
|
||||
public string Short { get; }
|
||||
public int ArgsNeeded { get; }
|
||||
public Action<Options, string[]> Assigner { get; }
|
||||
}
|
||||
}
|
||||
}
|
13
Net/Net.csproj
Normal file
13
Net/Net.csproj
Normal file
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>netcoreapp2.1;net45</TargetFrameworks>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\libhac\libhac.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
138
Net/NetContext.cs
Normal file
138
Net/NetContext.cs
Normal file
|
@ -0,0 +1,138 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using libhac;
|
||||
|
||||
namespace Net
|
||||
{
|
||||
internal class NetContext
|
||||
{
|
||||
private X509Certificate2 Certificate { get; set; }
|
||||
private string Eid { get; } = "lp1";
|
||||
private ulong Did { get; }
|
||||
private string Firmware { get; } = "5.1.0-3.0";
|
||||
private string CachePath { get; } = "titles";
|
||||
private Context ToolCtx { get; }
|
||||
|
||||
public NetContext(Context ctx)
|
||||
{
|
||||
ToolCtx = ctx;
|
||||
Did = ctx.Options.DeviceId;
|
||||
if (ctx.Options.CertFile != null)
|
||||
{
|
||||
SetCertificate(ctx.Options.CertFile);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCertificate(string filename)
|
||||
{
|
||||
Certificate = new X509Certificate2(filename, "switch");
|
||||
}
|
||||
|
||||
public Cnmt GetCnmt(ulong titleId, int version)
|
||||
{
|
||||
using (var stream = GetCnmtFile(titleId, version))
|
||||
{
|
||||
var nca = new Nca(ToolCtx.Keyset, stream, true);
|
||||
Stream sect = nca.OpenSection(0, false);
|
||||
var pfs0 = new Pfs0(sect);
|
||||
var file = pfs0.GetFile(0);
|
||||
|
||||
var cnmt = new Cnmt(new MemoryStream(file));
|
||||
return cnmt;
|
||||
}
|
||||
}
|
||||
|
||||
public Stream GetCnmtFile(ulong titleId, int version)
|
||||
{
|
||||
var cnmt = GetCnmtFileFromCache(titleId, version);
|
||||
if (cnmt != null) return cnmt;
|
||||
|
||||
if (Certificate == null) return null;
|
||||
|
||||
DownloadCnmt(titleId, version);
|
||||
return GetCnmtFileFromCache(titleId, version);
|
||||
}
|
||||
|
||||
public Stream GetCnmtFileFromCache(ulong titleId, int version)
|
||||
{
|
||||
string titleDir = GetTitleDir(titleId, version);
|
||||
var cnmtFiles = Directory.GetFiles(titleDir, "*.cnmt.nca").ToArray();
|
||||
|
||||
if (cnmtFiles.Length == 1)
|
||||
{
|
||||
return new FileStream(cnmtFiles[0], FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
}
|
||||
|
||||
if (cnmtFiles.Length > 1)
|
||||
{
|
||||
throw new FileNotFoundException($"More than cnmt file exists for {titleId:x16}v{version}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void DownloadCnmt(ulong titleId, int version)
|
||||
{
|
||||
var titleDir = GetTitleDir(titleId, version);
|
||||
|
||||
var ncaId = GetMetadataNcaId(titleId, version);
|
||||
var filename = $"{ncaId}.cnmt.nca";
|
||||
var filePath = Path.Combine(titleDir, filename);
|
||||
DownloadFile(GetContentUrl(ncaId), filePath);
|
||||
}
|
||||
|
||||
public void DownloadFile(string url, string filePath)
|
||||
{
|
||||
var response = Request("GET", url);
|
||||
using (var responseStream = response.GetResponseStream())
|
||||
using (var outStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
|
||||
{
|
||||
var dir = Path.GetDirectoryName(filePath) ?? throw new DirectoryNotFoundException();
|
||||
Directory.CreateDirectory(dir);
|
||||
responseStream.CopyStream(outStream, response.ContentLength, ToolCtx.Logger);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetTitleDir(ulong titleId, int version)
|
||||
{
|
||||
var titleDir = Path.Combine(CachePath, $"{titleId:x16}", $"{version}");
|
||||
Directory.CreateDirectory(titleDir);
|
||||
return titleDir;
|
||||
}
|
||||
|
||||
public string GetContentUrl(string ncaId)
|
||||
{
|
||||
string url = $"{GetAtumUrl()}/c/a/{ncaId}";
|
||||
return url;
|
||||
}
|
||||
|
||||
public string GetMetadataNcaId(ulong titleId, int version)
|
||||
{
|
||||
string url = $"{GetAtumUrl()}/t/a/{titleId:x16}/{version}?deviceid={Did}";
|
||||
|
||||
using (WebResponse response = Request("HEAD", url))
|
||||
{
|
||||
return response.Headers.Get("X-Nintendo-Content-ID");
|
||||
}
|
||||
}
|
||||
|
||||
private string GetAtumUrl()
|
||||
{
|
||||
return $"https://atum.hac.{Eid}.d4c.nintendo.net";
|
||||
}
|
||||
|
||||
public WebResponse Request(string method, string url)
|
||||
{
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||
request.ClientCertificates.Add(Certificate);
|
||||
request.UserAgent = string.Format("NintendoSDK Firmware/{0} (platform:NX; did:{1}; eid:{2})", Firmware, Did, Eid);
|
||||
request.Method = method;
|
||||
ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
|
||||
if (((HttpWebResponse)request.GetResponse()).StatusCode != HttpStatusCode.OK) { Console.WriteLine("http error"); return null; }
|
||||
return request.GetResponse();
|
||||
}
|
||||
}
|
||||
}
|
22
Net/Options.cs
Normal file
22
Net/Options.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using libhac;
|
||||
|
||||
namespace Net
|
||||
{
|
||||
internal class Options
|
||||
{
|
||||
public string Keyfile;
|
||||
public string TitleKeyFile;
|
||||
public string ConsoleKeyFile;
|
||||
public ulong TitleId;
|
||||
public int Version;
|
||||
public ulong DeviceId;
|
||||
public string CertFile;
|
||||
}
|
||||
|
||||
internal class Context
|
||||
{
|
||||
public Options Options;
|
||||
public Keyset Keyset;
|
||||
public IProgressReport Logger;
|
||||
}
|
||||
}
|
69
Net/Program.cs
Normal file
69
Net/Program.cs
Normal file
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using libhac;
|
||||
|
||||
namespace Net
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
var ctx = new Context();
|
||||
ctx.Options = CliParser.Parse(args);
|
||||
if (ctx.Options == null) return;
|
||||
|
||||
using (var logger = new ProgressBar())
|
||||
{
|
||||
ctx.Logger = logger;
|
||||
OpenKeyset(ctx);
|
||||
ProcessNet(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessNet(Context ctx)
|
||||
{
|
||||
if (ctx.Options.DeviceId == 0)
|
||||
{
|
||||
CliParser.PrintWithUsage("A non-zero Device ID must be set.");
|
||||
return;
|
||||
}
|
||||
|
||||
var net = new NetContext(ctx);
|
||||
var cnmt = net.GetCnmt(ctx.Options.TitleId, ctx.Options.Version);
|
||||
foreach (var entry in cnmt.ContentEntries)
|
||||
{
|
||||
Console.WriteLine($"{entry.NcaId.ToHexString()} {entry.Type}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void OpenKeyset(Context ctx)
|
||||
{
|
||||
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var homeKeyFile = Path.Combine(home, ".switch", "prod.keys");
|
||||
var homeTitleKeyFile = Path.Combine(home, ".switch", "title.keys");
|
||||
var homeConsoleKeyFile = Path.Combine(home, ".switch", "console.keys");
|
||||
var keyFile = ctx.Options.Keyfile;
|
||||
var titleKeyFile = ctx.Options.TitleKeyFile;
|
||||
var consoleKeyFile = ctx.Options.ConsoleKeyFile;
|
||||
|
||||
if (keyFile == null && File.Exists(homeKeyFile))
|
||||
{
|
||||
keyFile = homeKeyFile;
|
||||
}
|
||||
|
||||
if (titleKeyFile == null && File.Exists(homeTitleKeyFile))
|
||||
{
|
||||
titleKeyFile = homeTitleKeyFile;
|
||||
}
|
||||
|
||||
if (consoleKeyFile == null && File.Exists(homeConsoleKeyFile))
|
||||
{
|
||||
consoleKeyFile = homeConsoleKeyFile;
|
||||
}
|
||||
|
||||
ctx.Keyset = ExternalKeys.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile, ctx.Logger);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue