2018-06-21 18:16:51 +02:00
|
|
|
|
using System;
|
2018-06-30 01:49:53 +02:00
|
|
|
|
using System.Diagnostics;
|
2018-06-21 18:16:51 +02:00
|
|
|
|
using System.IO;
|
2018-06-21 23:03:58 +02:00
|
|
|
|
using System.Linq;
|
2018-06-21 16:25:20 +02:00
|
|
|
|
using libhac;
|
2018-06-16 19:11:38 +02:00
|
|
|
|
|
2018-06-21 16:25:20 +02:00
|
|
|
|
namespace hactoolnet
|
2018-06-16 19:11:38 +02:00
|
|
|
|
{
|
2018-06-21 16:25:20 +02:00
|
|
|
|
public static class Program
|
2018-06-16 19:11:38 +02:00
|
|
|
|
{
|
|
|
|
|
static void Main(string[] args)
|
2018-06-28 03:25:25 +02:00
|
|
|
|
{
|
2018-07-02 22:12:41 +02:00
|
|
|
|
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:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//ListSdfs(args);
|
2018-06-30 02:44:12 +02:00
|
|
|
|
//FileReadTest(args);
|
2018-06-28 22:02:23 +02:00
|
|
|
|
//ReadNca();
|
2018-06-29 21:53:51 +02:00
|
|
|
|
//ListSdfs(args);
|
|
|
|
|
//ReadNcaSdfs(args);
|
2018-06-28 03:25:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-07-02 22:12:41 +02:00
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.RomfsOut != null && nca.Sections[1] != null)
|
|
|
|
|
{
|
|
|
|
|
var romfs = nca.OpenSection(1, false);
|
|
|
|
|
|
|
|
|
|
using (var outFile = new FileStream(ctx.Options.RomfsOut, FileMode.Create, FileAccess.ReadWrite))
|
|
|
|
|
{
|
|
|
|
|
romfs.CopyStream(outFile, romfs.Length, ctx.Logger);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.Options.SectionOut[0] != null && nca.Sections[0] != null)
|
|
|
|
|
{
|
|
|
|
|
var romfs = nca.OpenSection(0, false);
|
|
|
|
|
|
|
|
|
|
using (var outFile = new FileStream(ctx.Options.SectionOut[0], FileMode.Create, FileAccess.ReadWrite))
|
|
|
|
|
{
|
|
|
|
|
romfs.CopyStream(outFile, romfs.Length, ctx.Logger);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void OpenKeyset(Context ctx)
|
|
|
|
|
{
|
|
|
|
|
ctx.Keyset = ExternalKeys.ReadKeyFile(ctx.Options.Keyfile, ctx.Options.TitleKeyFile, ctx.Logger);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-28 03:25:25 +02:00
|
|
|
|
private static void ListSdfs(string[] args)
|
2018-06-21 18:16:51 +02:00
|
|
|
|
{
|
2018-06-27 02:10:21 +02:00
|
|
|
|
var sdfs = LoadSdFs(args);
|
|
|
|
|
|
2018-06-25 21:01:24 +02:00
|
|
|
|
Console.WriteLine("Listing NCA files");
|
2018-06-27 02:10:21 +02:00
|
|
|
|
ListNcas(sdfs);
|
2018-06-25 21:01:24 +02:00
|
|
|
|
|
|
|
|
|
Console.WriteLine("Listing titles");
|
2018-06-27 02:10:21 +02:00
|
|
|
|
ListTitles(sdfs);
|
2018-06-25 21:01:24 +02:00
|
|
|
|
|
2018-07-02 20:16:38 +02:00
|
|
|
|
Console.WriteLine("Listing applications");
|
|
|
|
|
ListApplications(sdfs);
|
|
|
|
|
|
2018-06-27 02:10:21 +02:00
|
|
|
|
//DecryptNax0(sdfs, "C0628FB07A89E9050BDA258F74868E8D");
|
2018-06-28 22:02:23 +02:00
|
|
|
|
//DecryptTitle(sdfs, 0x010023900AEE0000);
|
2018-06-28 03:25:25 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-29 21:53:51 +02:00
|
|
|
|
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);
|
2018-06-30 02:44:12 +02:00
|
|
|
|
var file = romfs.OpenFile("/stream/voice/us/127/127390101.nop");
|
2018-06-29 21:53:51 +02:00
|
|
|
|
|
2018-06-30 02:44:12 +02:00
|
|
|
|
using (var output = new FileStream("127390101.nop", FileMode.Create))
|
2018-06-29 21:53:51 +02:00
|
|
|
|
{
|
2018-06-30 02:44:12 +02:00
|
|
|
|
file.CopyTo(output);
|
2018-06-29 21:53:51 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-28 03:25:25 +02:00
|
|
|
|
static void ReadNca()
|
|
|
|
|
{
|
|
|
|
|
var keyset = ExternalKeys.ReadKeyFile("keys.txt", "titlekeys.txt");
|
2018-06-29 21:53:51 +02:00
|
|
|
|
using (var file = new FileStream("27eeccfe5f6e7637352273bc46ab97e4.nca", FileMode.Open, FileAccess.Read))
|
2018-06-28 03:25:25 +02:00
|
|
|
|
{
|
|
|
|
|
var nca = new Nca(keyset, file, false);
|
2018-06-29 21:53:51 +02:00
|
|
|
|
var romfsStream = nca.OpenSection(1, false);
|
|
|
|
|
|
|
|
|
|
var romfs = new Romfs(romfsStream);
|
2018-06-30 01:49:53 +02:00
|
|
|
|
var bfstm = romfs.OpenFile("/Sound/Resource/Stream/Demo149_1_SoundTrack.bfstm");
|
2018-06-28 03:25:25 +02:00
|
|
|
|
|
2018-06-30 01:49:53 +02:00
|
|
|
|
using (var progress = new ProgressBar())
|
|
|
|
|
using (var output = new FileStream("Demo149_1_SoundTrack.bfstm", FileMode.Create))
|
2018-06-28 03:25:25 +02:00
|
|
|
|
{
|
2018-06-30 01:49:53 +02:00
|
|
|
|
var watch = Stopwatch.StartNew();
|
|
|
|
|
bfstm.CopyStream(output, bfstm.Length, progress);
|
|
|
|
|
watch.Stop();
|
|
|
|
|
progress.LogMessage(watch.Elapsed.TotalSeconds.ToString());
|
2018-06-28 03:25:25 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-21 18:16:51 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-29 21:53:51 +02:00
|
|
|
|
static void ReadNcaSdfs(string[] args)
|
|
|
|
|
{
|
|
|
|
|
var sdfs = LoadSdFs(args);
|
|
|
|
|
var nca = sdfs.Ncas["8EE79C7AB0F16737BC50F049DFDBB595"];
|
2018-06-30 01:49:53 +02:00
|
|
|
|
var romfsStream = nca.OpenSection(1, false);
|
2018-06-29 21:53:51 +02:00
|
|
|
|
var romfs = new Romfs(romfsStream);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-27 02:10:21 +02:00
|
|
|
|
static void DecryptNax0(SdFs sdFs, string name)
|
2018-06-16 19:11:38 +02:00
|
|
|
|
{
|
2018-06-27 02:42:01 +02:00
|
|
|
|
if (!sdFs.Ncas.ContainsKey(name)) return;
|
2018-06-27 02:10:21 +02:00
|
|
|
|
var nca = sdFs.Ncas[name];
|
|
|
|
|
using (var output = new FileStream($"{nca.NcaId}.nca", FileMode.Create))
|
|
|
|
|
using (var progress = new ProgressBar())
|
2018-06-20 19:42:56 +02:00
|
|
|
|
{
|
2018-06-27 02:10:21 +02:00
|
|
|
|
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);
|
2018-06-20 19:42:56 +02:00
|
|
|
|
}
|
2018-06-16 19:11:38 +02:00
|
|
|
|
}
|
2018-06-21 18:16:51 +02:00
|
|
|
|
|
2018-06-27 02:42:01 +02:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-27 02:10:21 +02:00
|
|
|
|
static SdFs LoadSdFs(string[] args)
|
2018-06-22 23:17:20 +02:00
|
|
|
|
{
|
2018-06-28 22:02:23 +02:00
|
|
|
|
var keyset = ExternalKeys.ReadKeyFile(args[0], "titlekeys.txt");
|
2018-06-22 23:17:20 +02:00
|
|
|
|
keyset.SetSdSeed(args[1].ToBytes());
|
2018-06-27 02:10:21 +02:00
|
|
|
|
var sdfs = new SdFs(keyset, args[2]);
|
|
|
|
|
return sdfs;
|
|
|
|
|
}
|
2018-06-26 00:26:47 +02:00
|
|
|
|
|
2018-06-27 02:10:21 +02:00
|
|
|
|
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}");
|
2018-06-22 23:17:20 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-27 02:10:21 +02:00
|
|
|
|
static void ListTitles(SdFs sdfs)
|
2018-06-21 18:16:51 +02:00
|
|
|
|
{
|
2018-06-27 02:10:21 +02:00
|
|
|
|
foreach (var title in sdfs.Titles.Values.OrderBy(x => x.Id))
|
2018-06-26 00:26:47 +02:00
|
|
|
|
{
|
2018-06-30 21:15:55 +02:00
|
|
|
|
Console.WriteLine($"{title.Name} {title.Control?.Version}");
|
2018-06-27 02:10:21 +02:00
|
|
|
|
Console.WriteLine($"{title.Id:X16} v{title.Version.Version} ({title.Version}) {title.Metadata.Type}");
|
2018-06-22 23:17:20 +02:00
|
|
|
|
|
2018-06-27 02:10:21 +02:00
|
|
|
|
foreach (var content in title.Metadata.ContentEntries)
|
2018-06-22 23:17:20 +02:00
|
|
|
|
{
|
2018-06-27 02:10:21 +02:00
|
|
|
|
Console.WriteLine(
|
2018-06-27 02:42:01 +02:00
|
|
|
|
$" {content.NcaId.ToHexString()}.nca {content.Type} {Util.GetBytesReadable(content.Size)}");
|
2018-06-23 04:02:19 +02:00
|
|
|
|
}
|
2018-06-27 02:10:21 +02:00
|
|
|
|
|
2018-06-28 22:02:23 +02:00
|
|
|
|
foreach (var nca in title.Ncas)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" {nca.HasRightsId} {nca.NcaId} {nca.Header.ContentType}");
|
|
|
|
|
|
2018-06-28 23:55:36 +02:00
|
|
|
|
foreach (var sect in nca.Sections.Where(x => x != null))
|
2018-06-28 22:02:23 +02:00
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($" {sect.SectionNum} {sect.Type} {sect.Header.CryptType} {sect.SuperblockHashValidity}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-27 02:10:21 +02:00
|
|
|
|
Console.WriteLine("");
|
2018-06-21 18:16:51 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-02 20:16:38 +02:00
|
|
|
|
|
|
|
|
|
static void ListApplications(SdFs sdfs)
|
|
|
|
|
{
|
|
|
|
|
foreach (var app in sdfs.Applications.Values.OrderBy(x => x.Name))
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"{app.Name} v{app.DisplayVersion}");
|
|
|
|
|
|
|
|
|
|
if (app.Main != null)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"Software: {Util.GetBytesReadable(app.Main.GetSize())}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (app.Patch != null)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"Update Data: {Util.GetBytesReadable(app.Patch.GetSize())}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (app.AddOnContent.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"DLC: {Util.GetBytesReadable(app.AddOnContent.Sum(x => x.GetSize()))}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (app.Nacp?.UserTotalSaveDataSize > 0)
|
|
|
|
|
Console.WriteLine($"User save: {Util.GetBytesReadable(app.Nacp.UserTotalSaveDataSize)}");
|
|
|
|
|
if (app.Nacp?.DeviceTotalSaveDataSize > 0)
|
|
|
|
|
Console.WriteLine($"System save: {Util.GetBytesReadable(app.Nacp.DeviceTotalSaveDataSize)}");
|
|
|
|
|
if (app.Nacp?.BcatSaveDataSize > 0)
|
|
|
|
|
Console.WriteLine($"BCAT save: {Util.GetBytesReadable(app.Nacp.BcatSaveDataSize)}");
|
|
|
|
|
|
|
|
|
|
Console.WriteLine("");
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-06-16 19:11:38 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-07-02 22:12:41 +02:00
|
|
|
|
|