XCI-Explorer/XCI_Explorer/MainForm.cs
2022-11-13 12:16:17 -05:00

1912 lines
No EOL
68 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml.Linq;
using XCI_Explorer.Helpers;
using XTSSharp;
namespace XCI_Explorer;
public partial class MainForm : Form
{
public List<char> chars = new();
public byte[] NcaHeaderEncryptionKey1_Prod;
public byte[] NcaHeaderEncryptionKey2_Prod;
public string Mkey;
public double UsedSize;
private Image[] Icons = new Image[16];
private readonly string[] Language = new string[16] {
"American English",
"British English",
"Japanese",
"French",
"German",
"Latin American Spanish",
"Spanish",
"Italian",
"Dutch",
"Canadian French",
"Portuguese",
"Russian",
"Korean",
"Traditional Chinese",
"Simplified Chinese",
"???"
};
public MainForm()
{
InitializeComponent();
Text = $"XCI Explorer v{getAssemblyVersion()}";
LB_SelectedData.Text = "";
LB_DataOffset.Text = "";
LB_DataSize.Text = "";
LB_HashedRegionSize.Text = "";
LB_ActualHash.Text = "";
LB_ExpectedHash.Text = "";
Show();
//MAC - Set Current Directory to application directory so it can find the keys
String startupPath = Application.StartupPath;
Directory.SetCurrentDirectory(startupPath);
if (!File.Exists("keys.txt"))
{
new CenterWinDialog(this);
if (MessageBox.Show("keys.txt is missing.\nDo you want to automatically download it now?\n\nBy pressing 'Yes' you agree that you own these keys.\n", "XCI Explorer", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
using HttpClient client = new();
using HttpResponseMessage response = client.Send(new HttpRequestMessage(HttpMethod.Get, Util.Base64Decode("aHR0cHM6Ly9wYXN0ZWJpbi5jb20vcmF3L0Z2M25GRzJR")));
using Stream stream = response.Content.ReadAsStream();
using FileStream fs = new("keys.txt", FileMode.CreateNew);
stream.CopyTo(fs);
}
if (!File.Exists("keys.txt"))
{
new CenterWinDialog(this);
MessageBox.Show("keys.txt failed to load.\nPlease include keys.txt in the root folder.");
Environment.Exit(0);
}
}
if (!File.Exists(Path.Join("tools", "hactool.exe")))
{
Directory.CreateDirectory("tools");
new CenterWinDialog(this);
MessageBox.Show("hactool.exe is missing.\nPlease include hactool.exe in the 'tools' folder.");
Environment.Exit(0);
}
getKey();
//MAC - Set the double clicked file name into the UI and process file
String[] args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{
TB_File.Text = args[1];
Application.DoEvents();
ProcessFile();
}
}
private string getAssemblyVersion()
{
string assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString();
string[] versionArray = assemblyVersion.Split('.');
assemblyVersion = string.Join(".", versionArray.Take(3));
return assemblyVersion;
}
private void getKey()
{
string text = (from x in File.ReadAllLines("keys.txt")
select x.Split('=') into x
where x.Length > 1
select x).ToDictionary((string[] x) => x[0].Trim(), (string[] x) => x[1])["header_key"].Replace(" ", "");
NcaHeaderEncryptionKey1_Prod = Util.StringToByteArray(text.Remove(32, 32));
NcaHeaderEncryptionKey2_Prod = Util.StringToByteArray(text.Remove(0, 32));
}
public bool getMKey()
{
Dictionary<string, string> dictionary = (from x in File.ReadAllLines("keys.txt")
select x.Split('=') into x
where x.Length > 1
select x).ToDictionary((string[] x) => x[0].Trim(), (string[] x) => x[1]);
Mkey = "master_key_";
string MkeyL = "master_key_";
if (NCA.NCA_Headers[0].MasterKeyRev == 0 || NCA.NCA_Headers[0].MasterKeyRev == 1)
{
Mkey += "00";
}
else if (NCA.NCA_Headers[0].MasterKeyRev < 17)
{
int num = NCA.NCA_Headers[0].MasterKeyRev - 1;
string capchar = num.ToString("X");
string lowchar = capchar.ToLower();
Mkey += $"0{capchar}";
MkeyL += $"0{lowchar}";
}
else if (NCA.NCA_Headers[0].MasterKeyRev >= 17)
{
int num2 = NCA.NCA_Headers[0].MasterKeyRev - 1;
string capchar = num2.ToString("X");
string lowchar = capchar.ToLower();
Mkey += num2.ToString();
MkeyL += num2.ToString();
}
try
{
Mkey = dictionary[Mkey].Replace(" ", "");
return true;
}
catch
{
try
{
MkeyL = dictionary[MkeyL].Replace(" ", "");
return true;
}
catch
{
return false;
}
}
}
private void ProcessFile()
{
// Code needs refactoring
LB_SelectedData.Text = "";
LB_DataOffset.Text = "";
LB_DataSize.Text = "";
LB_HashedRegionSize.Text = "";
LB_ExpectedHash.Text = "";
LB_ActualHash.Text = "";
B_Extract.Enabled = false;
try
{
if (CheckNSP())
{
B_TrimXCI.Enabled = false;
B_ExportCert.Enabled = false;
B_ImportCert.Enabled = false;
B_ViewCert.Enabled = false;
B_ClearCert.Enabled = false;
LoadNSP();
}
else if (CheckXCI())
{
B_TrimXCI.Enabled = true;
B_ExportCert.Enabled = true;
B_ImportCert.Enabled = true;
B_ViewCert.Enabled = true;
B_ClearCert.Enabled = true;
LoadXCI();
}
else
{
TB_File.Text = null;
new CenterWinDialog(this);
MessageBox.Show("File is corrupt or unsupported.");
}
}
catch (Exception e)
{
new CenterWinDialog(this);
MessageBox.Show($"File is corrupt or unsupported.\nException: {e.Message}");
}
}
private void B_LoadROM_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog = new()
{
Filter = "Switch Game File (*.xci, *.nsp, *.nsz)|*.xci;*.nsp;*.nsz|All Files (*.*)|*.*"
};
new CenterWinDialog(this);
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
TB_File.Text = openFileDialog.FileName;
ProcessFile();
}
}
private void LoadXCI()
{
string[] array = new string[5]
{
"B",
"KB",
"MB",
"GB",
"TB"
};
double num = new FileInfo(TB_File.Text).Length;
TB_ROMExactSize.Text = $"({num} bytes)";
int num2 = 0;
while (num >= 1024.0 && num2 < array.Length - 1)
{
num2++;
num /= 1024.0;
}
TB_ROMSize.Text = $"{num:0.##} {array[num2]}";
double num3 = UsedSize = XCI.XCI_Headers[0].CardSize2 * 512 + 512;
TB_ExactUsedSpace.Text = $"({num3} bytes)";
if (isTrimmed())
{
B_TrimXCI.Enabled = false;
}
num2 = 0;
while (num3 >= 1024.0 && num2 < array.Length - 1)
{
num2++;
num3 /= 1024.0;
}
TB_UsedSpace.Text = $"{num3:0.##} {array[num2]}";
TB_Capacity.Text = Util.GetCapacity(XCI.XCI_Headers[0].CardSize1);
LoadPartitions();
LoadNCAData();
LoadGameInfos();
}
// Giba's better implementation (more native)
public void LoadNSP()
{
CB_RegionName.Items.Clear();
CB_RegionName.Enabled = true;
TB_TID.Text = "";
TB_Capacity.Text = "";
TB_MKeyRev.Text = "";
TB_SDKVer.Text = "";
TB_GameRev.Text = "";
TB_ProdCode.Text = "";
TB_Name.Text = "";
TB_Dev.Text = "";
int basenum = 0;
int updnum = 0;
int dlcnum = 0;
string pversion = "";
int patchflag = 0;
int patchnum = 0;
string patchver = "";
int baseflag = 0;
string[] basetitle = new string[5];
string[] updtitle = new string[10];
string[] dlctitle = new string[300];
PB_GameIcon.BackgroundImage = null;
Array.Clear(Icons, 0, Icons.Length);
TV_Partitions.Nodes.Clear();
FileInfo fi = new(TB_File.Text);
string contentType = "";
// Maximum number of files in NSP to read in
const int MAXFILES = 250;
//Get File Size
string[] array_fs = new string[5] { "B", "KB", "MB", "GB", "TB" };
double num_fs = fi.Length;
int num2_fs = 0;
TB_ROMExactSize.Text = $"({num_fs} bytes)";
TB_ExactUsedSpace.Text = TB_ROMExactSize.Text;
while (num_fs >= 1024.0 && num2_fs < array_fs.Length - 1)
{
num2_fs++;
num_fs /= 1024.0;
}
TB_ROMSize.Text = $"{num_fs:0.##} {array_fs[num2_fs]}";
TB_UsedSpace.Text = TB_ROMSize.Text;
LoadNSPPartitions();
Process process = new();
try
{
FileStream fileStream = File.OpenRead(TB_File.Text);
string ncaTarget = "";
string xmlVersion = "";
List<char> chars = new();
byte[] array = new byte[16];
byte[] array2 = new byte[24];
fileStream.Read(array, 0, 16);
PFS0.PFS0_Headers[0] = new(array);
if (!PFS0.PFS0_Headers[0].Magic.Contains("PFS0"))
{
return;
}
PFS0.PFS0_Entry[] array3;
array3 = new PFS0.PFS0_Entry[Math.Max(PFS0.PFS0_Headers[0].FileCount, MAXFILES)]; //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files
for (int m = 0; m < PFS0.PFS0_Headers[0].FileCount; m++)
{
fileStream.Position = 16 + 24 * m;
fileStream.Read(array2, 0, 24);
array3[m] = new(array2);
if (m == MAXFILES - 1) //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files
{
break;
}
}
for (int n = 0; n < PFS0.PFS0_Headers[0].FileCount; n++)
{
fileStream.Position = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + array3[n].Name_ptr;
int num4;
while ((num4 = fileStream.ReadByte()) != 0 && num4 != 0)
{
chars.Add((char)num4);
}
array3[n].Name = new(chars.ToArray());
chars.Clear();
if (array3[n].Name.EndsWith(".cnmt.xml"))
{
byte[] array4 = new byte[array3[n].Size];
fileStream.Position = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + PFS0.PFS0_Headers[0].StringTableSize + array3[n].Offset;
fileStream.Read(array4, 0, (int)array3[n].Size);
XDocument xml = XDocument.Parse(Encoding.UTF8.GetString(array4));
TB_TID.Text = xml.Element("ContentMeta").Element("Id").Value.Remove(1, 2).ToUpper(); //id
pversion = xml.Element("ContentMeta").Element("Version").Value; //version
contentType = xml.Element("ContentMeta").Element("Type").Value; //content
var test = NACP.NACP_Datas[0].GameVer.Replace("\0", "");
if (contentType == "Patch")
{
patchflag = 1;
if (Convert.ToInt32(pversion) > patchnum)
{
patchnum = Convert.ToInt32(pversion);
xmlVersion = $"v{xml.Element("ContentMeta").Element("Version").Value}";
int number = Convert.ToInt32(pversion);
patchver = $"v{Convert.ToString((double)number / 65536)}";
}
updtitle[updnum] = $"[{TB_TID.Text}][v{pversion}]";
updnum++;
}
else if (contentType == "Application")
{
baseflag = 1;
if (patchflag != 1)
{
xmlVersion = $"v{xml.Element("ContentMeta").Element("Version").Value}";
}
basetitle[basenum] = $"[{TB_TID.Text}][v{pversion}]";
basenum++;
}
else
{
if (baseflag == 0 && patchflag == 0)
{
xmlVersion = $"v{xml.Element("ContentMeta").Element("Version").Value}";
}
dlctitle[dlcnum] = $"[{TB_TID.Text}][v{pversion}]";
dlcnum++;
}
if (contentType != "AddOnContent")
{
foreach (XElement xe in xml.Descendants("Content"))
{
if (xe.Element("Type").Value != "Control")
{
continue;
}
ncaTarget = $"{xe.Element("Id").Value}.nca";
break;
}
}
else //This is a DLC
{
foreach (XElement xe in xml.Descendants("Content"))
{
if (xe.Element("Type").Value != "Meta")
{
continue;
}
ncaTarget = $"{xe.Element("Id").Value}.cnmt.nca";
break;
}
}
}
if (n == MAXFILES - 1) //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files
{
break;
}
}
if (string.IsNullOrEmpty(ncaTarget))
{
//Missing content metadata xml. Read from content metadata nca instead
for (int n = 0; n < PFS0.PFS0_Headers[0].FileCount; n++)
{
if (array3[n].Name.EndsWith(".cnmt.nca"))
{
try
{
File.Delete("meta");
Directory.Delete("data", true);
}
catch { }
using (FileStream fileStream2 = File.OpenWrite("meta"))
{
fileStream.Position = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + PFS0.PFS0_Headers[0].StringTableSize + array3[n].Offset;
byte[] buffer = new byte[8192];
long num = array3[n].Size;
int num4;
while ((num4 = fileStream.Read(buffer, 0, 8192)) > 0 && num > 0)
{
fileStream2.Write(buffer, 0, num4);
num -= num4;
}
fileStream2.Close();
}
process = new()
{
StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = Path.Join("tools", "hactool.exe"),
Arguments = "-k keys.txt --section0dir=data meta",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
process.Start();
string masterkey = "";
while (!process.StandardOutput.EndOfStream)
{
string output = process.StandardOutput.ReadLine();
if (output.StartsWith("Master Key Revision"))
{
masterkey = Regex.Replace(output, @"\s+", " ");
}
}
process.WaitForExit();
if (!Directory.Exists("data"))
{
new CenterWinDialog(this);
MessageBox.Show($"{masterkey} is missing!");
}
else
{
try
{
string[] cnmt = Directory.GetFiles("data", "*.cnmt");
if (cnmt.Length == 0)
{
return;
}
using FileStream fileStream3 = File.OpenRead(cnmt[0]);
byte[] buffer = new byte[32];
byte[] buffer2 = new byte[56];
CNMT.CNMT_Header[] array7 = new CNMT.CNMT_Header[1];
fileStream3.Read(buffer, 0, 32);
array7[0] = new CNMT.CNMT_Header(buffer);
byte[] TitleID = BitConverter.GetBytes(array7[0].TitleID);
Array.Reverse(TitleID);
TB_TID.Text = BitConverter.ToString(TitleID).Replace("-", "");
if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.REGULAR_APPLICATION)
{
contentType = "Application";
baseflag = 1;
if (patchflag != 1)
{
xmlVersion = $"v{array7[0].TitleVersion}";
}
basetitle[basenum] = $"[{TB_TID.Text}][v{array7[0].TitleVersion}]";
basenum++;
}
else if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.UPDATE_TITLE)
{
contentType = "Patch";
patchflag = 1;
if (array7[0].TitleVersion > patchnum)
{
patchnum = array7[0].TitleVersion;
xmlVersion = $"v{array7[0].TitleVersion}";
int number = Convert.ToInt32(array7[0].TitleVersion);
patchver = $"v{Convert.ToString((double)number / 65536)}";
}
updtitle[updnum] = $"[{TB_TID.Text}][v{array7[0].TitleVersion}]";
updnum++;
}
else if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.ADD_ON_CONTENT)
{
if (baseflag == 0 && patchflag == 0)
{
xmlVersion = $"v{array7[0].TitleVersion}";
}
contentType = "AddOnContent";
dlctitle[dlcnum] = $"[{TB_TID.Text}][v{array7[0].TitleVersion}]";
dlcnum++;
}
fileStream3.Position = array7[0].Offset + 32;
CNMT.CNMT_Entry[] array9 = new CNMT.CNMT_Entry[array7[0].ContentCount];
for (int k = 0; k < array7[0].ContentCount; k++)
{
fileStream3.Read(buffer2, 0, 56);
array9[k] = new CNMT.CNMT_Entry(buffer2);
if (array9[k].Type == (byte)CNMT.CNMT_Entry.ContentType.CONTROL || array9[k].Type == (byte)CNMT.CNMT_Entry.ContentType.DATA)
{
ncaTarget = $"{BitConverter.ToString(array9[k].NcaId).ToLower().Replace("-", "")}.nca";
break;
}
}
fileStream3.Close();
}
catch
{
}
}
}
}
}
for (int n = 0; n < PFS0.PFS0_Headers[0].FileCount; n++)
{
if (array3[n].Name.Equals(ncaTarget))
{
Directory.CreateDirectory("tmp");
byte[] array5 = new byte[64 * 1024];
fileStream.Position = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + PFS0.PFS0_Headers[0].StringTableSize + array3[n].Offset;
using (Stream output = File.Create(Path.Join("tmp", ncaTarget)))
{
long Size = array3[n].Size;
int result = 0;
while ((result = fileStream.Read(array5, 0, (int)Math.Min(array5.Length, Size))) > 0)
{
output.Write(array5, 0, result);
Size -= result;
}
}
break;
}
if (n == MAXFILES - 1) //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files
{
break;
}
}
fileStream.Close();
if (contentType != "AddOnContent")
{
process = new Process
{
StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = Path.Join("tools", "hactool.exe"),
Arguments = $"-k keys.txt --romfsdir=tmp tmp/{ncaTarget}",
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
process.WaitForExit();
process.Close();
byte[] flux = new byte[200];
try
{
byte[] source = File.ReadAllBytes(Path.Join("tmp", "control.nacp"));
NACP.NACP_Datas[0] = new NACP.NACP_Data(source.Skip(0x3000).Take(0x1000).ToArray());
for (int i = 0; i < NACP.NACP_Strings.Length; i++)
{
NACP.NACP_Strings[i] = new NACP.NACP_String(source.Skip(i * 0x300).Take(0x300).ToArray());
if (NACP.NACP_Strings[i].Check != 0)
{
CB_RegionName.Items.Add(Language[i]);
string icon_filename = Path.Join("tmp", $"icon_{Language[i].Replace(" ", "")}.dat");
if (File.Exists(icon_filename))
{
using Bitmap original = new(icon_filename);
Icons[i] = new Bitmap(original);
PB_GameIcon.BackgroundImage = Icons[i];
}
}
}
string gameVer = NACP.NACP_Datas[0].GameVer.Replace("\0", "");
if (xmlVersion.Trim() == "")
{
TB_GameRev.Text = $"GENERAL:{Environment.NewLine}({gameVer}){Environment.NewLine}";
}
else
{
string cache = $"GENERAL:{Environment.NewLine}{gameVer}{((patchflag == 1) ? $" ({patchver})" : "")}{Environment.NewLine}";
if (basenum != 0)
{
cache += $"BASE:{Environment.NewLine}";
for (int i = 0; i < basenum; i++)
{
cache += basetitle[i] + Environment.NewLine;
}
}
else
{
cache += $"BASE:{Environment.NewLine}EMPTY{Environment.NewLine}";
}
if (updnum != 0)
{
cache += $"UPD:{Environment.NewLine}";
for (int i = 0; i < updnum; i++)
{
cache += updtitle[i] + Environment.NewLine;
}
}
else
{
cache += $"UPD:{Environment.NewLine}EMPTY{Environment.NewLine}";
}
if (dlcnum != 0)
{
cache += $"DLC:{Environment.NewLine}";
for (int i = 0; i < dlcnum; i++)
{
if (i < dlcnum - 1)
{
cache += dlctitle[i] + Environment.NewLine;
}
else
{
cache += dlctitle[i];
}
}
}
else
{
cache += $"DLC:{Environment.NewLine}EMPTY";
}
TB_GameRev.Text = cache;
label12.Text = $"{basenum} BASE, {updnum} UPD, {dlcnum} DLC";
}
TB_ProdCode.Text = NACP.NACP_Datas[0].GameProd.Replace("\0", "");
if (TB_ProdCode.Text == "")
{
TB_ProdCode.Text = "No Prod. ID";
}
for (int z = 0; z < NACP.NACP_Strings.Length; z++)
{
if (NACP.NACP_Strings[z].GameName.Replace("\0", "") != "")
{
TB_Name.Text = NACP.NACP_Strings[z].GameName.Replace("\0", "");
break;
}
}
for (int z = 0; z < NACP.NACP_Strings.Length; z++)
{
if (NACP.NACP_Strings[z].GameAuthor.Replace("\0", "") != "")
{
TB_Dev.Text = NACP.NACP_Strings[z].GameAuthor.Replace("\0", "");
break;
}
}
}
catch { }
}
else
{
if (xmlVersion.Trim() == "")
{
TB_GameRev.Text = $"GENERAL:{Environment.NewLine}{Environment.NewLine}";
}
else
{
string gameVer = NACP.NACP_Datas[0].GameVer.Replace("\0", "");
string cache = $"GENERAL:{Environment.NewLine}{gameVer}{((patchflag == 1) ? $" ({patchver})" : "")}{Environment.NewLine}";
if (basenum != 0)
{
cache += $"BASE:{Environment.NewLine}";
for (int i = 0; i < basenum; i++)
{
cache += basetitle[i] + Environment.NewLine;
}
}
else
{
cache += $"BASE:{Environment.NewLine} EMPTY {Environment.NewLine}";
}
if (updnum != 0)
{
cache += $"UPD:{Environment.NewLine}";
for (int i = 0; i < updnum; i++)
{
cache += updtitle[i] + Environment.NewLine;
}
}
else
{
cache += $"UPD:{Environment.NewLine} EMPTY {Environment.NewLine}";
}
if (dlcnum != 0)
{
cache += $"DLC:{Environment.NewLine}";
for (int i = 0; i < dlcnum; i++)
{
if (i < dlcnum - 1)
{
cache += dlctitle[i] + Environment.NewLine;
}
else
{
cache += dlctitle[i];
}
}
}
else
{
cache += $"DLC:{Environment.NewLine}EMPTY";
}
TB_GameRev.Text = cache;
label12.Text = $"{basenum} BASE, {updnum} UPD, {dlcnum} DLC";
}
TB_ProdCode.Text = "No Prod. ID";
}
// Lets get SDK Version, Distribution Type and Masterkey revision
// This is far from the best aproach, but it's what we have for now
process = new Process
{
StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = Path.Join("tools", "hactool.exe"),
Arguments = $"-k keys.txt tmp/{ncaTarget}",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
StreamReader sr = process.StandardOutput;
while (sr.Peek() >= 0)
{
string str;
string[] strArray;
str = sr.ReadLine();
strArray = str.Split(':');
if (strArray[0] == "SDK Version")
{
TB_SDKVer.Text = strArray[1].Trim();
}
else if (strArray[0] == "Master Key Revision")
{
string MasterKey = strArray[1].Trim();
int keyblob;
MasterKey = MasterKey.Split(new char[2] { 'x', ' ' })[1];
keyblob = Convert.ToInt32(MasterKey, 16);
MasterKey = Util.GetMkey((byte)(keyblob + 1));
TB_MKeyRev.Text = MasterKey;
break;
}
}
process.WaitForExit();
process.Close();
}
catch { }
try
{
File.Delete("meta");
Directory.Delete("data", true);
}
catch
{
}
try
{
Directory.Delete("tmp", true);
}
catch
{
}
TB_Capacity.Text = "eShop";
if (TB_Name.Text.Trim() != "")
{
CB_RegionName.SelectedIndex = 0;
}
}
private void LoadGameInfos()
{
CB_RegionName.Items.Clear();
CB_RegionName.Enabled = true;
TB_Name.Text = "";
TB_Dev.Text = "";
PB_GameIcon.BackgroundImage = null;
int basenum = 0;
int updnum = 0;
int dlcnum = 0;
int patchflag = 0;
int patchnum = 0;
string patchver = "";
int baseflag = 0;
string[] basetitle = new string[5];
string[] updtitle = new string[10];
string[] dlctitle = new string[300];
string xmlVersion = "";
string saveTID = "";
Array.Clear(Icons, 0, Icons.Length);
if (getMKey())
{
using FileStream fileStream = File.OpenRead(TB_File.Text);
List<string> ncaTarget = new();
string GameRevision = "";
for (int si = 0; si < SecureSize.Length; si++)
{
if (SecureSize[si] > 0x4E20000)
{
continue;
}
if (!SecureName[si].EndsWith(".cnmt.nca"))
{
continue;
}
try
{
File.Delete("meta");
Directory.Delete("data", true);
}
catch
{
}
using (FileStream fileStream2 = File.OpenWrite("meta"))
{
fileStream.Position = SecureOffset[si];
byte[] fsBuffer = new byte[8192];
long num = SecureSize[si];
int num4;
while ((num4 = fileStream.Read(fsBuffer, 0, 8192)) > 0 && num > 0)
{
fileStream2.Write(fsBuffer, 0, num4);
num -= num4;
}
fileStream2.Close();
}
Process process1 = new Process
{
StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = Path.Join("tools", "hactool.exe"),
Arguments = "-k keys.txt --section0dir=data meta",
UseShellExecute = false,
CreateNoWindow = true
}
};
process1.Start();
process1.WaitForExit();
string[] cnmt = Directory.GetFiles("data", "*.cnmt");
if (cnmt.Length == 0)
{
continue;
}
using FileStream fileStream3 = File.OpenRead(cnmt[0]);
byte[] buffer = new byte[32];
byte[] buffer2 = new byte[56];
CNMT.CNMT_Header[] array7 = new CNMT.CNMT_Header[1];
fileStream3.Read(buffer, 0, 32);
array7[0] = new CNMT.CNMT_Header(buffer);
byte[] TitleID = BitConverter.GetBytes(array7[0].TitleID);
Array.Reverse(TitleID);
saveTID = BitConverter.ToString(TitleID).Replace("-", "");
if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.REGULAR_APPLICATION)
{
baseflag = 1;
if (patchflag != 1)
{
xmlVersion = $"v{array7[0].TitleVersion}";
}
basetitle[basenum] = $"[{saveTID}][v{array7[0].TitleVersion}]";
basenum++;
}
else if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.UPDATE_TITLE)
{
patchflag = 1;
if (array7[0].TitleVersion > patchnum)
{
patchnum = array7[0].TitleVersion;
xmlVersion = $"v{array7[0].TitleVersion}";
int number = Convert.ToInt32(array7[0].TitleVersion);
patchver = $"v{Convert.ToString((double)number / 65536)}";
}
updtitle[updnum] = $"[{saveTID}][v{array7[0].TitleVersion}]";
updnum++;
}
else if (array7[0].Type == (byte)CNMT.CNMT_Header.TitleType.ADD_ON_CONTENT)
{
if (patchflag == 0 && baseflag == 0)
{
xmlVersion = $"v{array7[0].TitleVersion}";
}
dlctitle[dlcnum] = $"[{saveTID}][v{array7[0].TitleVersion}]";
dlcnum++;
}
fileStream3.Position = array7[0].Offset + 32;
CNMT.CNMT_Entry[] array9 = new CNMT.CNMT_Entry[array7[0].ContentCount];
for (int k = 0; k < array7[0].ContentCount; k++)
{
fileStream3.Read(buffer2, 0, 56);
array9[k] = new CNMT.CNMT_Entry(buffer2);
if (array9[k].Type == (byte)CNMT.CNMT_Entry.ContentType.CONTROL || array9[k].Type == (byte)CNMT.CNMT_Entry.ContentType.DATA)
{
ncaTarget.Add($"{BitConverter.ToString(array9[k].NcaId).ToLower().Replace("-", "")}.nca");
break;
}
}
fileStream3.Close();
}
for (int si = 0; si < SecureSize.Length; si++)
{
if (SecureSize[si] > 0x4E20000)
{
continue;
}
if (!ncaTarget.Contains(SecureName[si]))
{
continue;
}
try
{
File.Delete("meta");
Directory.Delete("data", true);
}
catch
{
}
using (FileStream fileStream2 = File.OpenWrite("meta"))
{
fileStream.Position = SecureOffset[si];
byte[] buffer = new byte[8192];
long num = SecureSize[si];
int num2;
while ((num2 = fileStream.Read(buffer, 0, 8192)) > 0 && num > 0)
{
fileStream2.Write(buffer, 0, num2);
num -= num2;
}
fileStream2.Close();
}
Process process = new();
process.StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = Path.Join("tools", "hactool.exe"),
Arguments = "-k keys.txt --romfsdir=data meta",
UseShellExecute = false,
CreateNoWindow = true
};
process.Start();
process.WaitForExit();
if (!File.Exists(Path.Join("data", "control.nacp")))
{
continue;
}
byte[] source = File.ReadAllBytes(Path.Join("data", "control.nacp"));
NACP.NACP_Datas[0] = new NACP.NACP_Data(source.Skip(0x3000).Take(0x1000).ToArray());
string GameVer = NACP.NACP_Datas[0].GameVer.Replace("\0", "");
Version version1, version2;
if (!Version.TryParse(Regex.Replace(GameRevision, @"[^\d.].*$", ""), out version1))
{
version1 = new Version();
}
if (!Version.TryParse(Regex.Replace(GameVer, @"[^\d.].*$", ""), out version2))
{
version2 = new Version();
}
if (version2.CompareTo(version1) > 0)
{
GameRevision = GameVer;
for (int i = 0; i < NACP.NACP_Strings.Length; i++)
{
NACP.NACP_Strings[i] = new NACP.NACP_String(source.Skip(i * 0x300).Take(0x300).ToArray());
if (NACP.NACP_Strings[i].Check == 0 || CB_RegionName.Items.Contains(Language[i]))
{
continue;
}
CB_RegionName.Items.Add(Language[i]);
string icon_filename = Path.Join("data", $"icon_{Language[i].Replace(" ", "")}.dat");
if (File.Exists(icon_filename))
{
using Bitmap original = new(icon_filename);
Icons[i] = new Bitmap(original);
PB_GameIcon.BackgroundImage = Icons[i];
}
}
TB_ProdCode.Text = NACP.NACP_Datas[0].GameProd;
if (TB_ProdCode.Text == "")
{
TB_ProdCode.Text = "No Prod. ID";
}
try
{
File.Delete("meta");
Directory.Delete("data", true);
}
catch
{
}
}
}
string gameVer = NACP.NACP_Datas[0].GameVer.Replace("\0", "");
string cache = $"GENERAL:{Environment.NewLine}{gameVer}{((patchflag == 1) ? $" ({patchver})" : "")}{Environment.NewLine}";
if (basenum != 0)
{
cache += $"BASE:{Environment.NewLine}";
for (int i = 0; i < basenum; i++)
{
cache += basetitle[i] + System.Environment.NewLine;
}
}
else
{
cache += $"BASE:{Environment.NewLine}EMPTY{Environment.NewLine}";
}
if (updnum != 0)
{
cache += $"UPD:{Environment.NewLine}";
for (int i = 0; i < updnum; i++)
{
cache += updtitle[i] + Environment.NewLine;
}
}
else
{
cache += $"UPD:{Environment.NewLine}EMPTY{Environment.NewLine}";
}
if (dlcnum != 0)
{
cache += $"DLC:{Environment.NewLine}";
for (int i = 0; i < dlcnum; i++)
{
if (i < dlcnum - 1)
{
cache += dlctitle[i] + Environment.NewLine;
}
else
{
cache += dlctitle[i];
}
}
}
else
{
cache += $"DLC:{Environment.NewLine}EMPTY";
}
TB_GameRev.Text = cache;
label12.Text = $"{basenum} BASE, {updnum} UPD, {dlcnum} DLC";
CB_RegionName.SelectedIndex = 0;
fileStream.Close();
}
else
{
TB_Dev.Text = $"{Mkey} not found";
TB_Name.Text = $"{Mkey} not found";
}
}
private void LoadNCAData()
{
NCA.NCA_Headers[0] = new NCA.NCA_Header(DecryptNCAHeader(gameNcaOffset));
TB_TID.Text = $"0{NCA.NCA_Headers[0].TitleID.ToString("X")}";
TB_SDKVer.Text = $"{NCA.NCA_Headers[0].SDKVersion4}.{NCA.NCA_Headers[0].SDKVersion3}.{NCA.NCA_Headers[0].SDKVersion2}.{NCA.NCA_Headers[0].SDKVersion1}";
TB_MKeyRev.Text = Util.GetMkey(NCA.NCA_Headers[0].MasterKeyRev);
}
//https://stackoverflow.com/questions/311165/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-and-vice-versa
public static string ByteArrayToString(byte[] ba)
{
StringBuilder hex = new(ba.Length * 2 + 2);
hex.Append("0x");
foreach (byte b in ba)
{
hex.AppendFormat("{0:x2}", b);
}
return hex.ToString();
}
public static string SHA256Bytes(byte[] ba)
{
SHA256 mySHA256 = SHA256.Create();
byte[] hashValue;
hashValue = mySHA256.ComputeHash(ba);
return ByteArrayToString(hashValue);
}
public bool isTrimmed() => TB_ROMExactSize.Text == TB_ExactUsedSpace.Text;
private void LoadPartitions()
{
string actualHash;
byte[] hashBuffer;
long offset;
TV_Partitions.Nodes.Clear();
TV_Parti = new TreeViewFileSystem(TV_Partitions);
rootNode = new BetterTreeNode("root")
{
Offset = -1L,
Size = -1L
};
TV_Partitions.Nodes.Add(rootNode);
bool LogoPartition = false;
FileStream fileStream = new(TB_File.Text, FileMode.Open, FileAccess.Read);
HFS0.HSF0_Entry[] array = new HFS0.HSF0_Entry[HFS0.HFS0_Headers[0].FileCount];
fileStream.Position = XCI.XCI_Headers[0].HFS0OffsetPartition + 16 + 64 * HFS0.HFS0_Headers[0].FileCount;
long num = XCI.XCI_Headers[0].HFS0OffsetPartition + XCI.XCI_Headers[0].HFS0SizeParition;
byte[] array2 = new byte[64];
byte[] array3 = new byte[16];
byte[] array4 = new byte[24];
for (int i = 0; i < HFS0.HFS0_Headers[0].FileCount; i++)
{
fileStream.Position = XCI.XCI_Headers[0].HFS0OffsetPartition + 16 + 64 * i;
fileStream.Read(array2, 0, 64);
array[i] = new HFS0.HSF0_Entry(array2);
fileStream.Position = XCI.XCI_Headers[0].HFS0OffsetPartition + 16 + 64 * HFS0.HFS0_Headers[0].FileCount + array[i].Name_ptr;
int num2;
while ((num2 = fileStream.ReadByte()) != 0 && num2 != 0)
{
chars.Add((char)num2);
}
array[i].Name = new string(chars.ToArray());
chars.Clear();
offset = num + array[i].Offset;
hashBuffer = new byte[array[i].HashedRegionSize];
fileStream.Position = offset;
fileStream.Read(hashBuffer, 0, array[i].HashedRegionSize);
actualHash = SHA256Bytes(hashBuffer);
TV_Parti.AddFile($"{array[i].Name}.hfs0", rootNode, offset, array[i].Size, array[i].HashedRegionSize, ByteArrayToString(array[i].Hash), actualHash);
BetterTreeNode betterTreeNode = TV_Parti.AddDir(array[i].Name, rootNode);
HFS0.HFS0_Header[] array5 = new HFS0.HFS0_Header[1];
fileStream.Position = array[i].Offset + num;
fileStream.Read(array3, 0, 16);
array5[0] = new HFS0.HFS0_Header(array3);
if (array[i].Name == "secure")
{
SecureSize = new long[array5[0].FileCount];
SecureOffset = new long[array5[0].FileCount];
SecureName = new string[array5[0].FileCount];
}
if (array[i].Name == "normal")
{
NormalSize = new long[array5[0].FileCount];
NormalOffset = new long[array5[0].FileCount];
}
if (array[i].Name == "logo")
{
if (array5[0].FileCount > 0)
{
LogoPartition = true;
}
}
HFS0.HSF0_Entry[] array6 = new HFS0.HSF0_Entry[array5[0].FileCount];
for (int j = 0; j < array5[0].FileCount; j++)
{
fileStream.Position = array[i].Offset + num + 16 + 64 * j;
fileStream.Read(array2, 0, 64);
array6[j] = new HFS0.HSF0_Entry(array2);
fileStream.Position = array[i].Offset + num + 16 + 64 * array5[0].FileCount + array6[j].Name_ptr;
while ((num2 = fileStream.ReadByte()) != 0 && num2 != 0)
{
chars.Add((char)num2);
}
array6[j].Name = new string(chars.ToArray());
chars.Clear();
if (array[i].Name == "secure")
{
SecureSize[j] = array6[j].Size;
SecureOffset[j] = array[i].Offset + array6[j].Offset + num + 16 + array5[0].StringTableSize + array5[0].FileCount * 64;
SecureName[j] = array6[j].Name;
}
if (array[i].Name == "normal")
{
NormalSize[j] = array6[j].Size;
NormalOffset[j] = array[i].Offset + array6[j].Offset + num + 16 + array5[0].StringTableSize + array5[0].FileCount * 64;
}
offset = array[i].Offset + array6[j].Offset + num + 16 + array5[0].StringTableSize + array5[0].FileCount * 64;
hashBuffer = new byte[array6[j].HashedRegionSize];
fileStream.Position = offset;
fileStream.Read(hashBuffer, 0, array6[j].HashedRegionSize);
actualHash = SHA256Bytes(hashBuffer);
TV_Parti.AddFile(array6[j].Name, betterTreeNode, offset, array6[j].Size, array6[j].HashedRegionSize, ByteArrayToString(array6[j].Hash), actualHash);
TreeNode[] array7 = TV_Partitions.Nodes.Find(betterTreeNode.Text, true);
if (array7.Length != 0)
{
TV_Parti.AddFile(array6[j].Name, (BetterTreeNode)array7[0], 0L, 0L);
}
}
}
long num3 = -9223372036854775808L;
for (int k = 0; k < SecureSize.Length; k++)
{
if (SecureSize[k] > num3)
{
gameNcaSize = SecureSize[k];
gameNcaOffset = SecureOffset[k];
num3 = SecureSize[k];
}
}
PFS0Offset = gameNcaOffset + 32768;
fileStream.Position = PFS0Offset;
fileStream.Read(array3, 0, 16);
PFS0.PFS0_Headers[0] = new(array3);
if (PFS0.PFS0_Headers[0].FileCount == 2 || !LogoPartition)
{
PFS0.PFS0_Entry[] array8;
try
{
array8 = new PFS0.PFS0_Entry[PFS0.PFS0_Headers[0].FileCount];
}
catch (Exception ex)
{
array8 = new PFS0.PFS0_Entry[0];
Debug.WriteLine($"Partitions Error: {ex.Message}");
}
for (int m = 0; m < PFS0.PFS0_Headers[0].FileCount; m++)
{
fileStream.Position = PFS0Offset + 16 + 24 * m;
fileStream.Read(array4, 0, 24);
array8[m] = new(array4);
PFS0Size += array8[m].Size;
}
TV_Parti.AddFile("boot.psf0", rootNode, PFS0Offset, 16 + 24 * PFS0.PFS0_Headers[0].FileCount + 64 + PFS0Size);
BetterTreeNode betterTreeNode2 = TV_Parti.AddDir("boot", rootNode);
for (int n = 0; n < PFS0.PFS0_Headers[0].FileCount; n++)
{
fileStream.Position = PFS0Offset + 16 + 24 * PFS0.PFS0_Headers[0].FileCount + array8[n].Name_ptr;
int num4;
while ((num4 = fileStream.ReadByte()) != 0 && num4 != 0)
{
chars.Add((char)num4);
}
array8[n].Name = new string(chars.ToArray());
chars.Clear();
TV_Parti.AddFile(array8[n].Name, betterTreeNode2, PFS0Offset + array8[n].Offset + 16 + PFS0.PFS0_Headers[0].StringTableSize + PFS0.PFS0_Headers[0].FileCount * 24, array8[n].Size);
TreeNode[] array9 = TV_Partitions.Nodes.Find(betterTreeNode2.Text, true);
if (array9.Length != 0)
{
TV_Parti.AddFile(array8[n].Name, (BetterTreeNode)array9[0], 0L, 0L);
}
}
}
fileStream.Close();
}
private void LoadNSPPartitions()
{
long offset;
TV_Partitions.Nodes.Clear();
TV_Parti = new TreeViewFileSystem(TV_Partitions);
rootNode = new BetterTreeNode("root")
{
Offset = -1L,
Size = -1L
};
TV_Partitions.Nodes.Add(rootNode);
// Maximum number of files in NSP to read in
const int MAXFILES = 250;
FileStream fileStream = File.OpenRead(TB_File.Text);
List<char> chars = new();
byte[] array = new byte[16];
byte[] array2 = new byte[24];
fileStream.Read(array, 0, 16);
PFS0.PFS0_Headers[0] = new(array);
if (!PFS0.PFS0_Headers[0].Magic.Contains("PFS0"))
{
return;
}
PFS0.PFS0_Entry[] array3;
array3 = new PFS0.PFS0_Entry[Math.Max(PFS0.PFS0_Headers[0].FileCount, MAXFILES)]; //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files
for (int m = 0; m < PFS0.PFS0_Headers[0].FileCount; m++)
{
fileStream.Position = 16 + 24 * m;
fileStream.Read(array2, 0, 24);
array3[m] = new(array2);
if (m == MAXFILES - 1) //Dump of TitleID 01009AA000FAA000 reports more than 10000000 files here, so it breaks the program. Standard is to have only 20 files
{
break;
}
}
for (int n = 0; n < PFS0.PFS0_Headers[0].FileCount; n++)
{
fileStream.Position = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + array3[n].Name_ptr;
int num4;
while ((num4 = fileStream.ReadByte()) != 0 && num4 != 0)
{
chars.Add((char)num4);
}
array3[n].Name = new(chars.ToArray());
chars.Clear();
offset = 16 + 24 * PFS0.PFS0_Headers[0].FileCount + PFS0.PFS0_Headers[0].StringTableSize + array3[n].Offset;
fileStream.Position = offset;
TV_Parti.AddFile(array3[n].Name, rootNode, offset, array3[n].Size);
}
fileStream.Close();
}
private void TV_Partitions_AfterSelect(object sender, TreeViewEventArgs e)
{
BetterTreeNode betterTreeNode = (BetterTreeNode)TV_Partitions.SelectedNode;
if (betterTreeNode.Offset == -1)
{
LB_SelectedData.Text = "";
LB_DataOffset.Text = "";
LB_DataSize.Text = "";
LB_HashedRegionSize.Text = "";
LB_ExpectedHash.Text = "";
LB_ActualHash.Text = "";
B_Extract.Enabled = false;
return;
}
selectedOffset = betterTreeNode.Offset;
selectedSize = betterTreeNode.Size;
string expectedHash = betterTreeNode.ExpectedHash;
string actualHash = betterTreeNode.ActualHash;
long HashedRegionSize = betterTreeNode.HashedRegionSize;
LB_DataOffset.Text = $"Offset: 0x{selectedOffset:X}";
LB_SelectedData.Text = e.Node.Text;
if (backgroundWorker1.IsBusy != true)
{
B_Extract.Enabled = true;
}
string[] array = new string[5]
{
"B",
"KB",
"MB",
"GB",
"TB"
};
double num = selectedSize;
int num2 = 0;
while (num >= 1024.0 && num2 < array.Length - 1)
{
num2++;
num /= 1024.0;
}
LB_DataSize.Text = $"Size: 0x{selectedSize.ToString("X")} ({num}{array[num2]})";
if (HashedRegionSize != 0)
{
LB_HashedRegionSize.Text = $"HashedRegionSize: 0x{HashedRegionSize:X}";
}
else
{
LB_HashedRegionSize.Text = "";
}
if (!string.IsNullOrEmpty(expectedHash))
{
LB_ExpectedHash.Text = $"Header Hash: {expectedHash[..32]}";
}
else
{
LB_ExpectedHash.Text = "";
}
if (!string.IsNullOrEmpty(actualHash))
{
LB_ActualHash.Text = $"Actual Hash: {actualHash[..32]}";
if (actualHash == expectedHash)
{
LB_ActualHash.ForeColor = Color.Green;
}
else
{
LB_ActualHash.ForeColor = Color.Red;
}
}
else
{
LB_ActualHash.Text = "";
}
}
public bool CheckXCI()
{
FileStream fileStream = new(TB_File.Text, FileMode.Open, FileAccess.Read);
byte[] array = new byte[61440];
byte[] array2 = new byte[16];
fileStream.Read(array, 0, 61440);
XCI.XCI_Headers[0] = new XCI.XCI_Header(array);
if (!XCI.XCI_Headers[0].Magic.Contains("HEAD"))
{
return false;
}
fileStream.Position = XCI.XCI_Headers[0].HFS0OffsetPartition;
fileStream.Read(array2, 0, 16);
HFS0.HFS0_Headers[0] = new HFS0.HFS0_Header(array2);
fileStream.Close();
return true;
}
public bool CheckNSP()
{
FileStream fileStream = File.OpenRead(TB_File.Text);
byte[] array = new byte[16];
fileStream.Read(array, 0, 16);
PFS0.PFS0_Headers[0] = new(array);
fileStream.Close();
if (!PFS0.PFS0_Headers[0].Magic.Contains("PFS0"))
{
return false;
}
return true;
}
private void B_ExportCert_Click(object sender, EventArgs e)
{
new CenterWinDialog(this);
if (!File.Exists(TB_File.Text))
{
MessageBox.Show("File not found");
return;
}
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "gamecard_cert.dat (*.dat)|*.dat";
saveFileDialog.FileName = Path.GetFileName("gamecard_cert.dat");
if (saveFileDialog.ShowDialog() != DialogResult.OK)
{
return;
}
FileStream fileStream = new(TB_File.Text, FileMode.Open, FileAccess.Read);
byte[] array = new byte[512];
fileStream.Position = 28672L;
fileStream.Read(array, 0, 512);
File.WriteAllBytes(saveFileDialog.FileName, array);
fileStream.Close();
MessageBox.Show($"Cert successfully exported to:\n\n{saveFileDialog.FileName}");
}
private void B_ImportCert_Click(object sender, EventArgs e)
{
new CenterWinDialog(this);
if (!File.Exists(TB_File.Text))
{
MessageBox.Show("File not found");
return;
}
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "gamecard_cert (*.dat)|*.dat|All files (*.*)|*.*";
if (openFileDialog.ShowDialog() == DialogResult.OK && new FileInfo(openFileDialog.FileName).Length == 512)
{
using (Stream stream = File.Open(TB_File.Text, FileMode.Open))
{
stream.Position = 28672L;
stream.Write(File.ReadAllBytes(openFileDialog.FileName), 0, 512);
}
MessageBox.Show($"Cert successfully imported from:\n\n{openFileDialog.FileName}");
}
}
private void B_ViewCert_Click(object sender, EventArgs e)
{
new CenterWinDialog(this);
if (!File.Exists(TB_File.Text))
{
MessageBox.Show("File not found");
return;
}
CertForm cert = new(this)
{
Text = $"Cert Data - {TB_File.Text}"
};
cert.Show();
}
private void B_ClearCert_Click(object sender, EventArgs e)
{
new CenterWinDialog(this);
if (!File.Exists(TB_File.Text))
{
MessageBox.Show("File not found");
return;
}
if (MessageBox.Show("The cert will be deleted permanently.\nContinue?", "XCI Explorer", MessageBoxButtons.YesNo) != DialogResult.Yes)
{
return;
}
using Stream stream = File.Open(TB_File.Text, FileMode.Open);
byte[] array = new byte[512];
for (int i = 0; i < array.Length; i++)
{
array[i] = byte.MaxValue;
}
stream.Position = 28672L;
stream.Write(array, 0, array.Length);
new CenterWinDialog(this);
MessageBox.Show("Cert deleted.");
}
private void B_Extract_Click(object sender, EventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog
{
FileName = LB_SelectedData.Text
};
if (saveFileDialog.ShowDialog() != DialogResult.OK)
{
return;
}
if (backgroundWorker1.IsBusy)
{
return;
}
B_Extract.Enabled = false;
B_LoadROM.Enabled = false;
B_TrimXCI.Enabled = false;
B_ImportCert.Enabled = false;
B_ClearCert.Enabled = false;
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync(saveFileDialog.FileName);
}
public byte[] DecryptNCAHeader(long offset)
{
byte[] array = new byte[3072];
if (!File.Exists(TB_File.Text))
{
return array;
}
FileStream fileStream = new(TB_File.Text, FileMode.Open, FileAccess.Read)
{
Position = offset
};
fileStream.Read(array, 0, 3072);
File.WriteAllBytes($"{TB_File.Text}.tmp", array);
Xts xts = XtsAes128.Create(NcaHeaderEncryptionKey1_Prod, NcaHeaderEncryptionKey2_Prod);
using (BinaryReader binaryReader = new(File.OpenRead($"{TB_File.Text}.tmp")))
{
using XtsStream xtsStream = new(binaryReader.BaseStream, xts, 512);
xtsStream.Read(array, 0, 3072);
}
File.Delete($"{TB_File.Text}.tmp");
fileStream.Close();
return array;
}
private void CB_RegionName_SelectedIndexChanged(object sender, EventArgs e)
{
int num = Array.FindIndex(Language, (string element) => element.StartsWith(CB_RegionName.Text, StringComparison.Ordinal));
// Icons for 1-2 Switch in some languages are "missing"
// This just shows the first real icon instead of a blank
if (Icons[num] != null)
{
PB_GameIcon.BackgroundImage = Icons[num];
}
else
{
for (int i = 0; i < CB_RegionName.Items.Count; i++)
{
if (Icons[i] == null)
{
continue;
}
PB_GameIcon.BackgroundImage = Icons[i];
break;
}
}
TB_Name.Text = NACP.NACP_Strings[num].GameName;
TB_Dev.Text = NACP.NACP_Strings[num].GameAuthor;
}
private void B_TrimXCI_Click(object sender, EventArgs e)
{
new CenterWinDialog(this);
if (!File.Exists(TB_File.Text))
{
MessageBox.Show("File not found");
return;
}
if (MessageBox.Show("Trim XCI?", "XCI Explorer", MessageBoxButtons.YesNo) != DialogResult.Yes)
{
return;
}
new CenterWinDialog(this);
if (isTrimmed())
{
return;
}
FileStream fileStream = new(TB_File.Text, FileMode.Open, FileAccess.Write);
fileStream.SetLength((long)UsedSize);
fileStream.Close();
B_TrimXCI.Enabled = false;
MessageBox.Show("Done.");
string[] array = new string[5]
{
"B",
"KB",
"MB",
"GB",
"TB"
};
double num = new FileInfo(TB_File.Text).Length;
TB_ROMExactSize.Text = $"({num} bytes)";
int num2 = 0;
while (num >= 1024.0 && num2 < array.Length - 1)
{
num2++;
num /= 1024.0;
}
TB_ROMSize.Text = $"{num:0.##} {array[num2]}";
double num3 = UsedSize = XCI.XCI_Headers[0].CardSize2 * 512 + 512;
TB_ExactUsedSpace.Text = $"({num3} bytes)";
num2 = 0;
while (num3 >= 1024.0 && num2 < array.Length - 1)
{
num2++;
num3 /= 1024.0;
}
TB_UsedSpace.Text = $"{num3:0.##} {array[num2]}";
}
private void LB_ExpectedHash_DoubleClick(object sender, EventArgs e)
{
BetterTreeNode betterTreeNode = (BetterTreeNode)TV_Partitions.SelectedNode;
if (betterTreeNode.Offset != -1)
{
Clipboard.SetText(betterTreeNode.ExpectedHash);
}
}
private void LB_ActualHash_DoubleClick(object sender, EventArgs e)
{
BetterTreeNode betterTreeNode = (BetterTreeNode)TV_Partitions.SelectedNode;
if (betterTreeNode.Offset != -1)
{
Clipboard.SetText(betterTreeNode.ActualHash);
}
}
private void TB_File_DragDrop(object sender, DragEventArgs e)
{
if (backgroundWorker1.IsBusy != true)
{
string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];
TB_File.Text = files[0];
ProcessFile();
}
}
private void TB_File_DragEnter(object sender, DragEventArgs e)
{
if (backgroundWorker1.IsBusy)
{
return;
}
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}
private void TABP_XCI_DragDrop(object sender, DragEventArgs e)
{
if (backgroundWorker1.IsBusy)
{
return;
}
string[] files = e.Data.GetData(DataFormats.FileDrop) as string[];
TB_File.Text = files[0];
ProcessFile();
}
private void TABP_XCI_DragEnter(object sender, DragEventArgs e)
{
if (backgroundWorker1.IsBusy)
{
return;
}
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
string fileName = (string)e.Argument;
using FileStream fileStream = File.OpenRead(TB_File.Text);
using FileStream fileStream2 = File.OpenWrite(fileName);
new BinaryReader(fileStream);
new BinaryWriter(fileStream2);
fileStream.Position = selectedOffset;
long num = selectedSize;
if (selectedSize < 10000)
{
byte[] buffer = new byte[1];
int num2;
while ((num2 = fileStream.Read(buffer, 0, 1)) > 0 && num > 0)
{
fileStream2.Write(buffer, 0, num2);
num -= num2;
}
}
else
{
byte[] buffer = new byte[8192];
int num2;
while ((num2 = fileStream.Read(buffer, 0, 8192)) > 0 && num > 0)
{
fileStream2.Write(buffer, 0, num2);
num -= num2;
}
}
fileStream.Close();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
new CenterWinDialog(this);
B_Extract.Enabled = true;
B_LoadROM.Enabled = true;
B_TrimXCI.Enabled = true;
B_ImportCert.Enabled = true;
B_ClearCert.Enabled = true;
if (e.Error != null)
{
MessageBox.Show($"Error: {e.Error.Message}");
}
else
{
MessageBox.Show("Done extracting NCA!");
}
}
private void TABP_XCI_Click(object sender, EventArgs e)
{
}
private void label11_Click(object sender, EventArgs e)
{
}
}