LibHac/hactoolnet/Program.cs
2018-07-02 21:23:07 -05:00

321 lines
11 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using libhac;
namespace hactoolnet
{
public static class Program
{
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);
switch (ctx.Options.InFileType)
{
case FileType.Nca:
ProcessNca(ctx);
break;
case FileType.Pfs0:
break;
case FileType.Romfs:
break;
case FileType.Nax0:
break;
case FileType.SwitchFs:
ProcessSwitchFs(ctx);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
//ListSdfs(args);
//FileReadTest(args);
//ReadNca();
//ListSdfs(args);
//ReadNcaSdfs(args);
}
private static void ProcessNca(Context ctx)
{
using (var file = new FileStream(ctx.Options.InFile, FileMode.Open, FileAccess.Read))
{
var nca = new Nca(ctx.Keyset, file, false);
for (int i = 0; i < 3; i++)
{
if (ctx.Options.SectionOut[i] != null)
{
nca.ExportSection(i, ctx.Options.SectionOut[i], ctx.Options.Raw, ctx.Logger);
}
if (ctx.Options.SectionOutDir[i] != null)
{
nca.ExtractSection(i, ctx.Options.SectionOutDir[i], ctx.Logger);
}
}
if (ctx.Options.ListRomFs && nca.Sections[1] != null)
{
var romfs = new Romfs(nca.OpenSection(1, false));
foreach (var romfsFile in romfs.Files)
{
ctx.Logger.LogMessage(romfsFile.FullPath);
}
}
}
}
private static void ProcessSwitchFs(Context ctx)
{
var switchFs = new SdFs(ctx.Keyset, ctx.Options.InFile);
if (ctx.Options.ListTitles)
{
ListTitles(switchFs);
}
if (ctx.Options.ListApps)
{
ctx.Logger.LogMessage(ListApplications(switchFs));
}
if (ctx.Options.RomfsOutDir != null)
{
var id = ctx.Options.TitleId;
if (id == 0)
{
ctx.Logger.LogMessage("Title ID must be specified to dump RomFS");
return;
}
if (!switchFs.Titles.TryGetValue(id, out var title))
{
ctx.Logger.LogMessage($"Could not find title {id:X16}");
return;
}
if (title.ProgramNca == null)
{
ctx.Logger.LogMessage($"Could not find main program data for title {id:X16}");
return;
}
var romfs = new Romfs(title.ProgramNca.OpenSection(1, false));
romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger);
}
}
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", "titlekeys.txt");
var keyFile = ctx.Options.Keyfile;
var titleKeyFile = ctx.Options.TitleKeyFile;
if (keyFile == null && File.Exists(homeKeyFile))
{
keyFile = homeKeyFile;
}
if (titleKeyFile == null && File.Exists(homeTitleKeyFile))
{
titleKeyFile = homeTitleKeyFile;
}
ctx.Keyset = ExternalKeys.ReadKeyFile(keyFile, titleKeyFile, ctx.Logger);
if (ctx.Options.SdSeed != null)
{
ctx.Keyset.SetSdSeed(ctx.Options.SdSeed.ToBytes());
}
}
private static void ListSdfs(string[] args)
{
var sdfs = LoadSdFs(args);
Console.WriteLine("Listing NCA files");
ListNcas(sdfs);
Console.WriteLine("Listing titles");
ListTitles(sdfs);
Console.WriteLine("Listing applications");
ListApplications(sdfs);
//DecryptNax0(sdfs, "C0628FB07A89E9050BDA258F74868E8D");
//DecryptTitle(sdfs, 0x010023900AEE0000);
}
static void FileReadTest(string[] args)
{
var sdfs = LoadSdFs(args);
var title = sdfs.Titles[0x0100E95004038000];
var nca = title.ProgramNca;
var romfsStream = nca.OpenSection(1, false);
var romfs = new Romfs(romfsStream);
var file = romfs.OpenFile("/stream/voice/us/127/127390101.nop");
using (var output = new FileStream("127390101.nop", FileMode.Create))
{
file.CopyTo(output);
}
}
static void ReadNca()
{
var keyset = ExternalKeys.ReadKeyFile("keys.txt", "titlekeys.txt");
using (var file = new FileStream("27eeccfe5f6e7637352273bc46ab97e4.nca", FileMode.Open, FileAccess.Read))
{
var nca = new Nca(keyset, file, false);
var romfsStream = nca.OpenSection(1, false);
var romfs = new Romfs(romfsStream);
var bfstm = romfs.OpenFile("/Sound/Resource/Stream/Demo149_1_SoundTrack.bfstm");
using (var progress = new ProgressBar())
using (var output = new FileStream("Demo149_1_SoundTrack.bfstm", FileMode.Create))
{
var watch = Stopwatch.StartNew();
bfstm.CopyStream(output, bfstm.Length, progress);
watch.Stop();
progress.LogMessage(watch.Elapsed.TotalSeconds.ToString());
}
}
}
static void ReadNcaSdfs(string[] args)
{
var sdfs = LoadSdFs(args);
var nca = sdfs.Ncas["8EE79C7AB0F16737BC50F049DFDBB595"];
var romfsStream = nca.OpenSection(1, false);
var romfs = new Romfs(romfsStream);
}
static void DecryptNax0(SdFs sdFs, string name)
{
if (!sdFs.Ncas.ContainsKey(name)) return;
var nca = sdFs.Ncas[name];
using (var output = new FileStream($"{nca.NcaId}.nca", FileMode.Create))
using (var progress = new ProgressBar())
{
progress.LogMessage($"Title ID: {nca.Header.TitleId:X16}");
progress.LogMessage($"Writing {nca.NcaId}.nca");
nca.Stream.Position = 0;
nca.Stream.CopyStream(output, nca.Stream.Length, progress);
}
}
static void DecryptTitle(SdFs sdFs, ulong titleId)
{
var title = sdFs.Titles[titleId];
var dirName = $"{titleId:X16}v{title.Version.Version}";
Directory.CreateDirectory(dirName);
foreach (var nca in title.Ncas)
{
using (var output = new FileStream(Path.Combine(dirName, nca.Filename), FileMode.Create))
using (var progress = new ProgressBar())
{
progress.LogMessage($"Writing {nca.Filename}");
nca.Stream.Position = 0;
nca.Stream.CopyStream(output, nca.Stream.Length, progress);
}
}
}
static SdFs LoadSdFs(string[] args)
{
var keyset = ExternalKeys.ReadKeyFile(args[0], "titlekeys.txt");
keyset.SetSdSeed(args[1].ToBytes());
var sdfs = new SdFs(keyset, args[2]);
return sdfs;
}
static void ListNcas(SdFs sdfs)
{
foreach (Nca nca in sdfs.Ncas.Values.OrderBy(x => x.Header.TitleId))
{
Console.WriteLine($"{nca.Header.TitleId:X16} {nca.Header.ContentType.ToString().PadRight(10, ' ')} {nca.NcaId}");
}
}
static void ListTitles(SdFs sdfs)
{
foreach (var title in sdfs.Titles.Values.OrderBy(x => x.Id))
{
Console.WriteLine($"{title.Name} {title.Control?.Version}");
Console.WriteLine($"{title.Id:X16} v{title.Version.Version} ({title.Version}) {title.Metadata.Type}");
foreach (var content in title.Metadata.ContentEntries)
{
Console.WriteLine(
$" {content.NcaId.ToHexString()}.nca {content.Type} {Util.GetBytesReadable(content.Size)}");
}
foreach (var nca in title.Ncas)
{
Console.WriteLine($" {nca.HasRightsId} {nca.NcaId} {nca.Header.ContentType}");
foreach (var sect in nca.Sections.Where(x => x != null))
{
Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.CryptType} {sect.SuperblockHashValidity}");
}
}
Console.WriteLine("");
}
}
static string ListApplications(SdFs sdfs)
{
var sb = new StringBuilder();
foreach (var app in sdfs.Applications.Values.OrderBy(x => x.Name))
{
sb.AppendLine($"{app.Name} v{app.DisplayVersion}");
if (app.Main != null)
{
sb.AppendLine($"Software: {Util.GetBytesReadable(app.Main.GetSize())}");
}
if (app.Patch != null)
{
sb.AppendLine($"Update Data: {Util.GetBytesReadable(app.Patch.GetSize())}");
}
if (app.AddOnContent.Count > 0)
{
sb.AppendLine($"DLC: {Util.GetBytesReadable(app.AddOnContent.Sum(x => x.GetSize()))}");
}
if (app.Nacp?.UserTotalSaveDataSize > 0)
sb.AppendLine($"User save: {Util.GetBytesReadable(app.Nacp.UserTotalSaveDataSize)}");
if (app.Nacp?.DeviceTotalSaveDataSize > 0)
sb.AppendLine($"System save: {Util.GetBytesReadable(app.Nacp.DeviceTotalSaveDataSize)}");
if (app.Nacp?.BcatSaveDataSize > 0)
sb.AppendLine($"BCAT save: {Util.GetBytesReadable(app.Nacp.BcatSaveDataSize)}");
sb.AppendLine();
}
return sb.ToString();
}
}
}