diff --git a/NUS Downloader/CertificateChain.cs b/NUS Downloader/CertificateChain.cs
new file mode 100644
index 0000000..d9f7145
--- /dev/null
+++ b/NUS Downloader/CertificateChain.cs
@@ -0,0 +1,494 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace libWiiSharp
+{
+ public class CertificateChain : IDisposable
+ {
+ private const string certCaHash = "5B7D3EE28706AD8DA2CBD5A6B75C15D0F9B6F318";
+ private const string certCpHash = "6824D6DA4C25184F0D6DAF6EDB9C0FC57522A41C";
+ private const string certXsHash = "09787045037121477824BC6A3E5E076156573F8A";
+ private SHA1 sha = SHA1.Create();
+ private bool[] certsComplete = new bool[3];
+
+ private byte[] certCa = new byte[0x400];
+ private byte[] certCp = new byte[0x300];
+ private byte[] certXs = new byte[0x300];
+
+ ///
+ /// If false, the Certificate Chain is not complete (i.e. at least one certificate is missing).
+ ///
+ public bool CertsComplete { get { return (certsComplete[0] && certsComplete[1] && certsComplete[2]); } }
+
+ #region IDisposable Members
+ private bool isDisposed = false;
+
+ ~CertificateChain()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && !isDisposed)
+ {
+ sha.Clear();
+ sha = null;
+
+ certsComplete = null;
+ certCa = null;
+ certCp = null;
+ certXs = null;
+ }
+
+ isDisposed = true;
+ }
+ #endregion
+
+ #region Public Functions
+ ///
+ /// Loads a cert file.
+ ///
+ ///
+ ///
+ public static CertificateChain Load(string pathToCert)
+ {
+ return Load(File.ReadAllBytes(pathToCert));
+ }
+
+ ///
+ /// Loads a cert file.
+ ///
+ ///
+ ///
+ public static CertificateChain Load(byte[] certFile)
+ {
+ CertificateChain c = new CertificateChain();
+ MemoryStream ms = new MemoryStream(certFile);
+
+ try { c.parseCert(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ return c;
+ }
+
+ ///
+ /// Loads a cert file.
+ ///
+ ///
+ ///
+ public static CertificateChain Load(Stream cert)
+ {
+ CertificateChain c = new CertificateChain();
+ c.parseCert(cert);
+ return c;
+ }
+
+ ///
+ /// Grabs certificates from Ticket and Tmd.
+ /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
+ ///
+ ///
+ ///
+ ///
+ public static CertificateChain FromTikTmd(string pathToTik, string pathToTmd)
+ {
+ return FromTikTmd(File.ReadAllBytes(pathToTik), File.ReadAllBytes(pathToTmd));
+ }
+
+ ///
+ /// Grabs certificates from Ticket and Tmd.
+ /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
+ ///
+ ///
+ ///
+ ///
+ public static CertificateChain FromTikTmd(byte[] tikFile, byte[] tmdFile)
+ {
+ CertificateChain c = new CertificateChain();
+ MemoryStream ms = new MemoryStream(tikFile);
+
+ try { c.grabFromTik(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms = new MemoryStream(tmdFile);
+
+ try { c.grabFromTmd(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+
+ if (!c.CertsComplete) throw new Exception("Couldn't locate all certs!");
+
+ return c;
+ }
+
+ ///
+ /// Grabs certificates from Ticket and Tmd.
+ /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
+ ///
+ ///
+ ///
+ ///
+ public static CertificateChain FromTikTmd(Stream tik, Stream tmd)
+ {
+ CertificateChain c = new CertificateChain();
+ c.grabFromTik(tik);
+ c.grabFromTmd(tmd);
+ return c;
+ }
+
+
+
+ ///
+ /// Loads a cert file.
+ ///
+ ///
+ public void LoadFile(string pathToCert)
+ {
+ LoadFile(File.ReadAllBytes(pathToCert));
+ }
+
+ ///
+ /// Loads a cert file.
+ ///
+ ///
+ public void LoadFile(byte[] certFile)
+ {
+ MemoryStream ms = new MemoryStream(certFile);
+
+ try { parseCert(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ }
+
+ ///
+ /// Loads a cert file.
+ ///
+ ///
+ public void LoadFile(Stream cert)
+ {
+ parseCert(cert);
+ }
+
+ ///
+ /// Grabs certificates from Ticket and Tmd.
+ /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
+ ///
+ ///
+ ///
+ ///
+ public void LoadFromTikTmd(string pathToTik, string pathToTmd)
+ {
+ LoadFromTikTmd(File.ReadAllBytes(pathToTik), File.ReadAllBytes(pathToTmd));
+ }
+
+ ///
+ /// Grabs certificates from Ticket and Tmd.
+ /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
+ ///
+ ///
+ ///
+ public void LoadFromTikTmd(byte[] tikFile, byte[] tmdFile)
+ {
+ MemoryStream ms = new MemoryStream(tikFile);
+
+ try { grabFromTik(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms = new MemoryStream(tmdFile);
+
+ try { grabFromTmd(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+
+ if (!CertsComplete) throw new Exception("Couldn't locate all certs!");
+ }
+
+ ///
+ /// Grabs certificates from Ticket and Tmd.
+ /// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
+ ///
+ ///
+ ///
+ public void LoadFromTikTmd(Stream tik, Stream tmd)
+ {
+ grabFromTik(tik);
+ grabFromTmd(tmd);
+ }
+
+
+
+ ///
+ /// Saves the Certificate Chain.
+ ///
+ ///
+ public void Save(string savePath)
+ {
+ if (File.Exists(savePath)) File.Delete(savePath);
+
+ using (FileStream fs = new FileStream(savePath, FileMode.Create))
+ writeToStream(fs);
+ }
+
+ ///
+ /// Returns the Certificate Chain as a memory stream.
+ ///
+ ///
+ public MemoryStream ToMemoryStream()
+ {
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ return ms;
+ }
+
+ ///
+ /// Returns the Certificate Chain as a byte array.
+ ///
+ ///
+ public byte[] ToByteArray()
+ {
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ byte[] res = ms.ToArray();
+ ms.Dispose();
+ return res;
+ }
+ #endregion
+
+ #region Private Functions
+ private void writeToStream(Stream writeStream)
+ {
+ fireDebug("Writing Certificate Chain...");
+
+ if (!CertsComplete)
+ { fireDebug(" Certificate Chain incomplete..."); throw new Exception("At least one certificate is missing!"); }
+
+ writeStream.Seek(0, SeekOrigin.Begin);
+
+ fireDebug(" Writing Certificate CA... (Offset: 0x{0})", writeStream.Position.ToString("x8"));
+ writeStream.Write(certCa, 0, certCa.Length);
+
+ fireDebug(" Writing Certificate CP... (Offset: 0x{0})", writeStream.Position.ToString("x8"));
+ writeStream.Write(certCp, 0, certCp.Length);
+
+ fireDebug(" Writing Certificate XS... (Offset: 0x{0})", writeStream.Position.ToString("x8"));
+ writeStream.Write(certXs, 0, certXs.Length);
+
+ fireDebug("Writing Certificate Chain Finished...");
+ }
+
+ private void parseCert(Stream certFile)
+ {
+ fireDebug("Parsing Certificate Chain...");
+ int off = 0;
+
+ for (int i = 0; i < 3; i++)
+ {
+ fireDebug(" Scanning at Offset 0x{0}:", off.ToString("x8"));
+
+ try
+ {
+ certFile.Seek(off, SeekOrigin.Begin);
+ byte[] temp = new byte[0x400];
+
+ certFile.Read(temp, 0, temp.Length);
+
+ fireDebug(" Checking for Certificate CA...");
+ if (isCertCa(temp) && !certsComplete[1])
+ { fireDebug(" Certificate CA detected..."); certCa = temp; certsComplete[1] = true; off += 0x400; continue; }
+
+ fireDebug(" Checking for Certificate CP...");
+ if (isCertCp(temp) && !certsComplete[2])
+ { fireDebug(" Certificate CP detected..."); Array.Resize(ref temp, 0x300); certCp = temp; certsComplete[2] = true; off += 0x300; continue; }
+
+ fireDebug(" Checking for Certificate XS...");
+ if (isCertXs(temp) && !certsComplete[0])
+ { fireDebug(" Certificate XS detected..."); Array.Resize(ref temp, 0x300); certXs = temp; certsComplete[0] = true; off += 0x300; continue; }
+ }
+ catch (Exception ex) { fireDebug("Error: {0}", ex.Message); }
+
+ off += 0x300;
+ }
+
+ if (!CertsComplete)
+ { fireDebug(" Couldn't locate all Certificates..."); throw new Exception("Couldn't locate all certs!"); }
+
+ fireDebug("Parsing Certificate Chain Finished...");
+ }
+
+ private void grabFromTik(Stream tik)
+ {
+ fireDebug("Scanning Ticket for Certificates...");
+ int off = 676;
+
+ for (int i = 0; i < 3; i++)
+ {
+ fireDebug(" Scanning at Offset 0x{0}:", off.ToString("x8"));
+
+ try
+ {
+ tik.Seek(off, SeekOrigin.Begin);
+ byte[] temp = new byte[0x400];
+
+ tik.Read(temp, 0, temp.Length);
+
+ fireDebug(" Checking for Certificate CA...");
+ if (isCertCa(temp) && !certsComplete[1])
+ { fireDebug(" Certificate CA detected..."); certCa = temp; certsComplete[1] = true; off += 0x400; continue; }
+
+ fireDebug(" Checking for Certificate CP...");
+ if (isCertCp(temp) && !certsComplete[2])
+ { fireDebug(" Certificate CP detected..."); Array.Resize(ref temp, 0x300); certCp = temp; certsComplete[2] = true; off += 0x300; continue; }
+
+ fireDebug(" Checking for Certificate XS...");
+ if (isCertXs(temp) && !certsComplete[0])
+ { fireDebug(" Certificate XS detected..."); Array.Resize(ref temp, 0x300); certXs = temp; certsComplete[0] = true; off += 0x300; continue; }
+ }
+ catch { }
+
+ off += 0x300;
+ }
+
+ fireDebug("Scanning Ticket for Certificates Finished...");
+ }
+
+ private void grabFromTmd(Stream tmd)
+ {
+ fireDebug("Scanning TMD for Certificates...");
+
+ byte[] temp = new byte[2];
+ tmd.Seek(478, SeekOrigin.Begin);
+ tmd.Read(temp, 0, 2);
+
+ int numContents = Shared.Swap(BitConverter.ToUInt16(temp, 0));
+ int off = 484 + numContents * 36;
+
+ for (int i = 0; i < 3; i++)
+ {
+ fireDebug(" Scanning at Offset 0x{0}:", off.ToString("x8"));
+
+ try
+ {
+ tmd.Seek(off, SeekOrigin.Begin);
+ temp = new byte[0x400];
+
+ tmd.Read(temp, 0, temp.Length);
+
+ fireDebug(" Checking for Certificate CA...");
+ if (isCertCa(temp) && !certsComplete[1])
+ { fireDebug(" Certificate CA detected..."); certCa = temp; certsComplete[1] = true; off += 0x400; continue; }
+
+ fireDebug(" Checking for Certificate CP...");
+ if (isCertCp(temp) && !certsComplete[2])
+ { fireDebug(" Certificate CP detected..."); Array.Resize(ref temp, 0x300); certCp = temp; certsComplete[2] = true; off += 0x300; continue; }
+
+ fireDebug(" Checking for Certificate XS...");
+ if (isCertXs(temp) && !certsComplete[0])
+ { fireDebug(" Certificate XS detected..."); Array.Resize(ref temp, 0x300); certXs = temp; certsComplete[0] = true; off += 0x300; continue; }
+ }
+ catch { }
+
+ off += 0x300;
+ }
+
+ fireDebug("Scanning TMD for Certificates Finished...");
+ }
+
+ private bool isCertXs(byte[] part)
+ {
+ if (part.Length < 0x300) return false;
+ else if (part.Length > 0x300) Array.Resize(ref part, 0x300);
+
+ if (part[0x184] == 'X' && part[0x185] == 'S')
+ {
+ byte[] newHash = sha.ComputeHash(part);
+ byte[] oldHash = Shared.HexStringToByteArray(certXsHash);
+
+ if (Shared.CompareByteArrays(newHash, oldHash)) return true;
+ }
+
+ return false;
+ }
+
+ private bool isCertCa(byte[] part)
+ {
+ if (part.Length < 0x400) return false;
+ else if (part.Length > 0x400) Array.Resize(ref part, 0x400);
+
+ if (part[0x284] == 'C' && part[0x285] == 'A')
+ {
+ byte[] newHash = sha.ComputeHash(part);
+ byte[] oldHash = Shared.HexStringToByteArray(certCaHash);
+
+ if (Shared.CompareByteArrays(newHash, oldHash)) return true;
+ }
+
+ return false;
+ }
+
+ private bool isCertCp(byte[] part)
+ {
+ if (part.Length < 0x300) return false;
+ else if (part.Length > 0x300) Array.Resize(ref part, 0x300);
+
+ if (part[0x184] == 'C' && part[0x185] == 'P')
+ {
+ byte[] newHash = sha.ComputeHash(part);
+ byte[] oldHash = Shared.HexStringToByteArray(certCpHash);
+
+ if (Shared.CompareByteArrays(newHash, oldHash)) return true;
+ }
+
+ return false;
+ }
+ #endregion
+
+ #region Events
+ ///
+ /// Fires debugging messages. You may write them into a log file or log textbox.
+ ///
+ public event EventHandler Debug;
+
+ private void fireDebug(string debugMessage, params object[] args)
+ {
+ EventHandler debug = Debug;
+ if (debug != null)
+ debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
+ }
+ #endregion
+ }
+}
diff --git a/NUS Downloader/CommonKey.cs b/NUS Downloader/CommonKey.cs
new file mode 100644
index 0000000..fe4dfb9
--- /dev/null
+++ b/NUS Downloader/CommonKey.cs
@@ -0,0 +1,35 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+namespace libWiiSharp
+{
+ public class CommonKey
+ {
+ private static string standardKey = "ebe42a225e8593e448d9c5457381aaf7";
+ private static string koreanKey = "63b82bb4f4614e2e13f2fefbba4c9b7e";
+
+ public static byte[] GetStandardKey()
+ {
+ return Shared.HexStringToByteArray(standardKey);
+ }
+
+ public static byte[] GetKoreanKey()
+ {
+ return Shared.HexStringToByteArray(koreanKey);
+ }
+ }
+}
diff --git a/NUS Downloader/ContentIndices.cs b/NUS Downloader/ContentIndices.cs
new file mode 100644
index 0000000..483d778
--- /dev/null
+++ b/NUS Downloader/ContentIndices.cs
@@ -0,0 +1,44 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace libWiiSharp
+{
+ internal struct ContentIndices : IComparable
+ {
+ private int index;
+ private int contentIndex;
+
+ public int Index { get { return index; } }
+ public int ContentIndex { get { return contentIndex; } }
+
+ public ContentIndices(int index, int contentIndex)
+ {
+ this.index = index;
+ this.contentIndex = contentIndex;
+ }
+
+ public int CompareTo(object obj)
+ {
+ if (obj is ContentIndices) return contentIndex.CompareTo(((ContentIndices)obj).contentIndex);
+ else throw new ArgumentException();
+ }
+ }
+}
diff --git a/NUS Downloader/Form1.Designer.cs b/NUS Downloader/Form1.Designer.cs
index 08a5b83..00e1b27 100644
--- a/NUS Downloader/Form1.Designer.cs
+++ b/NUS Downloader/Form1.Designer.cs
@@ -63,10 +63,6 @@
this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();
this.proxySettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator6 = new System.Windows.Forms.ToolStripSeparator();
- //this.getCommonKeyMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- //this.commonKeykeybinToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- //this.koreanKeykkeybinToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
- this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator();
this.aboutNUSDToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.proxyBox = new System.Windows.Forms.GroupBox();
this.label13 = new System.Windows.Forms.Label();
@@ -339,11 +335,9 @@
this.toolStripSeparator3,
this.proxySettingsToolStripMenuItem,
this.toolStripSeparator6,
- //this.getCommonKeyMenuItem,
- this.toolStripSeparator5,
this.aboutNUSDToolStripMenuItem});
this.extrasStrip.Name = "extrasStrip";
- this.extrasStrip.Size = new System.Drawing.Size(183, 110);
+ this.extrasStrip.Size = new System.Drawing.Size(183, 82);
//
// loadInfoFromTMDToolStripMenuItem
//
@@ -371,35 +365,6 @@
this.toolStripSeparator6.Name = "toolStripSeparator6";
this.toolStripSeparator6.Size = new System.Drawing.Size(179, 6);
//
- // getCommonKeyMenuItem
- //
- /*this.getCommonKeyMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.commonKeykeybinToolStripMenuItem,
- this.koreanKeykkeybinToolStripMenuItem});
- this.getCommonKeyMenuItem.Image = global::NUS_Downloader.Properties.Resources.key;
- this.getCommonKeyMenuItem.Name = "getCommonKeyMenuItem";
- this.getCommonKeyMenuItem.Size = new System.Drawing.Size(182, 22);
- this.getCommonKeyMenuItem.Text = "Retrieve Key";
- //
- // commonKeykeybinToolStripMenuItem
- //
- this.commonKeykeybinToolStripMenuItem.Name = "commonKeykeybinToolStripMenuItem";
- this.commonKeykeybinToolStripMenuItem.Size = new System.Drawing.Size(196, 22);
- this.commonKeykeybinToolStripMenuItem.Text = "Common Key (key.bin)";
- this.commonKeykeybinToolStripMenuItem.Click += new System.EventHandler(this.commonKeykeybinToolStripMenuItem_Click);
- //
- // koreanKeykkeybinToolStripMenuItem
- //
- this.koreanKeykkeybinToolStripMenuItem.Name = "koreanKeykkeybinToolStripMenuItem";
- this.koreanKeykkeybinToolStripMenuItem.Size = new System.Drawing.Size(196, 22);
- this.koreanKeykkeybinToolStripMenuItem.Text = "Korean Key (kkey.bin)";
- this.koreanKeykkeybinToolStripMenuItem.Click += new System.EventHandler(this.koreanKeykkeybinToolStripMenuItem_Click);*/
- //
- // toolStripSeparator5
- //
- this.toolStripSeparator5.Name = "toolStripSeparator5";
- this.toolStripSeparator5.Size = new System.Drawing.Size(179, 6);
- //
// aboutNUSDToolStripMenuItem
//
this.aboutNUSDToolStripMenuItem.Image = global::NUS_Downloader.Properties.Resources.information;
@@ -916,7 +881,6 @@
private System.Windows.Forms.ToolStripMenuItem PALMassUpdate;
private System.Windows.Forms.ToolStripMenuItem NTSCMassUpdate;
private System.Windows.Forms.ToolStripMenuItem KoreaMassUpdate;
- private System.Windows.Forms.ToolStripSeparator toolStripSeparator5;
private System.Windows.Forms.ToolStripMenuItem aboutNUSDToolStripMenuItem;
private System.Windows.Forms.CheckBox checkBox1;
private System.Windows.Forms.Button SaveProxyPwdPermanentBtn;
diff --git a/NUS Downloader/Form1.cs b/NUS Downloader/Form1.cs
index 59523a4..52df8d4 100644
--- a/NUS Downloader/Form1.cs
+++ b/NUS Downloader/Form1.cs
@@ -114,6 +114,7 @@ namespace NUS_Downloader
// Scripts Thread
private BackgroundWorker scriptsWorker;
+ /*
// Common Key hash
private static byte[] wii_commonkey = new byte[16]
{
@@ -124,7 +125,7 @@ namespace NUS_Downloader
{
0x63, 0xb8, 0x2b, 0xb4, 0xf4, 0x61, 0x4e, 0x2e, 0x13, 0xf2, 0xfe, 0xfb, 0xba, 0x4c, 0x9b, 0x7e
};
-
+ */
/*
public struct WADHeader
{
@@ -262,7 +263,7 @@ namespace NUS_Downloader
{
WriteStatus("Korean Common Key detected.");
}*/
- WriteStatus("Common keys are compiled in, key.bin/kkey.bin not read.");
+ //WriteStatus("Common keys are compiled in, key.bin/kkey.bin not read.");
// Check for DSi common key bin file...
if (NUSDFileExists("dsikey.bin") == true)
@@ -1011,15 +1012,15 @@ namespace NUS_Downloader
if (cetkbuf[0x01F1] == 0x01)
{
WriteStatus("Key Type: Korean");
- keyBytes = LoadCommonKey("kkey.bin");
+ keyBytes = libWiiSharp.CommonKey.GetKoreanKey();
}
else
{
WriteStatus("Key Type: Standard");
if (wiimode)
- keyBytes = LoadCommonKey("key.bin");
+ keyBytes = libWiiSharp.CommonKey.GetStandardKey();
else
- keyBytes = LoadCommonKey("dsikey.bin");
+ keyBytes = File.ReadAllBytes(Path.Combine(CURRENT_DIR, "dsikey.bin"));
}
initCrypt(iv, keyBytes);
@@ -1530,6 +1531,7 @@ namespace NUS_Downloader
return sha.ComputeHash(data);
}
+ /*
///
/// Loads the common key from disc.
///
@@ -1552,8 +1554,9 @@ namespace NUS_Downloader
}
else
return null;
- }
+ }*/
+ /*
///
/// Writes/overwrites the common key onto disc.
///
@@ -1579,7 +1582,7 @@ namespace NUS_Downloader
WriteStatus(String.Format("Error: Couldn't write {0}: {1}", keyfile, e.Message));
}
return false;
- }
+ }*/
private void button4_Click(object sender, EventArgs e)
{
diff --git a/NUS Downloader/Headers.cs b/NUS Downloader/Headers.cs
new file mode 100644
index 0000000..7faa41a
--- /dev/null
+++ b/NUS Downloader/Headers.cs
@@ -0,0 +1,747 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace libWiiSharp
+{
+ public class Headers
+ {
+ private static uint imd5Magic = 0x494d4435;
+ private static uint imetMagic = 0x494d4554;
+
+ ///
+ /// Convert HeaderType to int to get it's Length.
+ ///
+ public enum HeaderType
+ {
+ None = 0,
+ ///
+ /// Used in opening.bnr
+ ///
+ ShortIMET = 1536,
+ ///
+ /// Used in 00000000.app
+ ///
+ IMET = 1600,
+ ///
+ /// Used in banner.bin / icon.bin
+ ///
+ IMD5 = 32,
+ }
+
+ #region Public Functions
+ ///
+ /// Checks a file for Headers.
+ ///
+ ///
+ ///
+ public static HeaderType DetectHeader(string pathToFile)
+ {
+ return DetectHeader(File.ReadAllBytes(pathToFile));
+ }
+
+ ///
+ /// Checks the byte array for Headers.
+ ///
+ ///
+ ///
+ public static HeaderType DetectHeader(byte[] file)
+ {
+ if (file.Length > 68)
+ if (Shared.Swap(BitConverter.ToUInt32(file, 64)) == imetMagic)
+ return HeaderType.ShortIMET;
+ if (file.Length > 132)
+ if (Shared.Swap(BitConverter.ToUInt32(file, 128)) == imetMagic)
+ return HeaderType.IMET;
+ if (file.Length > 4)
+ if (Shared.Swap(BitConverter.ToUInt32(file, 0)) == imd5Magic)
+ return HeaderType.IMD5;
+
+ return HeaderType.None;
+ }
+
+ ///
+ /// Checks the stream for Headers.
+ ///
+ ///
+ ///
+ public static HeaderType DetectHeader(Stream file)
+ {
+ byte[] tmp = new byte[4];
+
+ if (file.Length > 68)
+ {
+ file.Seek(64, SeekOrigin.Begin);
+ file.Read(tmp, 0, tmp.Length);
+ if (Shared.Swap(BitConverter.ToUInt32(tmp, 0)) == imetMagic)
+ return HeaderType.ShortIMET;
+ }
+ if (file.Length > 132)
+ {
+ file.Seek(128, SeekOrigin.Begin);
+ file.Read(tmp, 0, tmp.Length);
+ if (Shared.Swap(BitConverter.ToUInt32(tmp, 0)) == imetMagic)
+ return HeaderType.IMET;
+ }
+ if (file.Length > 4)
+ {
+ file.Seek(0, SeekOrigin.Begin);
+ file.Read(tmp, 0, tmp.Length);
+ if (Shared.Swap(BitConverter.ToUInt32(tmp, 0)) == imd5Magic)
+ return HeaderType.IMD5;
+ }
+
+ return HeaderType.None;
+ }
+ #endregion
+
+ public class IMET
+ {
+ private bool hashesMatch = true;
+ private bool isShortImet = false;
+
+ private byte[] additionalPadding = new byte[64];
+ private byte[] padding = new byte[64];
+ private uint imetMagic = 0x494d4554;
+ private uint sizeOfHeader = 0x00000600; //Without additionalPadding
+ private uint unknown = 0x00000003;
+ private uint iconSize;
+ private uint bannerSize;
+ private uint soundSize;
+ private uint flags = 0x00000000;
+ private byte[] japaneseTitle = new byte[84];
+ private byte[] englishTitle = new byte[84];
+ private byte[] germanTitle = new byte[84];
+ private byte[] frenchTitle = new byte[84];
+ private byte[] spanishTitle = new byte[84];
+ private byte[] italianTitle = new byte[84];
+ private byte[] dutchTitle = new byte[84];
+ private byte[] unknownTitle1 = new byte[84];
+ private byte[] unknownTitle2 = new byte[84];
+ private byte[] koreanTitle = new byte[84];
+ private byte[] padding2 = new byte[588];
+ private byte[] hash = new byte[16];
+
+ ///
+ /// Short IMET has a padding of 64 bytes at the beginning while Long IMET has 128.
+ ///
+ public bool IsShortIMET { get { return isShortImet; } set { isShortImet = value; } }
+ ///
+ /// The size of uncompressed icon.bin
+ ///
+ public uint IconSize { get { return iconSize; } set { iconSize = value; } }
+ ///
+ /// The size of uncompressed banner.bin
+ ///
+ public uint BannerSize { get { return bannerSize; } set { bannerSize = value; } }
+ ///
+ /// The size of uncompressed sound.bin
+ ///
+ public uint SoundSize { get { return soundSize; } set { soundSize = value; } }
+ ///
+ /// The japanese Title.
+ ///
+ public string JapaneseTitle { get { return returnTitleAsString(japaneseTitle); } set { setTitleFromString(value, 0); } }
+ ///
+ /// The english Title.
+ ///
+ public string EnglishTitle { get { return returnTitleAsString(englishTitle); } set { setTitleFromString(value, 1); } }
+ ///
+ /// The german Title.
+ ///
+ public string GermanTitle { get { return returnTitleAsString(germanTitle); } set { setTitleFromString(value, 2); } }
+ ///
+ /// The french Title.
+ ///
+ public string FrenchTitle { get { return returnTitleAsString(frenchTitle); } set { setTitleFromString(value, 3); } }
+ ///
+ /// The spanish Title.
+ ///
+ public string SpanishTitle { get { return returnTitleAsString(spanishTitle); } set { setTitleFromString(value, 4); } }
+ ///
+ /// The italian Title.
+ ///
+ public string ItalianTitle { get { return returnTitleAsString(italianTitle); } set { setTitleFromString(value, 5); } }
+ ///
+ /// The dutch Title.
+ ///
+ public string DutchTitle { get { return returnTitleAsString(dutchTitle); } set { setTitleFromString(value, 6); } }
+ ///
+ /// The korean Title.
+ ///
+ public string KoreanTitle { get { return returnTitleAsString(koreanTitle); } set { setTitleFromString(value, 7); } }
+ ///
+ /// All Titles as a string array.
+ ///
+ public string[] AllTitles { get { return new string[] { JapaneseTitle, EnglishTitle, GermanTitle, FrenchTitle, SpanishTitle, ItalianTitle, DutchTitle, KoreanTitle }; } }
+ ///
+ /// When parsing an IMET header, this value will turn false if the hash stored in the header doesn't match the headers hash.
+ ///
+ public bool HashesMatch { get { return hashesMatch; } }
+
+ #region Public Functions
+ ///
+ /// Loads the IMET Header of a file.
+ ///
+ ///
+ ///
+ public static IMET Load(string pathToFile)
+ {
+ return Load(File.ReadAllBytes(pathToFile));
+ }
+
+ ///
+ /// Loads the IMET Header of a byte array.
+ ///
+ ///
+ ///
+ public static IMET Load(byte[] fileOrHeader)
+ {
+ HeaderType type = DetectHeader(fileOrHeader);
+ if (type != HeaderType.IMET && type != HeaderType.ShortIMET)
+ throw new Exception("No IMET Header found!");
+
+ IMET s = new IMET();
+ if (type == HeaderType.ShortIMET) s.isShortImet = true;
+
+ MemoryStream ms = new MemoryStream(fileOrHeader);
+ try { s.parseHeader(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ return s;
+ }
+
+ ///
+ /// Loads the IMET Header of a stream.
+ ///
+ ///
+ ///
+ public static IMET Load(Stream fileOrHeader)
+ {
+ HeaderType type = DetectHeader(fileOrHeader);
+ if (type != HeaderType.IMET && type != HeaderType.ShortIMET)
+ throw new Exception("No IMET Header found!");
+
+ IMET s = new IMET();
+ if (type == HeaderType.ShortIMET) s.isShortImet = true;
+
+ s.parseHeader(fileOrHeader);
+ return s;
+ }
+
+ ///
+ /// Creates a new IMET Header.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IMET Create(bool isShortImet, int iconSize, int bannerSize, int soundSize, params string[] titles)
+ {
+ IMET s = new IMET();
+ s.isShortImet = isShortImet;
+
+ for (int i = 0; i < titles.Length; i++)
+ s.setTitleFromString(titles[i], i);
+
+ for (int i = titles.Length; i < 8; i++)
+ s.setTitleFromString((titles.Length > 1) ? titles[1] : titles[0], i);
+
+ s.iconSize = (uint)iconSize;
+ s.bannerSize = (uint)bannerSize;
+ s.soundSize = (uint)soundSize;
+
+ return s;
+ }
+
+ ///
+ /// Removes the IMET Header of a file.
+ ///
+ ///
+ public static void RemoveHeader(string pathToFile)
+ {
+ byte[] fileWithoutHeader = RemoveHeader(File.ReadAllBytes(pathToFile));
+ File.Delete(pathToFile);
+
+ File.WriteAllBytes(pathToFile, fileWithoutHeader);
+ }
+
+ ///
+ /// Removes the IMET Header of a byte array.
+ ///
+ ///
+ ///
+ public static byte[] RemoveHeader(byte[] file)
+ {
+ HeaderType type = DetectHeader(file);
+ if (type != HeaderType.IMET && type != HeaderType.ShortIMET)
+ throw new Exception("No IMET Header found!");
+
+ byte[] fileWithoutHeader = new byte[file.Length - (int)type];
+ Array.Copy(file, (int)type, fileWithoutHeader, 0, fileWithoutHeader.Length);
+
+ return fileWithoutHeader;
+ }
+
+
+
+ ///
+ /// Sets all title to the given string.
+ ///
+ ///
+ public void SetAllTitles(string newTitle)
+ {
+ for (int i = 0; i < 10; i++)
+ setTitleFromString(newTitle, i);
+ }
+
+ ///
+ /// Returns the Header as a memory stream.
+ ///
+ ///
+ public MemoryStream ToMemoryStream()
+ {
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ return ms;
+ }
+
+ ///
+ /// Returns the Header as a byte array.
+ ///
+ ///
+ public byte[] ToByteArray()
+ {
+ return ToMemoryStream().ToArray();
+ }
+
+ ///
+ /// Writes the Header to the given stream.
+ ///
+ ///
+ public void Write(Stream writeStream)
+ {
+ writeToStream(writeStream);
+ }
+
+ ///
+ /// Changes the Titles.
+ ///
+ ///
+ public void ChangeTitles(params string[] newTitles)
+ {
+ for (int i = 0; i < newTitles.Length; i++)
+ setTitleFromString(newTitles[i], i);
+
+ for (int i = newTitles.Length; i < 8; i++)
+ setTitleFromString((newTitles.Length > 1) ? newTitles[1] : newTitles[0], i);
+ }
+
+ ///
+ /// Returns a string array with the Titles.
+ ///
+ ///
+ public string[] GetTitles()
+ {
+ return new string[] { JapaneseTitle, EnglishTitle, GermanTitle, FrenchTitle, SpanishTitle, ItalianTitle, DutchTitle, KoreanTitle };
+ }
+ #endregion
+
+ #region Private Functions
+ private void writeToStream(Stream writeStream)
+ {
+ writeStream.Seek(0, SeekOrigin.Begin);
+
+ if (!isShortImet) writeStream.Write(additionalPadding, 0, additionalPadding.Length);
+
+ writeStream.Write(padding, 0, padding.Length);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(imetMagic)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(sizeOfHeader)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(unknown)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(iconSize)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(bannerSize)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(soundSize)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(flags)), 0, 4);
+
+ writeStream.Write(japaneseTitle, 0, japaneseTitle.Length);
+ writeStream.Write(englishTitle, 0, englishTitle.Length);
+ writeStream.Write(germanTitle, 0, germanTitle.Length);
+ writeStream.Write(frenchTitle, 0, frenchTitle.Length);
+ writeStream.Write(spanishTitle, 0, spanishTitle.Length);
+ writeStream.Write(italianTitle, 0, italianTitle.Length);
+ writeStream.Write(dutchTitle, 0, dutchTitle.Length);
+ writeStream.Write(unknownTitle1, 0, unknownTitle1.Length);
+ writeStream.Write(unknownTitle2, 0, unknownTitle2.Length);
+ writeStream.Write(koreanTitle, 0, koreanTitle.Length);
+ writeStream.Write(padding2, 0, padding2.Length);
+
+ int hashPos = (int)writeStream.Position;
+ hash = new byte[16];
+ writeStream.Write(hash, 0, hash.Length);
+
+ byte[] toHash = new byte[writeStream.Position];
+ writeStream.Seek(0, SeekOrigin.Begin);
+ writeStream.Read(toHash, 0, toHash.Length);
+
+ computeHash(toHash, isShortImet ? 0 : 0x40);
+
+ writeStream.Seek(hashPos, SeekOrigin.Begin);
+ writeStream.Write(hash, 0, hash.Length);
+ }
+
+ private void computeHash(byte[] headerBytes, int hashPos)
+ {
+ MD5 md5 = MD5.Create();
+ hash = md5.ComputeHash(headerBytes, hashPos, 0x600);
+ md5.Clear();
+ }
+
+ private void parseHeader(Stream headerStream)
+ {
+ headerStream.Seek(0, SeekOrigin.Begin);
+ byte[] tmp = new byte[4];
+
+ if (!isShortImet) headerStream.Read(additionalPadding, 0, additionalPadding.Length);
+ headerStream.Read(padding, 0, padding.Length);
+
+ headerStream.Read(tmp, 0, 4);
+ if (Shared.Swap(BitConverter.ToUInt32(tmp, 0)) != imetMagic)
+ throw new Exception("Invalid Magic!");
+
+ headerStream.Read(tmp, 0, 4);
+ if (Shared.Swap(BitConverter.ToUInt32(tmp, 0)) != sizeOfHeader)
+ throw new Exception("Invalid Header Size!");
+
+ headerStream.Read(tmp, 0, 4);
+ unknown = Shared.Swap(BitConverter.ToUInt32(tmp, 0));
+
+ headerStream.Read(tmp, 0, 4);
+ iconSize = Shared.Swap(BitConverter.ToUInt32(tmp, 0));
+
+ headerStream.Read(tmp, 0, 4);
+ bannerSize = Shared.Swap(BitConverter.ToUInt32(tmp, 0));
+
+ headerStream.Read(tmp, 0, 4);
+ soundSize = Shared.Swap(BitConverter.ToUInt32(tmp, 0));
+
+ headerStream.Read(tmp, 0, 4);
+ flags = Shared.Swap(BitConverter.ToUInt32(tmp, 0));
+
+ headerStream.Read(japaneseTitle, 0, japaneseTitle.Length);
+ headerStream.Read(englishTitle, 0, englishTitle.Length);
+ headerStream.Read(germanTitle, 0, germanTitle.Length);
+ headerStream.Read(frenchTitle, 0, frenchTitle.Length);
+ headerStream.Read(spanishTitle, 0, spanishTitle.Length);
+ headerStream.Read(italianTitle, 0, italianTitle.Length);
+ headerStream.Read(dutchTitle, 0, dutchTitle.Length);
+ headerStream.Read(unknownTitle1, 0, unknownTitle1.Length);
+ headerStream.Read(unknownTitle2, 0, unknownTitle2.Length);
+ headerStream.Read(koreanTitle, 0, koreanTitle.Length);
+
+ headerStream.Read(padding2, 0, padding2.Length);
+ headerStream.Read(hash, 0, hash.Length);
+
+ headerStream.Seek(-16, SeekOrigin.Current);
+ headerStream.Write(new byte[16], 0, 16);
+
+ byte[] temp = new byte[headerStream.Length];
+ headerStream.Seek(0, SeekOrigin.Begin);
+ headerStream.Read(temp, 0, temp.Length);
+
+ MD5 m = MD5.Create();
+ byte[] newHash = m.ComputeHash(temp, (isShortImet) ? 0 : 0x40, 0x600);
+ m.Clear();
+
+ hashesMatch = Shared.CompareByteArrays(newHash, hash);
+ }
+
+ private string returnTitleAsString(byte[] title)
+ {
+ string tempStr = string.Empty;
+
+ for (int i = 0; i < 84; i += 2)
+ {
+ char tempChar = BitConverter.ToChar(new byte[] { title[i + 1], title[i] }, 0);
+ if (tempChar != 0x00) tempStr += tempChar;
+ }
+
+ return tempStr;
+ }
+
+ private void setTitleFromString(string title, int titleIndex)
+ {
+ byte[] tempArray = new byte[84];
+
+ for (int i = 0; i < title.Length; i++)
+ {
+ byte[] tempBytes = BitConverter.GetBytes(title[i]);
+ tempArray[i * 2 + 1] = tempBytes[0];
+ tempArray[i * 2] = tempBytes[1];
+ }
+
+ switch (titleIndex)
+ {
+ case 0:
+ japaneseTitle = tempArray;
+ break;
+ case 1:
+ englishTitle = tempArray;
+ break;
+ case 2:
+ germanTitle = tempArray;
+ break;
+ case 3:
+ frenchTitle = tempArray;
+ break;
+ case 4:
+ spanishTitle = tempArray;
+ break;
+ case 5:
+ italianTitle = tempArray;
+ break;
+ case 6:
+ dutchTitle = tempArray;
+ break;
+ case 7:
+ koreanTitle = tempArray;
+ break;
+ }
+ }
+ #endregion
+ }
+
+ public class IMD5
+ {
+ private uint imd5Magic = 0x494d4435;
+ private uint fileSize;
+ private byte[] padding = new byte[8];
+ private byte[] hash = new byte[16];
+
+ ///
+ /// The size of the file without the IMD5 Header.
+ ///
+ public uint FileSize { get { return fileSize; } }
+ ///
+ /// The hash of the file without the IMD5 Header.
+ ///
+ public byte[] Hash { get { return hash; } }
+
+ private IMD5() { }
+
+ #region Public Functions
+ ///
+ /// Loads the IMD5 Header of a file.
+ ///
+ ///
+ ///
+ public static IMD5 Load(string pathToFile)
+ {
+ return Load(File.ReadAllBytes(pathToFile));
+ }
+
+ ///
+ /// Loads the IMD5 Header of a byte array.
+ ///
+ ///
+ ///
+ public static IMD5 Load(byte[] fileOrHeader)
+ {
+ HeaderType type = DetectHeader(fileOrHeader);
+ if (type != HeaderType.IMD5)
+ throw new Exception("No IMD5 Header found!");
+
+ IMD5 h = new IMD5();
+ MemoryStream ms = new MemoryStream(fileOrHeader);
+
+ try { h.parseHeader(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ return h;
+ }
+
+ ///
+ /// Loads the IMD5 Header of a stream.
+ ///
+ ///
+ ///
+ public static IMD5 Load(Stream fileOrHeader)
+ {
+ HeaderType type = DetectHeader(fileOrHeader);
+ if (type != HeaderType.IMD5)
+ throw new Exception("No IMD5 Header found!");
+
+ IMD5 h = new IMD5();
+ h.parseHeader(fileOrHeader);
+ return h;
+ }
+
+ ///
+ /// Creates a new IMD5 Header.
+ ///
+ ///
+ ///
+ public static IMD5 Create(byte[] file)
+ {
+ IMD5 h = new IMD5();
+
+ h.fileSize = (uint)file.Length;
+ h.computeHash(file);
+
+ return h;
+ }
+
+ ///
+ /// Adds an IMD5 Header to a file.
+ ///
+ ///
+ public static void AddHeader(string pathToFile)
+ {
+ byte[] fileWithHeader = AddHeader(File.ReadAllBytes(pathToFile));
+ File.Delete(pathToFile);
+
+ using (FileStream fs = new FileStream(pathToFile, FileMode.Create))
+ fs.Write(fileWithHeader, 0, fileWithHeader.Length);
+ }
+
+ ///
+ /// Adds an IMD5 Header to a byte array.
+ ///
+ ///
+ ///
+ public static byte[] AddHeader(byte[] file)
+ {
+ IMD5 h = IMD5.Create(file);
+
+ MemoryStream ms = new MemoryStream();
+ h.writeToStream(ms);
+ ms.Write(file, 0, file.Length);
+
+ byte[] res = ms.ToArray();
+ ms.Dispose();
+ return res;
+ }
+
+ ///
+ /// Removes the IMD5 Header of a file.
+ ///
+ ///
+ public static void RemoveHeader(string pathToFile)
+ {
+ byte[] fileWithoutHeader = RemoveHeader(File.ReadAllBytes(pathToFile));
+ File.Delete(pathToFile);
+
+ using (FileStream fs = new FileStream(pathToFile, FileMode.Create))
+ fs.Write(fileWithoutHeader, 0, fileWithoutHeader.Length);
+ }
+
+ ///
+ /// Removes the IMD5 Header of a byte array.
+ ///
+ ///
+ ///
+ public static byte[] RemoveHeader(byte[] file)
+ {
+ MemoryStream ms = new MemoryStream();
+ ms.Write(file, 32, file.Length - 32);
+
+ byte[] ret = ms.ToArray();
+ ms.Dispose();
+
+ return ret;
+ }
+
+
+
+ ///
+ /// Returns the IMD5 Header as a memory stream.
+ ///
+ ///
+ public MemoryStream ToMemoryStream()
+ {
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ return ms;
+ }
+
+ ///
+ /// Returns the IMD5 Header as a byte array.
+ ///
+ ///
+ public byte[] ToByteArray()
+ {
+ return ToMemoryStream().ToArray();
+ }
+
+ ///
+ /// Writes the IMD5 Header to the given stream.
+ ///
+ ///
+ public void Write(Stream writeStream)
+ {
+ writeToStream(writeStream);
+ }
+ #endregion
+
+ #region Private Functions
+ private void writeToStream(Stream writeStream)
+ {
+ writeStream.Seek(0, SeekOrigin.Begin);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(imd5Magic)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(fileSize)), 0, 4);
+ writeStream.Write(padding, 0, padding.Length);
+ writeStream.Write(hash, 0, hash.Length);
+ }
+
+ private void computeHash(byte[] bytesToHash)
+ {
+ MD5 md5 = MD5.Create();
+ hash = md5.ComputeHash(bytesToHash);
+ md5.Clear();
+ }
+
+ private void parseHeader(Stream headerStream)
+ {
+ headerStream.Seek(0, SeekOrigin.Begin);
+ byte[] tmp = new byte[4];
+
+ headerStream.Read(tmp, 0, 4);
+ if (Shared.Swap(BitConverter.ToUInt32(tmp, 0)) != imd5Magic)
+ throw new Exception("Invalid Magic!");
+
+ headerStream.Read(tmp, 0, 4);
+ fileSize = Shared.Swap(BitConverter.ToUInt32(tmp, 0));
+
+ headerStream.Read(padding, 0, padding.Length);
+ headerStream.Read(hash, 0, hash.Length);
+ }
+ #endregion
+ }
+ }
+}
diff --git a/NUS Downloader/IosPatcher.cs b/NUS Downloader/IosPatcher.cs
new file mode 100644
index 0000000..00121fa
--- /dev/null
+++ b/NUS Downloader/IosPatcher.cs
@@ -0,0 +1,313 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+//Patches based on Dop-Mii - Thanks to Arikado and Lunatik
+
+using System;
+using System.ComponentModel;
+using System.Text;
+
+namespace libWiiSharp
+{
+ ///
+ /// An IOS patcher which can patch fakesigning, es_identify and nand permissions.
+ ///
+ public class IosPatcher
+ {
+ private WAD wadFile;
+ private int esIndex = -1;
+
+ #region Public Functions
+ ///
+ /// Loads an IOS wad to patch the es module.
+ ///
+ ///
+ public void LoadIOS(ref WAD iosWad)
+ {
+ if ((iosWad.TitleID >> 32) != 1 ||
+ (iosWad.TitleID & 0xFFFFFFFF) < 3 ||
+ (iosWad.TitleID & 0xFFFFFFFF) > 255)
+ throw new Exception("Only IOS WADs can be patched!");
+
+ wadFile = iosWad;
+ getEsIndex();
+ }
+
+ ///
+ /// Patches fakesigning.
+ /// Returns the number of applied patches.
+ ///
+ ///
+ public int PatchFakeSigning()
+ {
+ if (esIndex < 0) return -1;
+ return patchFakeSigning(ref wadFile.Contents[esIndex]);
+ }
+
+ ///
+ /// Patches es_identify.
+ /// Returns the number of applied patches.
+ ///
+ ///
+ public int PatchEsIdentify()
+ {
+ if (esIndex < 0) return -1;
+ return patchEsIdentify(ref wadFile.Contents[esIndex]);
+ }
+
+ ///
+ /// Patches nand permissions.
+ /// Returns the number of applied patches.
+ ///
+ ///
+ public int PatchNandPermissions()
+ {
+ if (esIndex < 0) return -1;
+ return patchNandPermissions(ref wadFile.Contents[esIndex]);
+ }
+
+ ///
+ /// Patches fakesigning, es_identify and nand permissions.
+ /// Returns the number of applied patches.
+ ///
+ ///
+ public int PatchAll()
+ {
+ if (esIndex < 0) return -1;
+ return patchAll(ref wadFile.Contents[esIndex]);
+ }
+
+ ///
+ /// Patches fakesigning.
+ /// Returns the number of applied patches.
+ ///
+ ///
+ public int PatchFakeSigning(ref byte[] esModule)
+ {
+ return patchFakeSigning(ref esModule);
+ }
+
+ ///
+ /// Patches es_identify.
+ /// Returns the number of applied patches.
+ ///
+ ///
+ public int PatchEsIdentify(ref byte[] esModule)
+ {
+ return patchEsIdentify(ref esModule);
+ }
+
+ ///
+ /// Patches nand permissions.
+ /// Returns the number of applied patches.
+ ///
+ ///
+ public int PatchNandPermissions(ref byte[] esModule)
+ {
+ return patchNandPermissions(ref esModule);
+ }
+
+ ///
+ /// Patches fakesigning, es_identify and nand permissions.
+ /// Returns the number of applied patches.
+ ///
+ ///
+ public int PatchAll(ref byte[] esModule)
+ {
+ return patchAll(ref esModule);
+ }
+ #endregion
+
+ #region Private Functions
+ private int patchFakeSigning(ref byte[] esModule)
+ {
+ fireDebug("Patching Fakesigning...");
+ int patchCount = 0;
+
+ byte[] oldHashCheck = { 0x20, 0x07, 0x23, 0xA2 };
+ byte[] newHashCheck = { 0x20, 0x07, 0x4B, 0x0B };
+
+ for (int i = 0; i < esModule.Length - 4; i++)
+ {
+ fireProgress((i + 1) * 100 / esModule.Length);
+
+ if (Shared.CompareByteArrays(esModule, i, oldHashCheck, 0, 4) ||
+ Shared.CompareByteArrays(esModule, i, newHashCheck, 0, 4))
+ {
+ fireDebug(" Patching at Offset: 0x{0}", i.ToString("x8").ToUpper());
+ esModule[i + 1] = 0x00;
+ i += 4;
+ patchCount++;
+ }
+ }
+
+ fireDebug("Patching Fakesigning Finished... (Patches applied: {0})", patchCount);
+ return patchCount;
+ }
+
+ private int patchEsIdentify(ref byte[] esModule)
+ {
+ fireDebug("Patching ES_Identify...");
+ int patchCount = 0;
+
+ byte[] identifyCheck = { 0x28, 0x03, 0xD1, 0x23 };
+
+ for (int i = 0; i < esModule.Length - 4; i++)
+ {
+ fireProgress((i + 1) * 100 / esModule.Length);
+
+ if (Shared.CompareByteArrays(esModule, i, identifyCheck, 0, 4))
+ {
+ fireDebug(" Patching at Offset: 0x{0}", i.ToString("x8").ToUpper());
+ esModule[i + 2] = 0x00;
+ esModule[i + 3] = 0x00;
+ i += 4;
+ patchCount++;
+ }
+ }
+
+ fireDebug("Patching ES_Identify Finished... (Patches applied: {0})", patchCount);
+ return patchCount;
+ }
+
+ private int patchNandPermissions(ref byte[] esModule)
+ {
+ fireDebug("Patching NAND Permissions...");
+ int patchCount = 0;
+
+ byte[] permissionTable = { 0x42, 0x8B, 0xD0, 0x01, 0x25, 0x66 };
+
+ for (int i = 0; i < esModule.Length - 6; i++)
+ {
+ fireProgress((i + 1) * 100 / esModule.Length);
+
+ if (Shared.CompareByteArrays(esModule, i, permissionTable, 0, 6))
+ {
+ fireDebug(" Patching at Offset: 0x{0}", i.ToString("x8").ToUpper());
+ esModule[i + 2] = 0xE0;
+ i += 6;
+ patchCount++;
+ }
+ }
+
+ fireDebug("Patching NAND Permissions Finished... (Patches applied: {0})", patchCount);
+ return patchCount;
+ }
+
+ private int patchAll(ref byte[] esModule)
+ {
+ fireDebug("Patching Fakesigning, ES_Identify and NAND Permissions...");
+ int patchCount = 0;
+
+ byte[] oldHashCheck = { 0x20, 0x07, 0x23, 0xA2 };
+ byte[] newHashCheck = { 0x20, 0x07, 0x4B, 0x0B };
+ byte[] identifyCheck = { 0x28, 0x03, 0xD1, 0x23 };
+ byte[] permissionTable = { 0x42, 0x8B, 0xD0, 0x01, 0x25, 0x66 };
+
+ for (int i = 0; i < esModule.Length - 6; i++)
+ {
+ fireProgress((i + 1) * 100 / esModule.Length);
+
+ if (Shared.CompareByteArrays(esModule, i, oldHashCheck, 0, 4) ||
+ Shared.CompareByteArrays(esModule, i, newHashCheck, 0, 4))
+ {
+ fireDebug(" Patching Fakesigning at Offset: 0x{0}", i.ToString("x8").ToUpper());
+ esModule[i + 1] = 0x00;
+ i += 4;
+ patchCount++;
+ }
+ else if (Shared.CompareByteArrays(esModule, i, identifyCheck, 0, 4))
+ {
+ fireDebug(" Patching ES_Identify at Offset: 0x{0}", i.ToString("x8").ToUpper());
+ esModule[i + 2] = 0x00;
+ esModule[i + 3] = 0x00;
+ i += 4;
+ patchCount++;
+ }
+ else if (Shared.CompareByteArrays(esModule, i, permissionTable, 0, 6))
+ {
+ fireDebug(" Patching NAND Permissions at Offset: 0x{0}", i.ToString("x8").ToUpper());
+ esModule[i + 2] = 0xE0;
+ i += 6;
+ patchCount++;
+ }
+ }
+
+ fireDebug("Patching Fakesigning, ES_Identify and NAND Permissions Finished... (Patches applied: {0})", patchCount);
+ return patchCount;
+ }
+
+ private void getEsIndex()
+ {
+ fireDebug("Scanning for ES Module...");
+ string iosTag = "$IOSVersion:";
+
+ for (int i = wadFile.NumOfContents - 1; i >= 0; i--)
+ {
+ fireDebug(" Scanning Content #{0} of {1}...", i + 1, wadFile.NumOfContents);
+ fireProgress((i + 1) * 100 / wadFile.NumOfContents);
+
+ for (int j = 0; j < wadFile.Contents[i].Length - 64; j++)
+ {
+ if (ASCIIEncoding.ASCII.GetString(wadFile.Contents[i], j, 12) == iosTag)
+ {
+ int curIndex = j + 12;
+ while (wadFile.Contents[i][curIndex] == ' ') { curIndex++; }
+
+ if (ASCIIEncoding.ASCII.GetString(wadFile.Contents[i], curIndex, 3) == "ES:")
+ {
+ fireDebug(" -> ES Module found!");
+ fireDebug("Scanning for ES Module Finished...");
+ esIndex = i;
+ fireProgress(100);
+ return;
+ }
+ }
+ }
+ }
+
+ fireDebug(@"/!\/!\/!\ ES Module wasn't found! /!\/!\/!\");
+ throw new Exception("ES module wasn't found!");
+ }
+ #endregion
+
+ #region Events
+ ///
+ /// Fires the Progress of various operations
+ ///
+ public event EventHandler Progress;
+ ///
+ /// Fires debugging messages. You may write them into a log file or log textbox.
+ ///
+ public event EventHandler Debug;
+
+ private void fireDebug(string debugMessage, params object[] args)
+ {
+ EventHandler debug = Debug;
+ if (debug != null)
+ debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
+ }
+
+ private void fireProgress(int progressPercentage)
+ {
+ EventHandler progress = Progress;
+ if (progress != null)
+ progress(new object(), new ProgressChangedEventArgs(progressPercentage, string.Empty));
+ }
+ #endregion
+ }
+}
diff --git a/NUS Downloader/Lz77.cs b/NUS Downloader/Lz77.cs
new file mode 100644
index 0000000..0433197
--- /dev/null
+++ b/NUS Downloader/Lz77.cs
@@ -0,0 +1,400 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+//Lz77 (de-)compression algorithm based on gbalzss by Andre Perrot (Thanks!)
+
+using System;
+using System.IO;
+
+namespace libWiiSharp
+{
+ public class Lz77
+ {
+ private static uint lz77Magic = 0x4c5a3737;
+ private const int N = 4096;
+ private const int F = 18;
+ private const int threshold = 2;
+ private int[] leftSon = new int[N + 1];
+ private int[] rightSon = new int[N + 257];
+ private int[] dad = new int[N + 1];
+ private ushort[] textBuffer = new ushort[N + 17];
+ private int matchPosition = 0, matchLength = 0;
+
+ ///
+ /// Lz77 Magic.
+ ///
+ public static uint Lz77Magic { get { return lz77Magic; } }
+
+ #region Public Functions
+ ///
+ /// Checks whether a file is Lz77 compressed or not.
+ ///
+ ///
+ ///
+ public static bool IsLz77Compressed(string file)
+ {
+ return IsLz77Compressed(File.ReadAllBytes(file));
+ }
+
+ ///
+ /// Checks whether a file is Lz77 compressed or not.
+ ///
+ ///
+ ///
+ public static bool IsLz77Compressed(byte[] file)
+ {
+ Headers.HeaderType h = Headers.DetectHeader(file);
+ return (Shared.Swap(BitConverter.ToUInt32(file, (int)h)) == lz77Magic) ;
+ }
+
+ ///
+ /// Checks whether a file is Lz77 compressed or not.
+ ///
+ ///
+ ///
+ public static bool IsLz77Compressed(Stream file)
+ {
+ Headers.HeaderType h = Headers.DetectHeader(file);
+ byte[] temp = new byte[4];
+ file.Seek((long)h, SeekOrigin.Begin);
+ file.Read(temp, 0, temp.Length);
+ return (Shared.Swap(BitConverter.ToUInt32(temp, 0)) == lz77Magic);
+ }
+
+
+
+ ///
+ /// Compresses a file using the Lz77 algorithm.
+ ///
+ ///
+ ///
+ public void Compress(string inFile, string outFile)
+ {
+ Stream compressedFile;
+
+ using (FileStream fsIn = new FileStream(inFile, FileMode.Open))
+ compressedFile = compress(fsIn);
+
+ byte[] output = new byte[compressedFile.Length];
+ compressedFile.Read(output, 0, output.Length);
+
+ if (File.Exists(outFile)) File.Delete(outFile);
+
+ using (FileStream fs = new FileStream(outFile, FileMode.Create))
+ fs.Write(output, 0, output.Length);
+ }
+
+ ///
+ /// Compresses the byte array using the Lz77 algorithm.
+ ///
+ ///
+ ///
+ public byte[] Compress(byte[] file)
+ {
+ return ((MemoryStream)compress(new MemoryStream(file))).ToArray();
+ }
+
+ ///
+ /// Compresses the stream using the Lz77 algorithm.
+ ///
+ ///
+ ///
+ public Stream Compress(Stream file)
+ {
+ return compress(file);
+ }
+
+ ///
+ /// Decompresses a file using the Lz77 algorithm.
+ ///
+ ///
+ ///
+ public void Decompress(string inFile, string outFile)
+ {
+ Stream compressedFile;
+
+ using (FileStream fsIn = new FileStream(inFile, FileMode.Open))
+ compressedFile = decompress(fsIn);
+
+ byte[] output = new byte[compressedFile.Length];
+ compressedFile.Read(output, 0, output.Length);
+
+ if (File.Exists(outFile)) File.Delete(outFile);
+
+ using (FileStream fs = new FileStream(outFile, FileMode.Create))
+ fs.Write(output, 0, output.Length);
+ }
+
+ ///
+ /// Decompresses the byte array using the Lz77 algorithm.
+ ///
+ ///
+ ///
+ public byte[] Decompress(byte[] file)
+ {
+ return ((MemoryStream)decompress(new MemoryStream(file))).ToArray();
+ }
+
+ public Stream Decompress(Stream file)
+ {
+ return decompress(file);
+ }
+ #endregion
+
+ #region Private Functions
+ private Stream decompress(Stream inFile)
+ {
+ if (!Lz77.IsLz77Compressed(inFile)) return inFile;
+ inFile.Seek(0, SeekOrigin.Begin);
+
+ int i, j, k, r, c, z;
+ uint flags, decompressedSize, currentSize = 0;
+
+ Headers.HeaderType h = Headers.DetectHeader(inFile);
+
+ byte[] temp = new byte[8];
+ inFile.Seek((int)h, SeekOrigin.Begin);
+ inFile.Read(temp, 0, 8);
+
+ if (Shared.Swap(BitConverter.ToUInt32(temp, 0)) != lz77Magic)
+ { inFile.Dispose(); throw new Exception("Invaild Magic!"); }
+ if (temp[4] != 0x10)
+ { inFile.Dispose(); throw new Exception("Unsupported Compression Type!"); }
+
+ decompressedSize = (BitConverter.ToUInt32(temp, 4)) >> 8;
+
+ for (i = 0; i < N - F; i++) textBuffer[i] = 0xdf;
+ r = N - F; flags = 7; z = 7;
+
+ MemoryStream outFile = new MemoryStream();
+ while (true)
+ {
+ flags <<= 1;
+ z++;
+
+ if (z == 8)
+ {
+ if ((c = inFile.ReadByte()) == -1) break;
+
+ flags = (uint)c;
+ z = 0;
+ }
+
+ if ((flags & 0x80) == 0)
+ {
+ if ((c = inFile.ReadByte()) == inFile.Length - 1) break;
+ if (currentSize < decompressedSize) outFile.WriteByte((byte)c);
+
+ textBuffer[r++] = (byte)c;
+ r &= (N - 1);
+ currentSize++;
+ }
+ else
+ {
+ if ((i = inFile.ReadByte()) == -1) break;
+ if ((j = inFile.ReadByte()) == -1) break;
+
+ j = j | ((i << 8) & 0xf00);
+ i = ((i >> 4) & 0x0f) + threshold;
+ for (k = 0; k <= i; k++)
+ {
+ c = textBuffer[(r - j - 1) & (N - 1)];
+ if (currentSize < decompressedSize) outFile.WriteByte((byte)c); textBuffer[r++] = (byte)c; r &= (N - 1); currentSize++;
+ }
+ }
+ }
+
+ return outFile;
+ }
+
+ private Stream compress(Stream inFile)
+ {
+ if (Lz77.IsLz77Compressed(inFile)) return inFile;
+ inFile.Seek(0, SeekOrigin.Begin);
+
+ int textSize = 0;
+ int codeSize = 0;
+
+ int i, c, r, s, length, lastMatchLength, codeBufferPointer, mask;
+ int[] codeBuffer = new int[17];
+
+ uint fileSize = ((Convert.ToUInt32(inFile.Length)) << 8) + 0x10;
+ MemoryStream outFile = new MemoryStream();
+
+ outFile.Write(BitConverter.GetBytes(Shared.Swap(lz77Magic)), 0, 4);
+ outFile.Write(BitConverter.GetBytes(fileSize), 0, 4);
+
+ InitTree();
+ codeBuffer[0] = 0;
+ codeBufferPointer = 1;
+ mask = 0x80;
+ s = 0;
+ r = N - F;
+
+ for (i = s; i < r; i++) textBuffer[i] = 0xffff;
+
+ for (length = 0; length < F && (c = (int)inFile.ReadByte()) != -1; length++)
+ textBuffer[r + length] = (ushort)c;
+
+ if ((textSize = length) == 0) return inFile;
+
+ for (i = 1; i <= F; i++) InsertNode(r - i);
+ InsertNode(r);
+
+ do
+ {
+ if (matchLength > length) matchLength = length;
+
+ if (matchLength <= threshold)
+ {
+ matchLength = 1;
+ codeBuffer[codeBufferPointer++] = textBuffer[r];
+ }
+ else
+ {
+ codeBuffer[0] |= mask;
+
+ codeBuffer[codeBufferPointer++] = (char)
+ (((r - matchPosition - 1) >> 8) & 0x0f) |
+ ((matchLength - (threshold + 1)) << 4);
+
+ codeBuffer[codeBufferPointer++] = (char)((r - matchPosition - 1) & 0xff);
+ }
+
+ if ((mask >>= 1) == 0)
+ {
+ for (i = 0; i < codeBufferPointer; i++)
+ outFile.WriteByte((byte)codeBuffer[i]);
+
+ codeSize += codeBufferPointer;
+ codeBuffer[0] = 0; codeBufferPointer = 1;
+ mask = 0x80;
+ }
+
+ lastMatchLength = matchLength;
+ for (i = 0; i < lastMatchLength && (c = (int)inFile.ReadByte()) != -1; i++)
+ {
+ DeleteNode(s);
+
+ textBuffer[s] = (ushort)c;
+ if (s < F - 1) textBuffer[s + N] = (ushort)c;
+ s = (s + 1) & (N - 1); r = (r + 1) & (N - 1);
+
+ InsertNode(r);
+ }
+
+ while (i++ < lastMatchLength)
+ {
+ DeleteNode(s);
+
+ s = (s + 1) & (N - 1); r = (r + 1) & (N - 1);
+ if (--length != 0) InsertNode(r);
+ }
+ } while (length > 0);
+
+
+ if (codeBufferPointer > 1)
+ {
+ for (i = 0; i < codeBufferPointer; i++) outFile.WriteByte((byte)codeBuffer[i]);
+ codeSize += codeBufferPointer;
+ }
+
+ if (codeSize % 4 != 0)
+ for (i = 0; i < 4 - (codeSize % 4); i++)
+ outFile.WriteByte(0x00);
+
+ return outFile;
+ }
+
+ private void InitTree()
+ {
+ int i;
+ for (i = N + 1; i <= N + 256; i++) rightSon[i] = N;
+ for (i = 0; i < N; i++) dad[i] = N;
+ }
+
+ private void InsertNode(int r)
+ {
+ int i, p, cmp;
+ cmp = 1;
+ p = N + 1 + (textBuffer[r] == 0xffff ? 0 : textBuffer[r]);
+ rightSon[r] = leftSon[r] = N; matchLength = 0;
+
+ for (; ; )
+ {
+ if (cmp >= 0)
+ {
+ if (rightSon[p] != N) p = rightSon[p];
+ else { rightSon[p] = r; dad[r] = p; return; }
+ }
+ else
+ {
+ if (leftSon[p] != N) p = leftSon[p];
+ else { leftSon[p] = r; dad[r] = p; return; }
+ }
+
+ for (i = 1; i < F; i++)
+ if ((cmp = textBuffer[r + i] - textBuffer[p + i]) != 0) break;
+
+ if (i > matchLength)
+ {
+ matchPosition = p;
+ if ((matchLength = i) >= F) break;
+ }
+ }
+
+ dad[r] = dad[p]; leftSon[r] = leftSon[p]; rightSon[r] = rightSon[p];
+ dad[leftSon[p]] = r; dad[rightSon[p]] = r;
+
+ if (rightSon[dad[p]] == p) rightSon[dad[p]] = r;
+ else leftSon[dad[p]] = r;
+
+ dad[p] = N;
+ }
+
+ private void DeleteNode(int p)
+ {
+ int q;
+
+ if (dad[p] == N) return;
+
+ if (rightSon[p] == N) q = leftSon[p];
+ else if (leftSon[p] == N) q = rightSon[p];
+ else
+ {
+ q = leftSon[p];
+
+ if (rightSon[q] != N)
+ {
+ do { q = rightSon[q]; } while (rightSon[q] != N);
+ rightSon[dad[q]] = leftSon[q]; dad[leftSon[q]] = dad[q];
+ leftSon[q] = leftSon[p]; dad[leftSon[p]] = q;
+ }
+
+ rightSon[q] = rightSon[p]; dad[rightSon[p]] = q;
+ }
+
+ dad[q] = dad[p];
+
+ if (rightSon[dad[p]] == p) rightSon[dad[p]] = q;
+ else leftSon[dad[p]] = q;
+
+ dad[p] = N;
+ }
+ #endregion
+ }
+}
diff --git a/NUS Downloader/NUS Downloader.csproj b/NUS Downloader/NUS Downloader.csproj
index fdb54bc..07b7729 100644
--- a/NUS Downloader/NUS Downloader.csproj
+++ b/NUS Downloader/NUS Downloader.csproj
@@ -68,7 +68,10 @@
+
+
+
PreserveNewest
Form
@@ -77,6 +80,9 @@
Form1.cs
PreserveNewest
+
+
+
@@ -108,6 +114,11 @@
Settings.settings
True
+
+
+
+
+
Component
diff --git a/NUS Downloader/Shared.cs b/NUS Downloader/Shared.cs
new file mode 100644
index 0000000..6e01478
--- /dev/null
+++ b/NUS Downloader/Shared.cs
@@ -0,0 +1,272 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+
+namespace libWiiSharp
+{
+ public static class Shared
+ {
+ ///
+ /// Merges two string arrays into one without double entries.
+ ///
+ ///
+ ///
+ ///
+ public static string[] MergeStringArrays(string[] a, string[] b)
+ {
+ List sList = new List(a);
+
+ foreach (string currentString in b)
+ if (!sList.Contains(currentString)) sList.Add(currentString);
+
+ sList.Sort();
+ return sList.ToArray();
+ }
+
+ ///
+ /// Compares two byte arrays.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool CompareByteArrays(byte[] first, int firstIndex, byte[] second, int secondIndex, int length)
+ {
+ if (first.Length < length || second.Length < length) return false;
+
+ for (int i = 0; i < length; i++)
+ if (first[firstIndex + i] != second[secondIndex + i]) return false;
+
+ return true;
+ }
+
+ ///
+ /// Compares two byte arrays.
+ ///
+ ///
+ ///
+ ///
+ public static bool CompareByteArrays(byte[] first, byte[] second)
+ {
+ if (first.Length != second.Length) return false;
+ else
+ for (int i = 0; i < first.Length; i++)
+ if (first[i] != second[i]) return false;
+
+ return true;
+ }
+
+ ///
+ /// Turns a byte array into a string, default separator is a space.
+ ///
+ ///
+ ///
+ ///
+ public static string ByteArrayToString(byte[] byteArray, char separator = ' ')
+ {
+ string res = string.Empty;
+
+ foreach (byte b in byteArray)
+ res += b.ToString("x2").ToUpper() + separator;
+
+ return res.Remove(res.Length - 1);
+ }
+
+ ///
+ /// Turns a hex string into a byte array.
+ ///
+ ///
+ ///
+ public static byte[] HexStringToByteArray(string hexString)
+ {
+ byte[] ba = new byte[hexString.Length / 2];
+
+ for (int i = 0; i < hexString.Length / 2; i++)
+ ba[i] = byte.Parse(hexString.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
+
+ return ba;
+ }
+
+ ///
+ /// Counts how often the given char exists in the given string.
+ ///
+ ///
+ ///
+ ///
+ public static int CountCharsInString(string theString, char theChar)
+ {
+ int count = 0;
+
+ foreach (char thisChar in theString)
+ if (thisChar == theChar)
+ count++;
+
+ return count;
+ }
+
+ ///
+ /// Pads the given value to a multiple of the given padding value, default padding value is 64.
+ ///
+ ///
+ ///
+ public static long AddPadding(long value)
+ {
+ return AddPadding(value, 64);
+ }
+
+ ///
+ /// Pads the given value to a multiple of the given padding value, default padding value is 64.
+ ///
+ ///
+ ///
+ ///
+ public static long AddPadding(long value, int padding)
+ {
+ if (value % padding != 0)
+ {
+ value = value + (padding - (value % padding));
+ }
+
+ return value;
+ }
+
+ ///
+ /// Pads the given value to a multiple of the given padding value, default padding value is 64.
+ ///
+ ///
+ ///
+ public static int AddPadding(int value)
+ {
+ return AddPadding(value, 64);
+ }
+
+ ///
+ /// Pads the given value to a multiple of the given padding value, default padding value is 64.
+ ///
+ ///
+ ///
+ ///
+ public static int AddPadding(int value, int padding)
+ {
+ if (value % padding != 0)
+ {
+ value = value + (padding - (value % padding));
+ }
+
+ return value;
+ }
+
+ ///
+ /// Swaps endianness.
+ ///
+ ///
+ ///
+ public static ushort Swap(ushort value)
+ {
+ return (ushort)IPAddress.HostToNetworkOrder((short)value);
+ }
+
+ ///
+ /// Swaps endianness.
+ ///
+ ///
+ ///
+ public static uint Swap(uint value)
+ {
+ return (uint)IPAddress.HostToNetworkOrder((int)value);
+ }
+
+ ///
+ /// Swaps endianness
+ ///
+ ///
+ ///
+ public static ulong Swap(ulong value)
+ {
+ return (ulong)IPAddress.HostToNetworkOrder((long)value);
+ }
+
+ ///
+ /// Turns a ushort array into a byte array.
+ ///
+ ///
+ ///
+ public static byte[] UShortArrayToByteArray(ushort[] array)
+ {
+ List results = new List();
+ foreach (ushort value in array)
+ {
+ byte[] converted = BitConverter.GetBytes(value);
+ results.AddRange(converted);
+ }
+ return results.ToArray();
+ }
+
+ ///
+ /// Turns a uint array into a byte array.
+ ///
+ ///
+ ///
+ public static byte[] UIntArrayToByteArray(uint[] array)
+ {
+ List results = new List();
+ foreach (uint value in array)
+ {
+ byte[] converted = BitConverter.GetBytes(value);
+ results.AddRange(converted);
+ }
+ return results.ToArray();
+ }
+
+ ///
+ /// Turns a byte array into a uint array.
+ ///
+ ///
+ ///
+ public static uint[] ByteArrayToUIntArray(byte[] array)
+ {
+ UInt32[] converted = new UInt32[array.Length / 4];
+ int j = 0;
+
+ for (int i = 0; i < array.Length; i += 4)
+ converted[j++] = BitConverter.ToUInt32(array, i);
+
+ return converted;
+ }
+
+ ///
+ /// Turns a byte array into a ushort array.
+ ///
+ ///
+ ///
+ public static ushort[] ByteArrayToUShortArray(byte[] array)
+ {
+ ushort[] converted = new ushort[array.Length / 2];
+ int j = 0;
+
+ for (int i = 0; i < array.Length; i += 2)
+ converted[j++] = BitConverter.ToUInt16(array, i);
+
+ return converted;
+ }
+ }
+}
diff --git a/NUS Downloader/TMD.cs b/NUS Downloader/TMD.cs
new file mode 100644
index 0000000..32cbef5
--- /dev/null
+++ b/NUS Downloader/TMD.cs
@@ -0,0 +1,659 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace libWiiSharp
+{
+ public enum ContentType : ushort
+ {
+ Normal = 0x0001,
+ DLC = 0x4001, //Seen this in a DLC wad...
+ Shared = 0x8001,
+ }
+
+ public enum Region : ushort
+ {
+ Japan = 0,
+ USA = 1,
+ Europe = 2,
+ Free = 3,
+ }
+
+ public class TMD : IDisposable
+ {
+ private bool fakeSign = false;
+
+ private uint signatureExponent = 0x00010001;
+ private byte[] signature = new byte[256];
+ private byte[] padding = new byte[60];
+ private byte[] issuer = new byte[64];
+ private byte version;
+ private byte caCrlVersion;
+ private byte signerCrlVersion;
+ private byte paddingByte;
+ private ulong startupIos;
+ private ulong titleId;
+ private uint titleType;
+ private ushort groupId;
+ private ushort padding2;
+ private ushort region;
+ private byte[] reserved = new byte[58];
+ private uint accessRights;
+ private ushort titleVersion;
+ private ushort numOfContents;
+ private ushort bootIndex;
+ private ushort padding3;
+ private List contents;
+
+ ///
+ /// The region of the title.
+ ///
+ public Region Region { get { return (Region)region; } set { region = (ushort)value; } }
+ ///
+ /// The IOS the title is launched with.
+ ///
+ public ulong StartupIOS { get { return startupIos; } set { startupIos = value; } }
+ ///
+ /// The Title ID.
+ ///
+ public ulong TitleID { get { return titleId; } set { titleId = value; } }
+ ///
+ /// The Title Version.
+ ///
+ public ushort TitleVersion { get { return titleVersion; } set { titleVersion = value; } }
+ ///
+ /// The Number of Contents.
+ ///
+ public ushort NumOfContents { get { return numOfContents; } }
+ ///
+ /// The boot index. Represents the index of the nand loader.
+ ///
+ public ushort BootIndex { get { return bootIndex; } set { if (value <= numOfContents) bootIndex = value; } }
+ ///
+ /// The content descriptions in the TMD.
+ ///
+ public TMD_Content[] Contents { get { return contents.ToArray(); } set { contents = new List(value); numOfContents = (ushort)value.Length; } }
+ ///
+ /// If true, the TMD will be fakesigned while saving.
+ ///
+ public bool FakeSign { get { return fakeSign; } set { fakeSign = value; } }
+
+ #region IDisposable Members
+ private bool isDisposed = false;
+
+ ~TMD()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && !isDisposed)
+ {
+ signature = null;
+ padding = null;
+ issuer = null;
+ reserved = null;
+
+ contents.Clear();
+ contents = null;
+ }
+
+ isDisposed = true;
+ }
+ #endregion
+
+ #region Public Functions
+ ///
+ /// Loads a tmd file.
+ ///
+ ///
+ ///
+ public static TMD Load(string pathToTmd)
+ {
+ return Load(File.ReadAllBytes(pathToTmd));
+ }
+
+ ///
+ /// Loads a tmd file.
+ ///
+ ///
+ ///
+ public static TMD Load(byte[] tmdFile)
+ {
+ TMD t = new TMD();
+ MemoryStream ms = new MemoryStream(tmdFile);
+
+ try { t.parseTmd(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ return t;
+ }
+
+ ///
+ /// Loads a tmd file.
+ ///
+ ///
+ ///
+ public static TMD Load(Stream tmd)
+ {
+ TMD t = new TMD();
+ t.parseTmd(tmd);
+ return t;
+ }
+
+
+
+ ///
+ /// Loads a tmd file.
+ ///
+ ///
+ public void LoadFile(string pathToTmd)
+ {
+ LoadFile(File.ReadAllBytes(pathToTmd));
+ }
+
+ ///
+ /// Loads a tmd file.
+ ///
+ ///
+ public void LoadFile(byte[] tmdFile)
+ {
+ MemoryStream ms = new MemoryStream(tmdFile);
+
+ try { parseTmd(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ }
+
+ ///
+ /// Loads a tmd file.
+ ///
+ ///
+ public void LoadFile(Stream tmd)
+ {
+ parseTmd(tmd);
+ }
+
+
+
+ ///
+ /// Saves the TMD.
+ ///
+ ///
+ public void Save(string savePath)
+ {
+ Save(savePath, false);
+ }
+
+ ///
+ /// Saves the TMD. If fakeSign is true, the Ticket will be fakesigned.
+ ///
+ ///
+ ///
+ public void Save(string savePath, bool fakeSign)
+ {
+ if (fakeSign) this.fakeSign = true;
+ if (File.Exists(savePath)) File.Delete(savePath);
+
+ using (FileStream fs = new FileStream(savePath, FileMode.Create))
+ writeToStream(fs);
+ }
+
+ ///
+ /// Returns the TMD as a memory stream.
+ ///
+ ///
+ public MemoryStream ToMemoryStream()
+ {
+ return ToMemoryStream(false);
+ }
+
+ ///
+ /// Returns the TMD as a memory stream. If fakeSign is true, the Ticket will be fakesigned.
+ ///
+ ///
+ ///
+ public MemoryStream ToMemoryStream(bool fakeSign)
+ {
+ if (fakeSign) this.fakeSign = true;
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ return ms;
+ }
+
+ ///
+ /// Returns the TMD as a byte array.
+ ///
+ ///
+ public byte[] ToByteArray()
+ {
+ return ToByteArray(false);
+ }
+
+ ///
+ /// Returns the TMD as a byte array. If fakeSign is true, the Ticket will be fakesigned.
+ ///
+ ///
+ ///
+ public byte[] ToByteArray(bool fakeSign)
+ {
+ if (fakeSign) this.fakeSign = true;
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ byte[] res = ms.ToArray();
+ ms.Dispose();
+ return res;
+ }
+
+ ///
+ /// Updates the content entries.
+ ///
+ ///
+ /// True if you use the content ID as name (e.g. 0000008a.app).
+ /// False if you use the index as name (e.g. 00000000.app)
+ public void UpdateContents(string contentDir)
+ {
+ bool namedContentId = true;
+ for (int i = 0; i < contents.Count; i++)
+ if (!File.Exists(contentDir + Path.DirectorySeparatorChar + contents[i].ContentID.ToString("x8") + ".app"))
+ { namedContentId = false; break; }
+
+ if (!namedContentId)
+ for (int i = 0; i < contents.Count; i++)
+ if (!File.Exists(contentDir + Path.DirectorySeparatorChar + contents[i].ContentID.ToString("x8") + ".app"))
+ throw new Exception("Couldn't find all content files!");
+
+ byte[][] conts = new byte[contents.Count][];
+
+ for (int i = 0; i < contents.Count; i++)
+ {
+ string file = contentDir + Path.DirectorySeparatorChar + ((namedContentId) ? contents[i].ContentID.ToString("x8") : contents[i].Index.ToString("x8")) + ".app";
+ conts[i] = File.ReadAllBytes(file);
+ }
+
+ updateContents(conts);
+ }
+
+ ///
+ /// Updates the content entries.
+ ///
+ ///
+ /// True if you use the content ID as name (e.g. 0000008a.app).
+ /// False if you use the index as name (e.g. 00000000.app)
+ public void UpdateContents(byte[][] contents)
+ {
+ updateContents(contents);
+ }
+
+ ///
+ /// Returns the Upper Title ID as a string.
+ ///
+ ///
+ public string GetUpperTitleID()
+ {
+ byte[] titleBytes = BitConverter.GetBytes(Shared.Swap((uint)titleId));
+ return new string(new char[] { (char)titleBytes[0], (char)titleBytes[1], (char)titleBytes[2], (char)titleBytes[3] });
+ }
+
+ ///
+ /// The Number of memory blocks the content will take.
+ ///
+ ///
+ public string GetNandBlocks()
+ {
+ return calculateNandBlocks();
+ }
+
+ ///
+ /// Adds a TMD content.
+ ///
+ ///
+ public void AddContent(TMD_Content content)
+ {
+ contents.Add(content);
+
+ numOfContents = (ushort)contents.Count;
+ }
+
+ ///
+ /// Removes the content with the given index.
+ ///
+ ///
+ public void RemoveContent(int contentIndex)
+ {
+ for (int i = 0; i < numOfContents; i++)
+ if (contents[i].Index == contentIndex)
+ { contents.RemoveAt(i); break; }
+
+ numOfContents = (ushort)contents.Count;
+ }
+
+ ///
+ /// Removes the content with the given ID.
+ ///
+ ///
+ public void RemoveContentByID(int contentId)
+ {
+ for (int i = 0; i < numOfContents; i++)
+ if (contents[i].ContentID == contentId)
+ { contents.RemoveAt(i); break; }
+
+ numOfContents = (ushort)contents.Count;
+ }
+ #endregion
+
+ #region Private Functions
+ private void writeToStream(Stream writeStream)
+ {
+ fireDebug("Writing TMD...");
+
+ if (fakeSign)
+ { fireDebug(" Clearing Signature..."); signature = new byte[256]; } //Clear Signature if we fake Sign
+
+ MemoryStream ms = new MemoryStream();
+ ms.Seek(0, SeekOrigin.Begin);
+
+ fireDebug(" Writing Signature Exponent... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(signatureExponent)), 0, 4);
+
+ fireDebug(" Writing Signature... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(signature, 0, signature.Length);
+
+ fireDebug(" Writing Padding... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(padding, 0, padding.Length);
+
+ fireDebug(" Writing Issuer... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(issuer, 0, issuer.Length);
+
+ fireDebug(" Writing Version... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.WriteByte(version);
+
+ fireDebug(" Writing CA Crl Version... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.WriteByte(caCrlVersion);
+
+ fireDebug(" Writing Signer Crl Version... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.WriteByte(signerCrlVersion);
+
+ fireDebug(" Writing Padding Byte... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.WriteByte(paddingByte);
+
+ fireDebug(" Writing Startup IOS... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(startupIos)), 0, 8);
+
+ fireDebug(" Writing Title ID... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(titleId)), 0, 8);
+
+ fireDebug(" Writing Title Type... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(titleType)), 0, 4);
+
+ fireDebug(" Writing Group ID... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(groupId)), 0, 2);
+
+ fireDebug(" Writing Padding2... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(padding2)), 0, 2);
+
+ fireDebug(" Writing Region... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(region)), 0, 2);
+
+ fireDebug(" Writing Reserved... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(reserved, 0, reserved.Length);
+
+ fireDebug(" Writing Access Rights... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(accessRights)), 0, 4);
+
+ fireDebug(" Writing Title Version... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(titleVersion)), 0, 2);
+
+ fireDebug(" Writing NumOfContents... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(numOfContents)), 0, 2);
+
+ fireDebug(" Writing Boot Index... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(bootIndex)), 0, 2);
+
+ fireDebug(" Writing Padding3... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(padding3)), 0, 2);
+
+ //Write Contents
+ List contentList = new List();
+ for (int i = 0; i < contents.Count; i++)
+ contentList.Add(new ContentIndices(i, contents[i].Index));
+
+ contentList.Sort();
+
+ for (int i = 0; i < contentList.Count; i++)
+ {
+ fireDebug(" Writing Content #{1} of {2}... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper().ToUpper(), i + 1, numOfContents);
+
+ ms.Write(BitConverter.GetBytes(Shared.Swap(contents[contentList[i].Index].ContentID)), 0, 4);
+ ms.Write(BitConverter.GetBytes(Shared.Swap(contents[contentList[i].Index].Index)), 0, 2);
+ ms.Write(BitConverter.GetBytes(Shared.Swap((ushort)contents[contentList[i].Index].Type)), 0, 2);
+ ms.Write(BitConverter.GetBytes(Shared.Swap(contents[contentList[i].Index].Size)), 0, 8);
+
+ ms.Write(contents[contentList[i].Index].Hash, 0, contents[contentList[i].Index].Hash.Length);
+ }
+
+ //fake Sign
+ byte[] tmd = ms.ToArray();
+ ms.Dispose();
+
+ if (fakeSign)
+ {
+ fireDebug(" Fakesigning TMD...");
+
+ byte[] hash = new byte[20];
+ SHA1 s = SHA1.Create();
+
+ for (ushort i = 0; i < 0xFFFF; i++)
+ {
+ byte[] bytes = BitConverter.GetBytes(i);
+ tmd[482] = bytes[1]; tmd[483] = bytes[0];
+
+ hash = s.ComputeHash(tmd);
+ if (hash[0] == 0x00)
+ { fireDebug(" -> Signed ({0})", i); break; } //Win! It's signed...
+
+ if (i == 0xFFFF - 1)
+ { fireDebug(" -> Signing Failed..."); throw new Exception("Fakesigning failed..."); }
+ }
+
+ s.Clear();
+ }
+
+ writeStream.Seek(0, SeekOrigin.Begin);
+ writeStream.Write(tmd, 0, tmd.Length);
+
+ fireDebug("Writing TMD Finished...");
+ }
+
+ private void updateContents(byte[][] conts)
+ {
+ SHA1 s = SHA1.Create();
+
+ for (int i = 0; i < contents.Count; i++)
+ {
+ contents[i].Size = (ulong)conts[i].Length;
+ contents[i].Hash = s.ComputeHash(conts[i]);
+ }
+
+ s.Clear();
+ }
+
+ private void parseTmd(Stream tmdFile)
+ {
+ fireDebug("Pasing TMD...");
+
+ tmdFile.Seek(0, SeekOrigin.Begin);
+ byte[] temp = new byte[8];
+
+ fireDebug(" Reading Signature Exponent... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(temp, 0, 4);
+ signatureExponent = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ fireDebug(" Reading Signature... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(signature, 0, signature.Length);
+
+ fireDebug(" Reading Padding... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(padding, 0, padding.Length);
+
+ fireDebug(" Reading Issuer... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(issuer, 0, issuer.Length);
+
+ fireDebug(" Reading Version... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ fireDebug(" Reading CA Crl Version... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ fireDebug(" Reading Signer Crl Version... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ fireDebug(" Reading Padding Byte... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(temp, 0, 4);
+ version = temp[0];
+ caCrlVersion = temp[1];
+ signerCrlVersion = temp[2];
+ paddingByte = temp[3];
+
+ fireDebug(" Reading Startup IOS... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(temp, 0, 8);
+ startupIos = Shared.Swap(BitConverter.ToUInt64(temp, 0));
+
+ fireDebug(" Reading Title ID... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(temp, 0, 8);
+ titleId = Shared.Swap(BitConverter.ToUInt64(temp, 0));
+
+ fireDebug(" Reading Title Type... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(temp, 0, 4);
+ titleType = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ fireDebug(" Reading Group ID... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(temp, 0, 2);
+ groupId = Shared.Swap(BitConverter.ToUInt16(temp, 0));
+
+ fireDebug(" Reading Padding2... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(temp, 0, 2);
+ padding2 = Shared.Swap(BitConverter.ToUInt16(temp, 0));
+
+ fireDebug(" Reading Region... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(temp, 0, 2);
+ region = Shared.Swap(BitConverter.ToUInt16(temp, 0));
+
+ fireDebug(" Reading Reserved... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(reserved, 0, reserved.Length);
+
+ fireDebug(" Reading Access Rights... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(temp, 0, 4);
+ accessRights = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ fireDebug(" Reading Title Version... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ fireDebug(" Reading NumOfContents... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ fireDebug(" Reading Boot Index... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ fireDebug(" Reading Padding3... (Offset: 0x{0})", tmdFile.Position.ToString("x8").ToUpper());
+ tmdFile.Read(temp, 0, 8);
+ titleVersion = Shared.Swap(BitConverter.ToUInt16(temp, 0));
+ numOfContents = Shared.Swap(BitConverter.ToUInt16(temp, 2));
+ bootIndex = Shared.Swap(BitConverter.ToUInt16(temp, 4));
+ padding3 = Shared.Swap(BitConverter.ToUInt16(temp, 6));
+
+ contents = new List();
+
+ //Read Contents
+ for (int i = 0; i < numOfContents; i++)
+ {
+ fireDebug(" Reading Content #{0} of {1}... (Offset: 0x{2})", i + 1, numOfContents, tmdFile.Position.ToString("x8").ToUpper().ToUpper());
+
+ TMD_Content tempContent = new TMD_Content();
+ tempContent.Hash = new byte[20];
+
+ tmdFile.Read(temp, 0, 8);
+ tempContent.ContentID = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+ tempContent.Index = Shared.Swap(BitConverter.ToUInt16(temp, 4));
+ tempContent.Type = (ContentType)Shared.Swap(BitConverter.ToUInt16(temp, 6));
+
+ tmdFile.Read(temp, 0, 8);
+ tempContent.Size = Shared.Swap(BitConverter.ToUInt64(temp, 0));
+
+ tmdFile.Read(tempContent.Hash, 0, tempContent.Hash.Length);
+
+ contents.Add(tempContent);
+ }
+
+ fireDebug("Pasing TMD Finished...");
+ }
+
+ private string calculateNandBlocks()
+ {
+ int nandSizeMin = 0;
+ int nandSizeMax = 0;
+
+ for (int i = 0; i < numOfContents; i++)
+ {
+ nandSizeMax += (int)contents[i].Size;
+ if (contents[i].Type == ContentType.Normal) nandSizeMin += (int)contents[i].Size;
+ }
+
+ int blocksMin = (int)Math.Ceiling((double)((double)nandSizeMin / (128 * 1024)));
+ int blocksMax = (int)Math.Ceiling((double)((double)nandSizeMax / (128 * 1024)));
+
+ if (blocksMin == blocksMax) return blocksMax.ToString();
+ else return string.Format("{0} - {1}", blocksMin, blocksMax);
+ }
+ #endregion
+
+ #region Events
+ ///
+ /// Fires debugging messages. You may write them into a log file or log textbox.
+ ///
+ public event EventHandler Debug;
+
+ private void fireDebug(string debugMessage, params object[] args)
+ {
+ EventHandler debug = Debug;
+ if (debug != null)
+ debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
+ }
+ #endregion
+ }
+
+ public class TMD_Content
+ {
+ private uint contentId;
+ private ushort index;
+ private ushort type;
+ private ulong size;
+ private byte[] hash = new byte[20];
+
+ public uint ContentID { get { return contentId; } set { contentId = value; } }
+ public ushort Index { get { return index; } set { index = value; } }
+ public ContentType Type { get { return (ContentType)type; } set { type = (ushort)value; } }
+ public ulong Size { get { return size; } set { size = value; } }
+ public byte[] Hash { get { return hash; } set { hash = value; } }
+ }
+}
diff --git a/NUS Downloader/Ticket.cs b/NUS Downloader/Ticket.cs
new file mode 100644
index 0000000..8f9e071
--- /dev/null
+++ b/NUS Downloader/Ticket.cs
@@ -0,0 +1,609 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace libWiiSharp
+{
+ public enum CommonKeyType : byte
+ {
+ Standard = 0x00,
+ Korean = 0x01,
+ }
+
+ public class Ticket : IDisposable
+ {
+ private byte newKeyIndex = (byte)CommonKeyType.Standard;
+ private byte[] decryptedTitleKey = new byte[16];
+ private bool fakeSign = false;
+ private bool titleKeyChanged = false;
+ private byte[] newEncryptedTitleKey = new byte[0];
+ private bool reDecrypt = false;
+
+ private uint signatureExponent = 0x00010001;
+ private byte[] signature = new byte[256];
+ private byte[] padding = new byte[60];
+ private byte[] issuer = new byte[64];
+ private byte[] unknown = new byte[63];
+ private byte[] encryptedTitleKey = new byte[16];
+ private byte unknown2;
+ private ulong ticketId;
+ private uint consoleId;
+ private ulong titleId;
+ private ushort unknown3 = 0xFFFF;
+ private ushort numOfDlc;
+ private ulong unknown4;
+ private byte padding2;
+ private byte commonKeyIndex = (byte)CommonKeyType.Standard;
+ private byte[] unknown5 = new byte[48];
+ private byte[] unknown6 = new byte[32]; //0xFF
+ private ushort padding3;
+ private uint enableTimeLimit;
+ private uint timeLimit;
+ private byte[] padding4 = new byte[88];
+
+ ///
+ /// The Title Key the WADs content is encrypted with.
+ ///
+ public byte[] TitleKey { get { return decryptedTitleKey; } set { decryptedTitleKey = value; titleKeyChanged = true; reDecrypt = false; } }
+ ///
+ /// Defines which Common Key is used (Standard / Korean).
+ ///
+ public CommonKeyType CommonKeyIndex { get { return (CommonKeyType)newKeyIndex; } set { newKeyIndex = (byte)value; } }
+ ///
+ /// The Ticket ID.
+ ///
+ public ulong TicketID { get { return ticketId; } set { ticketId = value; } }
+ ///
+ /// The Console ID.
+ ///
+ public uint ConsoleID { get { return consoleId; } set { consoleId = value; } }
+ ///
+ /// The Title ID.
+ ///
+ public ulong TitleID { get { return titleId; } set { titleId = value; if (reDecrypt) reDecryptTitleKey(); } }
+ ///
+ /// Number of DLC.
+ ///
+ public ushort NumOfDLC { get { return numOfDlc; } set { numOfDlc = value; } }
+ ///
+ /// If true, the Ticket will be fakesigned while saving.
+ ///
+ public bool FakeSign { get { return fakeSign; } set { fakeSign = value; } }
+ ///
+ /// True if the Title Key was changed.
+ ///
+ public bool TitleKeyChanged { get { return titleKeyChanged; } }
+
+ #region IDisposable Members
+ private bool isDisposed = false;
+
+ ~Ticket()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && !isDisposed)
+ {
+ decryptedTitleKey = null;
+ newEncryptedTitleKey = null;
+ signature = null;
+ padding = null;
+ issuer = null;
+ unknown = null;
+ encryptedTitleKey = null;
+ unknown5 = null;
+ unknown6 = null;
+ padding4 = null;
+ }
+
+ isDisposed = true;
+ }
+ #endregion
+
+ #region Public Functions
+ ///
+ /// Loads a tik file.
+ ///
+ ///
+ ///
+ public static Ticket Load(string pathToTicket)
+ {
+ return Load(File.ReadAllBytes(pathToTicket));
+ }
+
+ ///
+ /// Loads a tik file.
+ ///
+ ///
+ ///
+ public static Ticket Load(byte[] ticket)
+ {
+ Ticket tik = new Ticket();
+ MemoryStream ms = new MemoryStream(ticket);
+
+ try { tik.parseTicket(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ return tik;
+ }
+
+ ///
+ /// Loads a tik file.
+ ///
+ ///
+ ///
+ public static Ticket Load(Stream ticket)
+ {
+ Ticket tik = new Ticket();
+ tik.parseTicket(ticket);
+ return tik;
+ }
+
+
+
+ ///
+ /// Loads a tik file.
+ ///
+ ///
+ public void LoadFile(string pathToTicket)
+ {
+ LoadFile(File.ReadAllBytes(pathToTicket));
+ }
+
+ ///
+ /// Loads a tik file.
+ ///
+ ///
+ public void LoadFile(byte[] ticket)
+ {
+ MemoryStream ms = new MemoryStream(ticket);
+
+ try { parseTicket(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ }
+
+ ///
+ /// Loads a tik file.
+ ///
+ ///
+ public void LoadFile(Stream ticket)
+ {
+ parseTicket(ticket);
+ }
+
+
+
+ ///
+ /// Saves the Ticket.
+ ///
+ ///
+ public void Save(string savePath)
+ {
+ Save(savePath, false);
+ }
+
+ ///
+ /// Saves the Ticket. If fakeSign is true, the Ticket will be fakesigned.
+ ///
+ ///
+ ///
+ public void Save(string savePath, bool fakeSign)
+ {
+ if (fakeSign) this.fakeSign = true;
+ if (File.Exists(savePath)) File.Delete(savePath);
+
+ using (FileStream fs = new FileStream(savePath, FileMode.Create))
+ writeToStream(fs);
+ }
+
+ ///
+ /// Returns the Ticket as a memory stream.
+ ///
+ ///
+ public MemoryStream ToMemoryStream()
+ {
+ return ToMemoryStream(false);
+ }
+
+ ///
+ /// Returns the Ticket as a memory stream. If fakeSign is true, the Ticket will be fakesigned.
+ ///
+ ///
+ ///
+ public MemoryStream ToMemoryStream(bool fakeSign)
+ {
+ if (fakeSign) this.fakeSign = true;
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ return ms;
+ }
+
+ ///
+ /// Returns the Ticket as a byte array.
+ ///
+ ///
+ public byte[] ToByteArray()
+ {
+ return ToByteArray(false);
+ }
+
+ ///
+ /// Returns the Ticket as a byte array. If fakeSign is true, the Ticket will be fakesigned.
+ ///
+ ///
+ ///
+ public byte[] ToByteArray(bool fakeSign)
+ {
+ if (fakeSign) this.fakeSign = true;
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ byte[] res = ms.ToArray();
+ ms.Dispose();
+ return res;
+ }
+
+ ///
+ /// This will set a new encrypted Title Key (i.e. the one that you can "read" in the Ticket).
+ ///
+ ///
+ public void SetTitleKey(string newTitleKey)
+ {
+ SetTitleKey(newTitleKey.ToCharArray());
+ }
+
+ ///
+ /// This will set a new encrypted Title Key (i.e. the one that you can "read" in the Ticket).
+ ///
+ ///
+ public void SetTitleKey(char[] newTitleKey)
+ {
+ if (newTitleKey.Length != 16)
+ throw new Exception("The title key must be 16 characters long!");
+
+ for (int i = 0; i < 16; i++)
+ encryptedTitleKey[i] = (byte)newTitleKey[i];
+
+ decryptTitleKey();
+ titleKeyChanged = true;
+
+ reDecrypt = true;
+ newEncryptedTitleKey = encryptedTitleKey;
+ }
+
+ ///
+ /// This will set a new encrypted Title Key (i.e. the one that you can "read" in the Ticket).
+ ///
+ ///
+ public void SetTitleKey(byte[] newTitleKey)
+ {
+ if (newTitleKey.Length != 16)
+ throw new Exception("The title key must be 16 characters long!");
+
+ encryptedTitleKey = newTitleKey;
+ decryptTitleKey();
+ titleKeyChanged = true;
+
+ reDecrypt = true;
+ newEncryptedTitleKey = newTitleKey;
+ }
+
+ ///
+ /// Returns the Upper Title ID as a string.
+ ///
+ ///
+ public string GetUpperTitleID()
+ {
+ byte[] titleBytes = BitConverter.GetBytes(Shared.Swap((uint)titleId));
+ return new string(new char[] { (char)titleBytes[0], (char)titleBytes[1], (char)titleBytes[2], (char)titleBytes[3] });
+ }
+ #endregion
+
+ #region Private Functions
+ private void writeToStream(Stream writeStream)
+ {
+ fireDebug("Writing Ticket...");
+
+ fireDebug(" Encrypting Title Key...");
+ encryptTitleKey();
+ fireDebug(" -> Decrypted Title Key: {0}", Shared.ByteArrayToString(decryptedTitleKey));
+ fireDebug(" -> Encrypted Title Key: {0}", Shared.ByteArrayToString(encryptedTitleKey));
+
+ if (fakeSign)
+ { fireDebug(" Clearing Signature..."); signature = new byte[256]; } //Clear Signature if we fake Sign
+
+ MemoryStream ms = new MemoryStream();
+ ms.Seek(0, SeekOrigin.Begin);
+
+ fireDebug(" Writing Signature Exponent... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(signatureExponent)), 0, 4);
+
+ fireDebug(" Writing Signature... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(signature, 0, signature.Length);
+
+ fireDebug(" Writing Padding... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(padding, 0, padding.Length);
+
+ fireDebug(" Writing Issuer... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(issuer, 0, issuer.Length);
+
+ fireDebug(" Writing Unknown... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(unknown, 0, unknown.Length);
+
+ fireDebug(" Writing Title Key... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(encryptedTitleKey, 0, encryptedTitleKey.Length);
+
+ fireDebug(" Writing Unknown2... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.WriteByte(unknown2);
+
+ fireDebug(" Writing Ticket ID... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(ticketId)), 0, 8);
+
+ fireDebug(" Writing Console ID... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(consoleId)), 0, 4);
+
+ fireDebug(" Writing Title ID... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(titleId)), 0, 8);
+
+ fireDebug(" Writing Unknwon3... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(unknown3)), 0, 2);
+
+ fireDebug(" Writing NumOfDLC... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(numOfDlc)), 0, 2);
+
+ fireDebug(" Writing Unknwon4... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(unknown4)), 0, 8);
+
+ fireDebug(" Writing Padding2... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.WriteByte(padding2);
+
+ fireDebug(" Writing Common Key Index... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.WriteByte(commonKeyIndex);
+
+ fireDebug(" Writing Unknown5... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(unknown5, 0, unknown5.Length);
+
+ fireDebug(" Writing Unknown6... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(unknown6, 0, unknown6.Length);
+
+ fireDebug(" Writing Padding3... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(padding3)), 0, 2);
+
+ fireDebug(" Writing Enable Time Limit... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(enableTimeLimit)), 0, 4);
+
+ fireDebug(" Writing Time Limit... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(BitConverter.GetBytes(Shared.Swap(timeLimit)), 0, 4);
+
+ fireDebug(" Writing Padding4... (Offset: 0x{0})", ms.Position.ToString("x8").ToUpper());
+ ms.Write(padding4, 0, padding4.Length);
+
+ byte[] tik = ms.ToArray();
+ ms.Dispose();
+
+ //fake Sign
+ if (fakeSign)
+ {
+ fireDebug(" Fakesigning Ticket...");
+
+ byte[] hash = new byte[20];
+ SHA1 s = SHA1.Create();
+
+ for (ushort i = 0; i < 0xFFFF; i++)
+ {
+ byte[] bytes = BitConverter.GetBytes(i);
+ tik[498] = bytes[1]; tik[499] = bytes[0];
+
+ hash = s.ComputeHash(tik);
+ if (hash[0] == 0x00)
+ { fireDebug(" -> Signed ({0})", i); break; } //Win! It's signed...
+
+ if (i == 0xFFFF - 1)
+ { fireDebug(" -> Signing Failed..."); throw new Exception("Fakesigning failed..."); }
+ }
+
+ s.Clear();
+ }
+
+ writeStream.Seek(0, SeekOrigin.Begin);
+ writeStream.Write(tik, 0, tik.Length);
+
+ fireDebug("Writing Ticket Finished...");
+ }
+
+ private void parseTicket(Stream ticketFile)
+ {
+ fireDebug("Parsing Ticket...");
+
+ ticketFile.Seek(0, SeekOrigin.Begin);
+ byte[] temp = new byte[8];
+
+ fireDebug(" Reading Signature Exponent... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(temp, 0, 4);
+ signatureExponent = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ fireDebug(" Reading Signature... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(signature, 0, signature.Length);
+
+ fireDebug(" Reading Padding... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(padding, 0, padding.Length);
+
+ fireDebug(" Reading Issuer... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(issuer, 0, issuer.Length);
+
+ fireDebug(" Reading Unknown... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(unknown, 0, unknown.Length);
+
+ fireDebug(" Reading Title Key... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(encryptedTitleKey, 0, encryptedTitleKey.Length);
+
+ fireDebug(" Reading Unknown2... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ unknown2 = (byte)ticketFile.ReadByte();
+
+ fireDebug(" Reading Ticket ID.. (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(temp, 0, 8);
+ ticketId = Shared.Swap(BitConverter.ToUInt64(temp, 0));
+
+ fireDebug(" Reading Console ID... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(temp, 0, 4);
+ consoleId = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ fireDebug(" Reading Title ID... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(temp, 0, 8);
+ titleId = Shared.Swap(BitConverter.ToUInt64(temp, 0));
+
+ fireDebug(" Reading Unknown3... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ fireDebug(" Reading NumOfDLC... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(temp, 0, 4);
+ unknown3 = Shared.Swap(BitConverter.ToUInt16(temp, 0));
+ numOfDlc = Shared.Swap(BitConverter.ToUInt16(temp, 2));
+
+ fireDebug(" Reading Unknown4... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(temp, 0, 8);
+ unknown4 = Shared.Swap(BitConverter.ToUInt64(temp, 0));
+
+ fireDebug(" Reading Padding2... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ padding2 = (byte)ticketFile.ReadByte();
+
+ fireDebug(" Reading Common Key Index... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ commonKeyIndex = (byte)ticketFile.ReadByte();
+
+ newKeyIndex = commonKeyIndex;
+
+ fireDebug(" Reading Unknown5... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(unknown5, 0, unknown5.Length);
+
+ fireDebug(" Reading Unknown6... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(unknown6, 0, unknown6.Length);
+
+ fireDebug(" Reading Padding3... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(temp, 0, 2);
+ padding3 = Shared.Swap(BitConverter.ToUInt16(temp, 0));
+
+ fireDebug(" Reading Enable Time Limit... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ fireDebug(" Reading Time Limit... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(temp, 0, 8);
+ enableTimeLimit = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+ timeLimit = Shared.Swap(BitConverter.ToUInt32(temp, 4));
+
+ fireDebug(" Reading Padding4... (Offset: 0x{0})", ticketFile.Position.ToString("x8").ToUpper());
+ ticketFile.Read(padding4, 0, padding4.Length);
+
+ fireDebug(" Decrypting Title Key...");
+ decryptTitleKey();
+ fireDebug(" -> Encrypted Title Key: {0}", Shared.ByteArrayToString(encryptedTitleKey));
+ fireDebug(" -> Decrypted Title Key: {0}", Shared.ByteArrayToString(decryptedTitleKey));
+
+ fireDebug("Parsing Ticket Finished...");
+ }
+
+ private void decryptTitleKey()
+ {
+ byte[] ckey = (commonKeyIndex == 0x01) ? CommonKey.GetKoreanKey() : CommonKey.GetStandardKey();
+ byte[] iv = BitConverter.GetBytes(Shared.Swap(titleId));
+ Array.Resize(ref iv, 16);
+
+ RijndaelManaged rm = new RijndaelManaged();
+ rm.Mode = CipherMode.CBC;
+ rm.Padding = PaddingMode.None;
+ rm.KeySize = 128;
+ rm.BlockSize = 128;
+ rm.Key = ckey;
+ rm.IV = iv;
+
+ ICryptoTransform decryptor = rm.CreateDecryptor();
+
+ MemoryStream ms = new MemoryStream(encryptedTitleKey);
+ CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
+
+ cs.Read(decryptedTitleKey, 0, decryptedTitleKey.Length);
+
+ cs.Dispose();
+ ms.Dispose();
+ decryptor.Dispose();
+ rm.Clear();
+ }
+
+ private void encryptTitleKey()
+ {
+ commonKeyIndex = newKeyIndex;
+ byte[] ckey = (commonKeyIndex == 0x01) ? CommonKey.GetKoreanKey() : CommonKey.GetStandardKey();
+ byte[] iv = BitConverter.GetBytes(Shared.Swap(titleId));
+ Array.Resize(ref iv, 16);
+
+ RijndaelManaged rm = new RijndaelManaged();
+ rm.Mode = CipherMode.CBC;
+ rm.Padding = PaddingMode.None;
+ rm.KeySize = 128;
+ rm.BlockSize = 128;
+ rm.Key = ckey;
+ rm.IV = iv;
+
+ ICryptoTransform encryptor = rm.CreateEncryptor();
+
+ MemoryStream ms = new MemoryStream(decryptedTitleKey);
+ CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Read);
+
+ cs.Read(encryptedTitleKey, 0, encryptedTitleKey.Length);
+
+ cs.Dispose();
+ ms.Dispose();
+ encryptor.Dispose();
+ rm.Clear();
+ }
+
+ private void reDecryptTitleKey()
+ {
+ encryptedTitleKey = newEncryptedTitleKey;
+ decryptTitleKey();
+ }
+ #endregion
+
+ #region Events
+ ///
+ /// Fires debugging messages. You may write them into a log file or log textbox.
+ ///
+ public event EventHandler Debug;
+
+ private void fireDebug(string debugMessage, params object[] args)
+ {
+ EventHandler debug = Debug;
+ if (debug != null)
+ debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
+ }
+ #endregion
+ }
+}
diff --git a/NUS Downloader/U8.cs b/NUS Downloader/U8.cs
new file mode 100644
index 0000000..409d3af
--- /dev/null
+++ b/NUS Downloader/U8.cs
@@ -0,0 +1,1120 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace libWiiSharp
+{
+ public enum U8_NodeType : ushort
+ {
+ File = 0x0000,
+ Directory = 0x0100,
+ }
+
+ public class U8 : IDisposable
+ {
+ private const int dataPadding = 32;
+ private Headers.HeaderType headerType = Headers.HeaderType.None;
+ private object header = null;
+ private U8_Header u8Header = new U8_Header();
+ private U8_Node rootNode = new U8_Node();
+ private List u8Nodes = new List();
+ private List stringTable = new List();
+ private List data = new List();
+ private int iconSize = -1;
+ private int bannerSize = -1;
+ private int soundSize = -1;
+ private bool lz77 = false;
+
+ ///
+ /// The type of the Header of the U8 file.
+ ///
+ public Headers.HeaderType HeaderType { get { return headerType; } }
+ ///
+ /// The Header of the U8 file as an object. Will be null if the file has no Header.
+ ///
+ public object Header { get { return header; } }
+ ///
+ /// The Rootnode of the U8 file.
+ ///
+ public U8_Node RootNode { get { return rootNode; } }
+ ///
+ /// The Nodes of the U8 file.
+ ///
+ public List Nodes { get { return u8Nodes; } }
+ ///
+ /// The string table of the U8 file.
+ ///
+ public string[] StringTable { get { return stringTable.ToArray(); } }
+ ///
+ /// The actual data (files) in the U8 file. Will be an empty byte array for directory entries.
+ ///
+ public byte[][] Data { get { return data.ToArray(); } }
+
+ ///
+ /// The Number of Nodes WITHOUT the Rootnode.
+ ///
+ public int NumOfNodes { get { return (int)rootNode.SizeOfData - 1; } }
+ ///
+ /// The size of the icon.bin (if the U8 files contains an icon.bin).
+ ///
+ public int IconSize { get { return iconSize; } }
+ ///
+ /// The size of the banner.bin (if the U8 files contains an banner.bin).
+ ///
+ public int BannerSize { get { return bannerSize; } }
+ ///
+ /// The size of the sound.bin (if the U8 files contains an sound.bin).
+ ///
+ public int SoundSize { get { return soundSize; } }
+ ///
+ /// If true, the U8 file will be Lz77 compressed while saving.
+ ///
+ public bool Lz77Compress { get { return lz77; } set { lz77 = value; } }
+
+ public U8()
+ {
+ rootNode.Type = U8_NodeType.Directory;
+ }
+
+ #region IDisposable Members
+ private bool isDisposed = false;
+
+ ~U8()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && !isDisposed)
+ {
+ header = null;
+ u8Header = null;
+ rootNode = null;
+
+ u8Nodes.Clear();
+ u8Nodes = null;
+
+ stringTable.Clear();
+ stringTable = null;
+
+ data.Clear();
+ data = null;
+ }
+
+ isDisposed = true;
+ }
+ #endregion
+
+ #region Public Functions
+ ///
+ /// Checks whether a file is a U8 file or not.
+ ///
+ ///
+ ///
+ public static bool IsU8(string pathToFile)
+ {
+ return IsU8(File.ReadAllBytes(pathToFile));
+ }
+
+ ///
+ /// Checks whether a file is a U8 file or not.
+ ///
+ ///
+ ///
+ public static bool IsU8(byte[] file)
+ {
+ if (Lz77.IsLz77Compressed(file))
+ {
+ byte[] partOfFile = new byte[(file.Length > 2000) ? 2000 : file.Length];
+
+ for (int i = 0; i < partOfFile.Length; i++)
+ partOfFile[i] = file[i];
+
+ Lz77 l = new Lz77();
+ partOfFile = l.Decompress(partOfFile);
+
+ return IsU8(partOfFile);
+ }
+ else
+ {
+ Headers.HeaderType h = Headers.DetectHeader(file);
+ return (Shared.Swap(BitConverter.ToUInt32(file, (int)h)) == 0x55AA382D);
+ }
+ }
+
+
+
+ ///
+ /// Loads a U8 file.
+ ///
+ ///
+ ///
+ public static U8 Load(string pathToU8)
+ {
+ return Load(File.ReadAllBytes(pathToU8));
+ }
+
+ ///
+ /// Loads a U8 file.
+ ///
+ ///
+ ///
+ public static U8 Load(byte[] u8File)
+ {
+ U8 u = new U8();
+ MemoryStream ms = new MemoryStream(u8File);
+
+ try { u.parseU8(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ return u;
+ }
+
+ ///
+ /// Loads a U8 file.
+ ///
+ ///
+ ///
+ public static U8 Load(Stream u8File)
+ {
+ U8 u = new U8();
+ u.parseU8(u8File);
+ return u;
+ }
+
+ ///
+ /// Creates a U8 file.
+ ///
+ ///
+ ///
+ public static U8 FromDirectory(string pathToDirectory)
+ {
+ U8 u = new U8();
+ u.createFromDir(pathToDirectory);
+ return u;
+ }
+
+
+
+ ///
+ /// Loads a U8 file.
+ ///
+ ///
+ public void LoadFile(string pathToU8)
+ {
+ LoadFile(File.ReadAllBytes(pathToU8));
+ }
+
+ ///
+ /// Loads a U8 file.
+ ///
+ ///
+ public void LoadFile(byte[] u8File)
+ {
+ MemoryStream ms = new MemoryStream(u8File);
+
+ try { parseU8(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ }
+
+ ///
+ /// Loads a U8 file.
+ ///
+ ///
+ public void LoadFile(Stream u8File)
+ {
+ parseU8(u8File);
+ }
+
+ ///
+ /// Creates a U8 file.
+ ///
+ ///
+ public void CreateFromDirectory(string pathToDirectory)
+ {
+ createFromDir(pathToDirectory);
+ }
+
+
+
+ ///
+ /// Saves the U8 file.
+ ///
+ ///
+ public void Save(string savePath)
+ {
+ if (File.Exists(savePath)) File.Delete(savePath);
+
+ using (FileStream fs = new FileStream(savePath, FileMode.Create))
+ writeToStream(fs);
+ }
+
+ ///
+ /// Returns the U8 file as a memory stream.
+ ///
+ ///
+ public MemoryStream ToMemoryStream()
+ {
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ return ms;
+ }
+
+ ///
+ /// Returns the U8 file as a byte array.
+ ///
+ ///
+ public byte[] ToByteArray()
+ {
+ return ToMemoryStream().ToArray();
+ }
+
+ ///
+ /// Unpacks the U8 file to given directory.
+ ///
+ ///
+ public void Unpack(string saveDir)
+ {
+ unpackToDir(saveDir);
+ }
+
+ ///
+ /// Unpacks the U8 file to given directory.
+ ///
+ ///
+ public void Extract(string saveDir)
+ {
+ unpackToDir(saveDir);
+ }
+
+ ///
+ /// Adds an IMET Header to the U8 file.
+ ///
+ ///
+ ///
+ public void AddHeaderImet(bool shortImet, params string[] titles)
+ {
+ if (iconSize == -1)
+ throw new Exception("icon.bin wasn't found!");
+ else if (bannerSize == -1)
+ throw new Exception("banner.bin wasn't found!");
+ else if (soundSize == -1)
+ throw new Exception("sound.bin wasn't found!");
+
+ header = Headers.IMET.Create(shortImet, iconSize, bannerSize, soundSize, titles);
+ headerType = (shortImet) ? Headers.HeaderType.ShortIMET : Headers.HeaderType.IMET;
+ }
+
+ ///
+ /// Adds an IMD5 Header to the U8 file.
+ ///
+ public void AddHeaderImd5()
+ {
+ headerType = Headers.HeaderType.IMD5;
+ }
+
+ ///
+ /// Replaces the file with the given index.
+ ///
+ ///
+ ///
+ public void ReplaceFile(int fileIndex, string pathToNewFile, bool changeFileName = false)
+ {
+ if (u8Nodes[fileIndex].Type == U8_NodeType.Directory)
+ throw new Exception("You can't replace a directory with a file!");
+
+ data[fileIndex] = File.ReadAllBytes(pathToNewFile);
+ if (changeFileName) stringTable[fileIndex] = Path.GetFileName(pathToNewFile);
+
+ if (stringTable[fileIndex].ToLower() == "icon.bin")
+ iconSize = getRealSize(File.ReadAllBytes(pathToNewFile));
+ else if (stringTable[fileIndex].ToLower() == "banner.bin")
+ bannerSize = getRealSize(File.ReadAllBytes(pathToNewFile));
+ else if (stringTable[fileIndex].ToLower() == "sound.bin")
+ soundSize = getRealSize(File.ReadAllBytes(pathToNewFile));
+ }
+
+ ///
+ /// Replaces the file with the given index.
+ ///
+ ///
+ ///
+ public void ReplaceFile(int fileIndex, byte[] newData)
+ {
+ if (u8Nodes[fileIndex].Type == U8_NodeType.Directory)
+ throw new Exception("You can't replace a directory with a file!");
+
+ data[fileIndex] = newData;
+
+ if (stringTable[fileIndex].ToLower() == "icon.bin")
+ iconSize = getRealSize(newData);
+ else if (stringTable[fileIndex].ToLower() == "banner.bin")
+ bannerSize = getRealSize(newData);
+ else if (stringTable[fileIndex].ToLower() == "sound.bin")
+ soundSize = getRealSize(newData);
+ }
+
+ ///
+ /// Returns the index of the directory or file with the given name.
+ /// If no matching Node is found, -1 will be returned.
+ ///
+ ///
+ ///
+ public int GetNodeIndex(string fileOrDirName)
+ {
+ for (int i = 0; i < u8Nodes.Count; i++)
+ if (stringTable[i].ToLower() == fileOrDirName.ToLower()) return i;
+
+ return -1;
+ }
+
+ ///
+ /// Changes the name of a node.
+ ///
+ ///
+ ///
+ public void RenameNode(int index, string newName)
+ {
+ stringTable[index] = newName;
+ }
+
+ ///
+ /// Changes the name of a node.
+ ///
+ ///
+ ///
+ public void RenameNode(string oldName, string newName)
+ {
+ stringTable[GetNodeIndex(oldName)] = newName;
+ }
+
+ ///
+ /// Adds a directory to the U8 file.
+ /// Path must be like this: "/arc/timg/newFolder".
+ ///
+ ///
+ public void AddDirectory(string path)
+ {
+ addEntry(path, new byte[0]);
+ }
+
+ ///
+ /// Adds a file to the U8 file.
+ /// Path must be like this: "/arc/timg/newFile.tpl".
+ ///
+ ///
+ ///
+ public void AddFile(string path, byte[] data)
+ {
+ addEntry(path, data);
+ }
+
+ ///
+ /// Removes a directory from the U8 file.
+ /// If the directory contains files/dirs, they will also be deleted.
+ /// Path must be like this: "/arc/timg/folderToDelete".
+ ///
+ ///
+ public void RemoveDirectory(string path)
+ {
+ removeEntry(path);
+ }
+
+ ///
+ /// Removes a file from the U8 file.
+ /// Path must be like this: "/arc/timg/fileToDelete.tpl".
+ ///
+ ///
+ public void RemoveFile(string path)
+ {
+ removeEntry(path);
+ }
+ #endregion
+
+ #region Private Functions
+ private void writeToStream(Stream writeStream)
+ {
+ fireDebug("Writing U8 File...");
+
+ //Update Rootnode
+ fireDebug(" Updating Rootnode...");
+ rootNode.SizeOfData = (uint)u8Nodes.Count + 1;
+
+ MemoryStream u8Stream = new MemoryStream();
+
+ //Write Stringtable
+ u8Stream.Seek(u8Header.OffsetToRootNode + ((u8Nodes.Count + 1) * 12), SeekOrigin.Begin);
+
+ fireDebug(" Writing String Table... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper());
+ u8Stream.WriteByte(0x00);
+
+ int stringTablePosition = (int)u8Stream.Position - 1;
+ for (int i = 0; i < u8Nodes.Count; i++)
+ {
+ fireDebug(" -> Entry #{1} of {2}: \"{3}\"... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper(), i + 1, u8Nodes.Count, stringTable[i]);
+
+ u8Nodes[i].OffsetToName = (ushort)(u8Stream.Position - stringTablePosition);
+ byte[] stringBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(stringTable[i]);
+
+ u8Stream.Write(stringBytes, 0, stringBytes.Length);
+ u8Stream.WriteByte(0x00);
+ }
+
+ u8Header.HeaderSize = (uint)(u8Stream.Position - u8Header.OffsetToRootNode);
+ u8Header.OffsetToData = 0;
+
+ //Write Data
+ for (int i = 0; i < u8Nodes.Count; i++)
+ {
+ fireProgress((i + 1) * 100 / u8Nodes.Count);
+
+ if (u8Nodes[i].Type == U8_NodeType.File)
+ {
+ u8Stream.Seek(Shared.AddPadding((int)u8Stream.Position, dataPadding), SeekOrigin.Begin);
+
+ fireDebug(" Writing Data #{1} of {2}... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper(), i + 1, u8Nodes.Count);
+
+ if (u8Header.OffsetToData == 0) u8Header.OffsetToData = (uint)u8Stream.Position;
+ u8Nodes[i].OffsetToData = (uint)u8Stream.Position;
+ u8Nodes[i].SizeOfData = (uint)data[i].Length;
+
+ u8Stream.Write(data[i], 0, data[i].Length);
+ }
+ else fireDebug(" Node #{0} of {1} is a Directory...", i + 1, u8Nodes.Count);
+ }
+
+ //Pad End to 16 bytes
+ while (u8Stream.Position % 16 != 0)
+ u8Stream.WriteByte(0x00);
+
+ //Write Header + Nodes
+ u8Stream.Seek(0, SeekOrigin.Begin);
+
+ fireDebug(" Writing Header... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper());
+ u8Header.Write(u8Stream);
+
+ fireDebug(" Writing Rootnode... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper());
+ rootNode.Write(u8Stream);
+
+ for (int i = 0; i < u8Nodes.Count; i++)
+ {
+ fireDebug(" Writing Node Entry #{1} of {2}... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper(), i + 1, u8Nodes.Count);
+ u8Nodes[i].Write(u8Stream);
+ }
+
+ byte[] u8Array = u8Stream.ToArray();
+ u8Stream.Dispose();
+
+ if (lz77)
+ {
+ fireDebug(" Lz77 Compressing U8 File...");
+
+ Lz77 l = new Lz77();
+ u8Array = l.Compress(u8Array);
+ }
+
+ //Write File Header
+ if (headerType == Headers.HeaderType.IMD5)
+ {
+ fireDebug(" Adding IMD5 Header...");
+
+ writeStream.Seek(0, SeekOrigin.Begin);
+ Headers.IMD5 h = Headers.IMD5.Create(u8Array);
+ h.Write(writeStream);
+ }
+ else if (headerType == Headers.HeaderType.IMET || headerType == Headers.HeaderType.ShortIMET)
+ {
+ fireDebug(" Adding IMET Header...");
+
+ ((Headers.IMET)header).IconSize = (uint)iconSize;
+ ((Headers.IMET)header).BannerSize = (uint)bannerSize;
+ ((Headers.IMET)header).SoundSize = (uint)soundSize;
+
+ writeStream.Seek(0, SeekOrigin.Begin);
+ ((Headers.IMET)header).Write(writeStream);
+ }
+
+ writeStream.Write(u8Array, 0, u8Array.Length);
+
+ fireDebug("Writing U8 File Finished...");
+ }
+
+ private void unpackToDir(string saveDir)
+ {
+ fireDebug("Unpacking U8 File to: {0}", saveDir);
+
+ if (!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir);
+
+ string[] dirs = new string[u8Nodes.Count];
+ dirs[0] = saveDir;
+ int[] dirCount = new int[u8Nodes.Count];
+ int dirIndex = 0;
+
+ for (int i = 0; i < u8Nodes.Count; i++)
+ {
+ fireDebug(" Unpacking Entry #{0} of {1}", i + 1, u8Nodes.Count);
+ fireProgress((i + 1) * 100 / u8Nodes.Count);
+
+ switch (u8Nodes[i].Type)
+ {
+ case U8_NodeType.Directory:
+ fireDebug(" -> Directory: \"{0}\"", stringTable[i]);
+
+ if (dirs[dirIndex][dirs[dirIndex].Length - 1] != Path.DirectorySeparatorChar) { dirs[dirIndex] = dirs[dirIndex] + Path.DirectorySeparatorChar; }
+ Directory.CreateDirectory(dirs[dirIndex] + stringTable[i]);
+ dirs[dirIndex + 1] = dirs[dirIndex] + stringTable[i];
+ dirIndex++;
+ dirCount[dirIndex] = (int)u8Nodes[i].SizeOfData;
+ break;
+ default:
+ fireDebug(" -> File: \"{0}\"", stringTable[i]);
+ fireDebug(" -> Size: {0} bytes", data[i].Length);
+
+ using (FileStream fs = new FileStream(dirs[dirIndex] + Path.DirectorySeparatorChar + stringTable[i], FileMode.Create))
+ fs.Write(data[i], 0, data[i].Length);
+ break;
+ }
+
+ while (dirIndex > 0 && dirCount[dirIndex] == i + 2)
+ { dirIndex--; }
+ }
+
+ fireDebug("Unpacking U8 File Finished");
+ }
+
+ private void parseU8(Stream u8File)
+ {
+ fireDebug("Pasing U8 File...");
+
+ u8Header = new U8_Header();
+ rootNode = new U8_Node();
+ u8Nodes = new List();
+ stringTable = new List();
+ data = new List();
+
+ fireDebug(" Detecting Header...");
+ headerType = Headers.DetectHeader(u8File);
+ Headers.HeaderType tempType = headerType;
+
+ fireDebug(" -> {0}", headerType.ToString());
+
+ if (headerType == Headers.HeaderType.IMD5)
+ {
+ fireDebug(" Reading IMD5 Header...");
+ header = Headers.IMD5.Load(u8File);
+
+ byte[] file = new byte[u8File.Length];
+ u8File.Read(file, 0, file.Length);
+
+ MD5 m = MD5.Create();
+ byte[] newHash = m.ComputeHash(file, (int)headerType, (int)u8File.Length - (int)headerType);
+ m.Clear();
+
+ if (!Shared.CompareByteArrays(newHash, ((Headers.IMD5)header).Hash))
+ {
+ fireDebug(@"/!\ /!\ /!\ Hashes do not match /!\ /!\ /!\");
+ fireWarning(string.Format("Hashes of IMD5 header and file do not match! The content might be corrupted!"));
+ }
+ }
+ else if (headerType == Headers.HeaderType.IMET || headerType == Headers.HeaderType.ShortIMET)
+ {
+ fireDebug(" Reading IMET Header...");
+ header = Headers.IMET.Load(u8File);
+
+ if (!((Headers.IMET)header).HashesMatch)
+ {
+ fireDebug(@"/!\ /!\ /!\ Hashes do not match /!\ /!\ /!\");
+ fireWarning(string.Format("The hash stored in the IMET header doesn't match the headers hash! The header and/or file might be corrupted!"));
+ }
+ }
+
+ fireDebug(" Checking for Lz77 Compression...");
+ if (Lz77.IsLz77Compressed(u8File))
+ {
+ fireDebug(" -> Lz77 Compression Found...");
+ fireDebug(" Decompressing U8 Data...");
+
+ Lz77 l = new Lz77();
+ Stream decompressedFile = l.Decompress(u8File);
+
+ tempType = Headers.DetectHeader(decompressedFile);
+ u8File = decompressedFile;
+
+ lz77 = true;
+ }
+
+ u8File.Seek((int)tempType, SeekOrigin.Begin);
+ byte[] temp = new byte[4];
+
+ //Read U8 Header
+ fireDebug(" Reading U8 Header: Magic... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper());
+ u8File.Read(temp, 0, 4);
+ if (Shared.Swap(BitConverter.ToUInt32(temp, 0)) != u8Header.U8Magic)
+ { fireDebug(" -> Invalid Magic!"); throw new Exception("U8 Header: Invalid Magic!"); }
+
+ fireDebug(" Reading U8 Header: Offset to Rootnode... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper());
+ u8File.Read(temp, 0, 4);
+ if (Shared.Swap(BitConverter.ToUInt32(temp, 0)) != u8Header.OffsetToRootNode)
+ { fireDebug(" -> Invalid Offset to Rootnode"); throw new Exception("U8 Header: Invalid Offset to Rootnode!"); }
+
+ fireDebug(" Reading U8 Header: Header Size... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper());
+ u8File.Read(temp, 0, 4);
+ u8Header.HeaderSize = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ fireDebug(" Reading U8 Header: Offset to Data... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper());
+ u8File.Read(temp, 0, 4);
+ u8Header.OffsetToData = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ u8File.Seek(16, SeekOrigin.Current);
+
+ //Read Rootnode
+ fireDebug(" Reading Rootnode... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper());
+ u8File.Read(temp, 0, 4);
+ rootNode.Type = (U8_NodeType)Shared.Swap(BitConverter.ToUInt16(temp, 0));
+ rootNode.OffsetToName = Shared.Swap(BitConverter.ToUInt16(temp, 2));
+
+ u8File.Read(temp, 0, 4);
+ rootNode.OffsetToData = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ u8File.Read(temp, 0, 4);
+ rootNode.SizeOfData = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ int stringTablePosition = (int)((int)tempType + u8Header.OffsetToRootNode + rootNode.SizeOfData * 12);
+ int nodePosition = (int)u8File.Position;
+
+ //Read Nodes
+ for (int i = 0; i < rootNode.SizeOfData - 1; i++)
+ {
+ fireDebug(" Reading Node #{1} of {2}... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper(), i + 1, rootNode.SizeOfData - 1);
+ fireProgress((int)((i + 1) * 100 / (rootNode.SizeOfData - 1)));
+
+ U8_Node tempNode = new U8_Node();
+ string tempName = string.Empty;
+ byte[] tempData = new byte[0];
+
+ //Read Node Entry
+ u8File.Seek(nodePosition, SeekOrigin.Begin);
+
+ fireDebug(" -> Reading Node Entry... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper());
+ u8File.Read(temp, 0, 4);
+ tempNode.Type = (U8_NodeType)Shared.Swap(BitConverter.ToUInt16(temp, 0));
+ tempNode.OffsetToName = Shared.Swap(BitConverter.ToUInt16(temp, 2));
+
+ u8File.Read(temp, 0, 4);
+ tempNode.OffsetToData = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ u8File.Read(temp, 0, 4);
+ tempNode.SizeOfData = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ nodePosition = (int)u8File.Position;
+
+ fireDebug(" -> {0}", tempNode.Type.ToString());
+
+ //Read Node Name
+ u8File.Seek(stringTablePosition + tempNode.OffsetToName, SeekOrigin.Begin);
+
+ fireDebug(" -> Reading Node Name... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper());
+ for (;;)
+ {
+ char tempChar = (char)u8File.ReadByte();
+ if (tempChar == 0x00) break;
+
+ tempName += tempChar;
+
+ if (tempName.Length > 255) break;
+ }
+
+ fireDebug(" -> {0}", tempName);
+
+ //Read Node Data
+ if (tempNode.Type == U8_NodeType.File)
+ {
+ u8File.Seek((int)tempType + tempNode.OffsetToData, SeekOrigin.Begin);
+
+ fireDebug(" -> Reading Node Data (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper());
+
+ tempData = new byte[tempNode.SizeOfData];
+ u8File.Read(tempData, 0, tempData.Length);
+ }
+
+ if (tempName.ToLower() == "icon.bin") iconSize = getRealSize(tempData);
+ else if (tempName.ToLower() == "banner.bin") bannerSize = getRealSize(tempData);
+ else if (tempName.ToLower() == "sound.bin") soundSize = getRealSize(tempData);
+
+ u8Nodes.Add(tempNode);
+ stringTable.Add(tempName);
+ data.Add(tempData);
+ }
+
+ fireDebug("Pasing U8 File Finished...");
+ }
+
+ private void createFromDir(string path)
+ {
+ fireDebug("Creating U8 File from: {0}", path);
+
+ if (path[path.Length - 1] != Path.DirectorySeparatorChar) path += Path.DirectorySeparatorChar;
+
+ fireDebug(" Collecting Content...");
+ string[] dirEntries = getDirContent(path, true);
+ int offsetToName = 1;
+ int offsetToData = 0;
+
+ fireDebug(" Creating U8 Header...");
+ u8Header = new U8_Header();
+ rootNode = new U8_Node();
+ u8Nodes = new List();
+ stringTable = new List();
+ data = new List();
+
+ //Create Rootnode
+ fireDebug(" Creating Rootnode...");
+ rootNode.Type = U8_NodeType.Directory;
+ rootNode.OffsetToName = 0;
+ rootNode.OffsetToData = 0;
+ rootNode.SizeOfData = (uint)dirEntries.Length + 1;
+
+ //Create Nodes
+ for (int i = 0; i < dirEntries.Length; i++)
+ {
+ fireDebug(" Creating Node #{0} of {1}", i + 1, dirEntries.Length);
+ fireProgress((i + 1) * 100 / dirEntries.Length);
+
+ U8_Node tempNode = new U8_Node();
+ byte[] tempData = new byte[0];
+
+ string tempDirEntry = dirEntries[i].Remove(0, path.Length - 1);
+
+ if (Directory.Exists(dirEntries[i])) //It's a dir
+ {
+ fireDebug(" -> Directory");
+
+ tempNode.Type = U8_NodeType.Directory;
+ tempNode.OffsetToData = (uint)Shared.CountCharsInString(tempDirEntry, Path.DirectorySeparatorChar); //Recursion
+
+ int size = u8Nodes.Count + 2;
+ for (int j = 0; j < dirEntries.Length; j++)
+ if (dirEntries[j].Contains(dirEntries[i] + "\\")) size++;
+
+ tempNode.SizeOfData = (uint)size;
+ }
+ else //It's a file
+ {
+ fireDebug(" -> File");
+ fireDebug(" -> Reading File Data...");
+
+ tempData = File.ReadAllBytes(dirEntries[i]);
+ tempNode.Type = U8_NodeType.File;
+ tempNode.OffsetToData = (uint)offsetToData;
+ tempNode.SizeOfData = (uint)tempData.Length;
+
+ offsetToData += Shared.AddPadding(offsetToData + tempData.Length, dataPadding);
+ }
+
+ tempNode.OffsetToName = (ushort)offsetToName;
+ offsetToName += (Path.GetFileName(dirEntries[i])).Length + 1;
+
+ fireDebug(" -> Reading Name...");
+ string tempName = Path.GetFileName(dirEntries[i]);
+
+ if (tempName.ToLower() == "icon.bin") iconSize = getRealSize(tempData);
+ else if (tempName.ToLower() == "banner.bin") bannerSize = getRealSize(tempData);
+ else if (tempName.ToLower() == "sound.bin") soundSize = getRealSize(tempData);
+
+ u8Nodes.Add(tempNode);
+ stringTable.Add(tempName);
+ data.Add(tempData);
+ }
+
+ //Update U8 Header
+ fireDebug(" Updating U8 Header...");
+ u8Header.HeaderSize = (uint)(((u8Nodes.Count + 1) * 12) + offsetToName);
+ u8Header.OffsetToData = (uint)Shared.AddPadding((int)u8Header.OffsetToRootNode + (int)u8Header.HeaderSize, dataPadding);
+
+ //Update dataOffsets
+ fireDebug(" Calculating Data Offsets...");
+
+ for (int i = 0; i < u8Nodes.Count; i++)
+ {
+ fireDebug(" -> Node #{0} of {1}...", i + 1, u8Nodes.Count);
+
+ int tempOffset = (int)u8Nodes[i].OffsetToData;
+ u8Nodes[i].OffsetToData = (uint)(u8Header.OffsetToData + tempOffset);
+ }
+
+ fireDebug("Creating U8 File Finished...");
+ }
+
+ private string[] getDirContent(string dir, bool root)
+ {
+ string[] files = Directory.GetFiles(dir);
+ string[] dirs = Directory.GetDirectories(dir);
+ string all = "";
+
+ if (!root)
+ all += dir + "\n";
+
+ for (int i = 0; i < files.Length; i++)
+ all += files[i] + "\n";
+
+ foreach (string thisDir in dirs)
+ {
+ string[] temp = getDirContent(thisDir, false);
+
+ foreach (string thisTemp in temp)
+ all += thisTemp + "\n";
+ }
+
+ return all.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
+ }
+
+ private int getRealSize(byte[] data)
+ {
+ if (data[0] == 'I' && data[1] == 'M' && data[2] == 'D' && data[3] == '5')
+ if (data[0x20] == 'L' && data[0x21] == 'Z' && data[0x22] == '7' && data[0x23] == '7')
+ return (BitConverter.ToInt32(data, 0x24)) >> 8;
+ else
+ return data.Length - 32;
+
+ return data.Length;
+ }
+
+ private void addEntry(string nodePath, byte[] fileData)
+ {
+ //Parse path
+ if (nodePath.StartsWith("/")) nodePath = nodePath.Remove(0, 1);
+ string[] path = nodePath.Split('/'); //Last entry is the filename
+
+ int nodeIndex = -1;
+ int maxIndex = (u8Nodes.Count > 0) ? u8Nodes.Count - 1 : 0;
+ int currentIndex = 0;
+ List pathIndices = new List();
+
+ for (int i = 0; i < path.Length - 1; i++)
+ {
+ for (int j = currentIndex; j <= maxIndex; j++)
+ {
+ if (stringTable[j].ToLower() == path[i].ToLower())
+ {
+ if (i == path.Length - 2) nodeIndex = j;
+
+ maxIndex = (int)u8Nodes[j].SizeOfData - 1;
+ currentIndex = j + 1;
+ pathIndices.Add(j);
+
+ break;
+ }
+
+ if (j == maxIndex - 1) throw new Exception("Path wasn't found!");
+ }
+ }
+
+ //Get last entry in current dir
+ int lastEntry;
+
+ if (nodeIndex > -1) lastEntry = (int)u8Nodes[nodeIndex].SizeOfData - 2;
+ else lastEntry = (rootNode.SizeOfData > 1) ? (int)rootNode.SizeOfData - 2 : -1;
+
+ //Create and insert node + data
+ U8_Node tempNode = new U8_Node();
+ tempNode.Type = (fileData.Length == 0) ? U8_NodeType.Directory : U8_NodeType.File;
+ tempNode.SizeOfData = (uint)((fileData.Length == 0) ? lastEntry + 2 : fileData.Length);
+ tempNode.OffsetToData = (uint)((fileData.Length == 0) ? Shared.CountCharsInString(nodePath, '/') : 0);
+
+ stringTable.Insert(lastEntry + 1, path[path.Length - 1]);
+ u8Nodes.Insert(lastEntry + 1, tempNode);
+ data.Insert(lastEntry + 1, fileData);
+
+ //Update rootnode and path sizes (+1)
+ rootNode.SizeOfData += 1;
+
+ foreach (int index in pathIndices)
+ if (u8Nodes[index].Type == U8_NodeType.Directory)
+ u8Nodes[index].SizeOfData += 1;
+
+ for (int i = lastEntry + 1; i < u8Nodes.Count; i++)
+ if (u8Nodes[i].Type == U8_NodeType.Directory)
+ u8Nodes[i].SizeOfData += 1;
+ }
+
+ private void removeEntry(string nodePath)
+ {
+ //Parse path
+ if (nodePath.StartsWith("/")) nodePath = nodePath.Remove(0, 1);
+ string[] path = nodePath.Split('/'); //Last entry is the filename
+
+ int nodeIndex = -1;
+ int maxIndex = u8Nodes.Count - 1;
+ int currentIndex = 0;
+ List pathIndices = new List();
+
+ for (int i = 0; i < path.Length; i++)
+ {
+ for (int j = currentIndex; j < maxIndex; j++)
+ {
+ if (stringTable[j].ToLower() == path[i].ToLower())
+ {
+ if (i == path.Length - 1) nodeIndex = j;
+ else pathIndices.Add(j);
+
+ maxIndex = (int)u8Nodes[j].SizeOfData - 1;
+ currentIndex = j + 1;
+
+ break;
+ }
+
+ if (j == maxIndex - 1) throw new Exception("Path wasn't found!");
+ }
+ }
+
+ //Remove Node (and subnodes if node is dir)
+ int removed = 0;
+
+ if (u8Nodes[nodeIndex].Type == U8_NodeType.Directory)
+ {
+ for (int i = (int)u8Nodes[nodeIndex].SizeOfData - 2; i >= nodeIndex; i--)
+ {
+ stringTable.RemoveAt(i);
+ u8Nodes.RemoveAt(i);
+ data.RemoveAt(i);
+
+ removed++;
+ }
+ }
+ else
+ {
+ stringTable.RemoveAt(nodeIndex);
+ u8Nodes.RemoveAt(nodeIndex);
+ data.RemoveAt(nodeIndex);
+
+ removed++;
+ }
+
+ //Update rootnode and path sizes
+ rootNode.SizeOfData -= (uint)removed;
+
+ foreach (int index in pathIndices)
+ if (u8Nodes[index].Type == U8_NodeType.Directory)
+ u8Nodes[index].SizeOfData -= (uint)removed;
+
+ for (int i = nodeIndex + 1; i < u8Nodes.Count; i++)
+ if (u8Nodes[i].Type == U8_NodeType.Directory)
+ u8Nodes[i].SizeOfData -= (uint)removed;
+ }
+ #endregion
+
+ #region Events
+ ///
+ /// Fires the Progress of various operations
+ ///
+ public event EventHandler Progress;
+ ///
+ /// Fires warnings (e.g. when hashes do not match)
+ ///
+ public event EventHandler Warning;
+ ///
+ /// Fires debugging messages. You may write them into a log file or log textbox.
+ ///
+ public event EventHandler Debug;
+
+
+ private void fireWarning(string warningMessage)
+ {
+ EventHandler warning = Warning;
+ if (warning != null)
+ warning(new object(), new MessageEventArgs(warningMessage));
+ }
+
+ private void fireDebug(string debugMessage, params object[] args)
+ {
+ EventHandler debug = Debug;
+ if (debug != null)
+ debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
+ }
+
+ private void fireProgress(int progressPercentage)
+ {
+ EventHandler progress = Progress;
+ if (progress != null)
+ progress(new object(), new ProgressChangedEventArgs(progressPercentage, string.Empty));
+ }
+ #endregion
+ }
+
+ public class U8_Header
+ {
+ private uint u8Magic = 0x55AA382D;
+ private uint offsetToRootNode = 0x20;
+ private uint headerSize;
+ private uint offsetToData;
+ private byte[] padding = new byte[16];
+
+ public uint U8Magic { get { return u8Magic; } }
+ public uint OffsetToRootNode { get { return offsetToRootNode; } }
+ public uint HeaderSize { get { return headerSize; } set { headerSize = value; } }
+ public uint OffsetToData { get { return offsetToData; } set { offsetToData = value; } }
+ public byte[] Padding { get { return padding; } }
+
+ public void Write(Stream writeStream)
+ {
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(u8Magic)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(offsetToRootNode)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(headerSize)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(offsetToData)), 0, 4);
+ writeStream.Write(padding, 0, 16);
+ }
+ }
+
+ public class U8_Node
+ {
+ private ushort type;
+ private ushort offsetToName;
+ private uint offsetToData;
+ private uint sizeOfData;
+
+ public U8_NodeType Type { get { return (U8_NodeType)type; } set { type = (ushort)value; } }
+ public ushort OffsetToName { get { return offsetToName; } set { offsetToName = value; } }
+ public uint OffsetToData { get { return offsetToData; } set { offsetToData = value; } }
+ public uint SizeOfData { get { return sizeOfData; } set { sizeOfData = value; } }
+
+ public void Write(Stream writeStream)
+ {
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(type)), 0, 2);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(offsetToName)), 0, 2);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(offsetToData)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(sizeOfData)), 0, 4);
+ }
+ }
+}
diff --git a/NUS Downloader/WAD.cs b/NUS Downloader/WAD.cs
new file mode 100644
index 0000000..93e1e71
--- /dev/null
+++ b/NUS Downloader/WAD.cs
@@ -0,0 +1,1269 @@
+/* This file is part of libWiiSharp
+ * Copyright (C) 2009 Leathl
+ *
+ * libWiiSharp is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * libWiiSharp is distributed in the hope that it will be
+ * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace libWiiSharp
+{
+ public enum LowerTitleID : uint
+ {
+ SystemTitles = 0x00000001,
+ SystemChannels = 0x00010002,
+ Channel = 0x00010001,
+ GameChannel = 0x00010004,
+ DLC = 0x00010005,
+ HiddenChannels = 0x00010008,
+ }
+
+ public class WAD : IDisposable
+ {
+ private SHA1 sha = SHA1.Create();
+ private DateTime creationTimeUTC = new DateTime(1970, 1, 1);
+ private bool hasBanner = false;
+ private bool lz77CompressBannerAndIcon = true;
+ private bool lz77DecompressBannerAndIcon = false;
+ private bool keepOriginalFooter = false;
+
+ private WAD_Header wadHeader;
+ private CertificateChain cert = new CertificateChain();
+ private Ticket tik = new Ticket();
+ private TMD tmd = new TMD();
+ private List contents;
+ private U8 bannerApp = new U8();
+ private byte[] footer = new byte[0];
+
+ ///
+ /// The region of the title.
+ ///
+ public Region Region { get { return tmd.Region; } set { tmd.Region = value; } }
+ ///
+ /// The Number of contents.
+ ///
+ public int NumOfContents { get { return tmd.NumOfContents; } }
+ ///
+ /// The content of the WAD.
+ ///
+ public byte[][] Contents { get { return contents.ToArray(); } }
+ ///
+ /// If true, the Ticket and TMD will be fakesigned.
+ ///
+ public bool FakeSign { get { return (tik.FakeSign && tmd.FakeSign); } set { tik.FakeSign = value; tmd.FakeSign = value; } }
+ ///
+ /// The banner app file (aka 00000000.app). Will be empty if HasBanner is false.
+ ///
+ public U8 BannerApp { get { return bannerApp; } set { bannerApp = value; } }
+ ///
+ /// The IOS the Title is launched with.
+ ///
+ public ulong StartupIOS { get { return tmd.StartupIOS; } set { tmd.StartupIOS = value; } }
+ ///
+ /// The Title ID.
+ ///
+ public ulong TitleID { get { return tik.TitleID; } set { tik.TitleID = value; tmd.TitleID = value; } }
+ ///
+ /// The upper Title ID as string.
+ ///
+ public string UpperTitleID { get { return tik.GetUpperTitleID(); } }
+ ///
+ /// The Title Version.
+ ///
+ public ushort TitleVersion { get { return tmd.TitleVersion; } set { tmd.TitleVersion = value; } }
+ ///
+ /// The boot index. Represents the index of the nand loader.
+ ///
+ public ushort BootIndex { get { return tmd.BootIndex; } set { tmd.BootIndex = value; } }
+ ///
+ /// The creation time of the Title. Will be 1/1/1970 if no Timestamp footer was found.
+ ///
+ public DateTime CreationTimeUTC { get { return creationTimeUTC; } }
+ ///
+ /// True if the WAD has a banner.
+ ///
+ public bool HasBanner { get { return hasBanner; } }
+ ///
+ /// If true, the banner.bin and icon.bin files will be Lz77 compressed while saving the WAD.
+ ///
+ public bool Lz77CompressBannerAndIcon { get { return lz77CompressBannerAndIcon; } set { lz77CompressBannerAndIcon = value; if (value) lz77DecompressBannerAndIcon = false; } }
+ ///
+ /// If true, the banner.bin and icon.bin files will be Lz77 decompressed while saving the WAD.
+ ///
+ public bool Lz77DecompressBannerAndIcon { get { return lz77DecompressBannerAndIcon; } set { lz77DecompressBannerAndIcon = value; if (value) lz77CompressBannerAndIcon = false; } }
+ ///
+ /// The Number of memory blocks the content will take.
+ /// Might be inaccurate due to Lz77 (de)compression while saving.
+ ///
+ public string NandBlocks { get { return tmd.GetNandBlocks(); } }
+ ///
+ /// All Channel Titles as a string array. Will be empty if HasBanner is false.
+ ///
+ public string[] ChannelTitles { get { if (hasBanner) return ((Headers.IMET)bannerApp.Header).AllTitles; else return new string[0]; } set { ChangeChannelTitles(value); } }
+ ///
+ /// If false, a timestamp will be added as footer (64 bytes).
+ /// Else, the original footer will be kept or the one you provided.
+ ///
+ public bool KeepOriginalFooter { get { return keepOriginalFooter; } set { keepOriginalFooter = value; } }
+ ///
+ /// The TMDs content entries.
+ ///
+ public TMD_Content[] TmdContents { get { return tmd.Contents; } }
+
+ public WAD()
+ {
+ cert.Debug += new EventHandler(cert_Debug);
+ tik.Debug += new EventHandler(tik_Debug);
+ tmd.Debug += new EventHandler(tmd_Debug);
+ bannerApp.Debug += new EventHandler(bannerApp_Debug);
+ bannerApp.Warning += new EventHandler(bannerApp_Warning);
+ }
+
+ #region IDisposable Members
+ private bool isDisposed = false;
+
+ ~WAD()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && !isDisposed)
+ {
+ sha.Clear();
+ sha = null;
+
+ wadHeader = null;
+
+ cert.Dispose();
+ tik.Dispose();
+ tmd.Dispose();
+
+ contents.Clear();
+ contents = null;
+
+ bannerApp.Dispose();
+
+ footer = null;
+ }
+
+ isDisposed = true;
+ }
+ #endregion
+
+ #region Public Functions
+ ///
+ /// Loads a WAD file.
+ ///
+ ///
+ ///
+ public static WAD Load(string pathToWad)
+ {
+ return Load(File.ReadAllBytes(pathToWad));
+ }
+
+ ///
+ /// Loads a WAD file.
+ ///
+ ///
+ ///
+ public static WAD Load(byte[] wadFile)
+ {
+ WAD w = new WAD();
+ MemoryStream ms = new MemoryStream(wadFile);
+
+ try { w.parseWad(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ return w;
+ }
+
+ ///
+ /// Loads a WAD file.
+ ///
+ ///
+ ///
+ public static WAD Load(Stream wad)
+ {
+ WAD w = new WAD();
+ w.parseWad(wad);
+ return w;
+ }
+
+ ///
+ /// Creates a WAD file from contents.
+ ///
+ ///
+ ///
+ public static WAD Create(string contentDir)
+ {
+ string[] certPath = Directory.GetFiles(contentDir, "*cert*");
+ string[] tikPath = Directory.GetFiles(contentDir, "*tik*");
+ string[] tmdPath = Directory.GetFiles(contentDir, "*tmd*");
+
+ CertificateChain _cert = CertificateChain.Load(certPath[0]);
+ Ticket _tik = Ticket.Load(tikPath[0]);
+ TMD _tmd = TMD.Load(tmdPath[0]);
+
+ bool namedContentId = true;
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ if (!File.Exists(contentDir + Path.DirectorySeparatorChar + _tmd.Contents[i].ContentID.ToString("x8") + ".app"))
+ { namedContentId = false; break; }
+
+ if (!namedContentId)
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ if (!File.Exists(contentDir + Path.DirectorySeparatorChar + _tmd.Contents[i].Index.ToString("x8") + ".app"))
+ throw new Exception("Couldn't find all content files!");
+
+ byte[][] contents = new byte[_tmd.Contents.Length][];
+
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ {
+ string file = contentDir + Path.DirectorySeparatorChar + ((namedContentId) ? _tmd.Contents[i].ContentID.ToString("x8") : _tmd.Contents[i].Index.ToString("x8")) + ".app";
+ contents[i] = File.ReadAllBytes(file);
+ }
+
+ return Create(_cert, _tik, _tmd, contents);
+ }
+
+ ///
+ /// Creates a WAD file from contents.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static WAD Create(string pathToCert, string pathToTik, string pathToTmd, string contentDir)
+ {
+ CertificateChain _cert = CertificateChain.Load(pathToCert);
+ Ticket _tik = Ticket.Load(pathToTik);
+ TMD _tmd = TMD.Load(pathToTmd);
+
+ bool namedContentId = true;
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ if (!File.Exists(contentDir + Path.DirectorySeparatorChar + _tmd.Contents[i].ContentID.ToString("x8") + ".app"))
+ { namedContentId = false; break; }
+
+ if (!namedContentId)
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ if (!File.Exists(contentDir + Path.DirectorySeparatorChar + _tmd.Contents[i].Index.ToString("x8") + ".app"))
+ throw new Exception("Couldn't find all content files!");
+
+ byte[][] contents = new byte[_tmd.Contents.Length][];
+
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ {
+ string file = contentDir + Path.DirectorySeparatorChar + ((namedContentId) ? _tmd.Contents[i].ContentID.ToString("x8") : _tmd.Contents[i].Index.ToString("x8")) + ".app";
+ contents[i] = File.ReadAllBytes(file);
+ }
+
+ return Create(_cert, _tik, _tmd, contents);
+ }
+
+ ///
+ /// Creates a WAD file from contents.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static WAD Create(byte[] cert, byte[] tik, byte[] tmd, byte[][] contents)
+ {
+ CertificateChain _cert = CertificateChain.Load(cert);
+ Ticket _tik = Ticket.Load(tik);
+ TMD _tmd = TMD.Load(tmd);
+
+ return Create(_cert, _tik, _tmd, contents);
+ }
+
+ ///
+ /// Creates a WAD file from contents.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static WAD Create(CertificateChain cert, Ticket tik, TMD tmd, byte[][] contents)
+ {
+ WAD w = new WAD();
+ w.cert = cert;
+ w.tik = tik;
+ w.tmd = tmd;
+ w.contents = new List(contents);
+
+ w.wadHeader = new WAD_Header();
+ w.wadHeader.TmdSize = (uint)(484 + (tmd.Contents.Length * 36));
+
+ int contentSize = 0;
+ for (int i = 0; i < contents.Length - 1; i++)
+ contentSize += Shared.AddPadding(contents[i].Length);
+
+ contentSize += contents[contents.Length - 1].Length;
+
+ w.wadHeader.ContentSize = (uint)contentSize;
+
+ for (int i = 0; i < w.tmd.Contents.Length; i++)
+ if (w.tmd.Contents[i].Index == 0x0000)
+ {
+ try { w.bannerApp.LoadFile(contents[i]); w.hasBanner = true; }
+ catch { w.hasBanner = false; } //Probably System Wad => No Banner App...
+ break;
+ }
+
+ return w;
+ }
+
+
+
+ ///
+ /// Loads a WAD file.
+ ///
+ ///
+ public void LoadFile(string pathToWad)
+ {
+ LoadFile(File.ReadAllBytes(pathToWad));
+ }
+
+ ///
+ /// Loads a WAD file.
+ ///
+ ///
+ public void LoadFile(byte[] wadFile)
+ {
+ MemoryStream ms = new MemoryStream(wadFile);
+
+ try { parseWad(ms); }
+ catch { ms.Dispose(); throw; }
+
+ ms.Dispose();
+ }
+
+ ///
+ /// Loads a WAD file.
+ ///
+ ///
+ public void LoadFile(Stream wad)
+ {
+ parseWad(wad);
+ }
+
+ ///
+ /// Creates a WAD file from contents.
+ ///
+ ///
+ public void CreateNew(string contentDir)
+ {
+ string[] certPath = Directory.GetFiles(contentDir, "*cert*");
+ string[] tikPath = Directory.GetFiles(contentDir, "*tik*");
+ string[] tmdPath = Directory.GetFiles(contentDir, "*tmd*");
+
+ CertificateChain _cert = CertificateChain.Load(certPath[0]);
+ Ticket _tik = Ticket.Load(tikPath[0]);
+ TMD _tmd = TMD.Load(tmdPath[0]);
+
+ bool namedContentId = true;
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ if (!File.Exists(contentDir + Path.DirectorySeparatorChar + _tmd.Contents[i].ContentID.ToString("x8") + ".app"))
+ { namedContentId = false; break; }
+
+ if (!namedContentId)
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ if (!File.Exists(contentDir + Path.DirectorySeparatorChar + _tmd.Contents[i].Index.ToString("x8") + ".app"))
+ throw new Exception("Couldn't find all content files!");
+
+ byte[][] contents = new byte[_tmd.Contents.Length][];
+
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ {
+ string file = contentDir + Path.DirectorySeparatorChar + ((namedContentId) ? _tmd.Contents[i].ContentID.ToString("x8") : _tmd.Contents[i].Index.ToString("x8")) + ".app";
+ contents[i] = File.ReadAllBytes(file);
+ }
+
+ CreateNew(_cert, _tik, _tmd, contents);
+ }
+
+ ///
+ /// Creates a WAD file from contents.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void CreateNew(string pathToCert, string pathToTik, string pathToTmd, string contentDir)
+ {
+ CertificateChain _cert = CertificateChain.Load(pathToCert);
+ Ticket _tik = Ticket.Load(pathToTik);
+ TMD _tmd = TMD.Load(pathToTmd);
+
+ bool namedContentId = true;
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ if (!File.Exists(contentDir + Path.DirectorySeparatorChar + _tmd.Contents[i].ContentID.ToString("x8") + ".app"))
+ { namedContentId = false; break; }
+
+ if (!namedContentId)
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ if (!File.Exists(contentDir + Path.DirectorySeparatorChar + _tmd.Contents[i].Index.ToString("x8") + ".app"))
+ throw new Exception("Couldn't find all content files!");
+
+ byte[][] contents = new byte[_tmd.Contents.Length][];
+
+ for (int i = 0; i < _tmd.Contents.Length; i++)
+ {
+ string file = contentDir + Path.DirectorySeparatorChar + ((namedContentId) ? _tmd.Contents[i].ContentID.ToString("x8") : _tmd.Contents[i].Index.ToString("x8")) + ".app";
+ contents[i] = File.ReadAllBytes(file);
+ }
+
+ CreateNew(_cert, _tik, _tmd, contents);
+ }
+
+ ///
+ /// Creates a WAD file from contents.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void CreateNew(byte[] cert, byte[] tik, byte[] tmd, byte[][] contents)
+ {
+ CertificateChain _cert = CertificateChain.Load(cert);
+ Ticket _tik = Ticket.Load(tik);
+ TMD _tmd = TMD.Load(tmd);
+
+ CreateNew(_cert, _tik, _tmd, contents);
+ }
+
+ ///
+ /// Creates a WAD file from contents.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void CreateNew(CertificateChain cert, Ticket tik, TMD tmd, byte[][] contents)
+ {
+ this.cert = cert;
+ this.tik = tik;
+ this.tmd = tmd;
+ this.contents = new List(contents);
+
+ this.wadHeader = new WAD_Header();
+ this.wadHeader.TmdSize = (uint)(484 + (tmd.Contents.Length * 36));
+
+ int contentSize = 0;
+ for (int i = 0; i < contents.Length - 1; i++)
+ contentSize += Shared.AddPadding(contents[i].Length);
+
+ contentSize += contents[contents.Length - 1].Length;
+
+ this.wadHeader.ContentSize = (uint)contentSize;
+
+ for (int i = 0; i < this.tmd.Contents.Length; i++)
+ if (this.tmd.Contents[i].Index == 0x0000)
+ {
+ try { this.bannerApp.LoadFile(contents[i]); hasBanner = true; }
+ catch { hasBanner = false; } //Probably System Wad => No Banner App...
+ break;
+ }
+ }
+
+
+ ///
+ /// Saves the WAD file to the given location.
+ ///
+ ///
+ public void Save(string savePath)
+ {
+ if (File.Exists(savePath)) File.Delete(savePath);
+
+ using (FileStream fs = new FileStream(savePath, FileMode.Create))
+ writeToStream(fs);
+ }
+
+ ///
+ /// Returns the WAD file as a memory stream.
+ ///
+ ///
+ public MemoryStream ToMemoryStream()
+ {
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ return ms;
+ }
+
+ ///
+ /// Returns the WAD file as a byte array.
+ ///
+ ///
+ public byte[] ToByteArray()
+ {
+ MemoryStream ms = new MemoryStream();
+
+ try { writeToStream(ms); }
+ catch { ms.Dispose(); throw; }
+
+ byte[] res = ms.ToArray();
+ ms.Dispose();
+ return res;
+ }
+
+ ///
+ /// Changes the Title ID of the WAD file.
+ ///
+ ///
+ ///
+ public void ChangeTitleID(LowerTitleID lowerID, string upperID)
+ {
+ if (upperID.Length != 4) throw new Exception("Upper Title ID must be 4 characters long!");
+
+ byte[] temp = new byte[4];
+ temp[0] = (byte)upperID[3];
+ temp[1] = (byte)upperID[2];
+ temp[2] = (byte)upperID[1];
+ temp[3] = (byte)upperID[0];
+ uint upper = BitConverter.ToUInt32(temp, 0);
+
+ ulong newId = ((ulong)lowerID << 32) | upper;
+
+ tik.TitleID = newId;
+ tmd.TitleID = newId;
+ }
+
+ ///
+ /// Changes the IOS the Title is launched with.
+ ///
+ ///
+ public void ChangeStartupIOS(int newIos)
+ {
+ StartupIOS = ((ulong)0x00000001 << 32) | (uint)newIos;
+ }
+
+ ///
+ /// Changes the Title Key in the Ticket.
+ /// The given value will be the encrypted Key (i.e. what you can "read" in the Ticket).
+ ///
+ ///
+ public void ChangeTitleKey(string newTitleKey)
+ {
+ tik.SetTitleKey(newTitleKey);
+ }
+
+ ///
+ /// Changes the Title Key in the Ticket.
+ /// The given value will be the encrypted Key (i.e. what you can "read" in the Ticket).
+ ///
+ ///
+ public void ChangeTitleKey(char[] newTitleKey)
+ {
+ tik.SetTitleKey(newTitleKey);
+ }
+
+ ///
+ /// Changes the Title Key in the Ticket.
+ /// The given value will be the encrypted Key (i.e. what you can "read" in the Ticket).
+ ///
+ ///
+ public void ChangeTitleKey(byte[] newTitleKey)
+ {
+ tik.SetTitleKey(newTitleKey);
+ }
+
+ ///
+ /// Returns a content by it's TMD index.
+ ///
+ ///
+ ///
+ public byte[] GetContentByIndex(int index)
+ {
+ for (int i = 0; i < tmd.NumOfContents; i++)
+ if (tmd.Contents[i].Index == index)
+ return contents[i];
+
+ throw new Exception(string.Format("Content with index {0} not found!", index));
+ }
+
+ ///
+ /// Returns a content by it's content ID.
+ ///
+ ///
+ ///
+ public byte[] GetContentByID(int contentID)
+ {
+ for (int i = 0; i < tmd.NumOfContents; i++)
+ if (tmd.Contents[i].Index == contentID)
+ return contents[i];
+
+ throw new Exception(string.Format("Content with content ID {0} not found!", contentID));
+ }
+
+ ///
+ /// Changes the Channel Titles (Only if HasBanner is true).
+ /// 0: Japanese,
+ /// 1: English,
+ /// 2: German,
+ /// 3: French,
+ /// 4: Spanish,
+ /// 5: Italian,
+ /// 6: Dutch,
+ /// 7: Korean
+ ///
+ ///
+ public void ChangeChannelTitles(params string[] newTitles)
+ {
+ if (hasBanner)
+ ((Headers.IMET)bannerApp.Header).ChangeTitles(newTitles);
+ }
+
+ ///
+ /// Adds a content to the WAD.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void AddContent(byte[] newContent, int contentID, int index, ContentType type = ContentType.Normal)
+ {
+ TMD_Content temp = new TMD_Content();
+ temp.ContentID = (uint)contentID;
+ temp.Index = (ushort)index;
+ temp.Type = type;
+ temp.Size = (ulong)newContent.Length;
+ temp.Hash = sha.ComputeHash(newContent);
+
+ tmd.AddContent(temp);
+ contents.Add(newContent);
+
+ wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36);
+ }
+
+ ///
+ /// Removes a content from the WAD.
+ ///
+ ///
+ public void RemoveContent(int index)
+ {
+ for (int i = 0; i < tmd.Contents.Length; i++)
+ if (tmd.Contents[i].Index == index)
+ { tmd.RemoveContent(index); contents.RemoveAt(i); wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36); return; }
+
+ throw new Exception(string.Format("Content with index {0} not found!", index));
+ }
+
+ ///
+ /// Removes a content by it's content ID.
+ ///
+ ///
+ public void RemoveContentByID(int contentID)
+ {
+ for (int i = 0; i < tmd.Contents.Length; i++)
+ if (tmd.Contents[i].Index == contentID)
+ { tmd.RemoveContentByID(contentID); contents.RemoveAt(i); wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36); return; }
+
+ throw new Exception(string.Format("Content with content ID {0} not found!", contentID));
+ }
+
+ ///
+ /// Removes all contents from the WAD. If HasBanner is true, the banner content (Index 0) won't be removed!
+ ///
+ public void RemoveAllContents()
+ {
+ if (!hasBanner)
+ {
+ tmd.Contents = new TMD_Content[0];
+ contents = new List();
+
+ wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36);
+ }
+ else
+ {
+ for (int i=0;i();
+
+ tmd.AddContent(tmpTmdCont);
+ contents.Add(tmpCont);
+
+ wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36);
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Unpacks the WAD to the given directory.
+ /// If nameContentID is true, contents are named after their content ID, else after their index.
+ ///
+ ///
+ ///
+ public void Unpack(string unpackDir, bool nameContentID = false)
+ {
+ unpackAll(unpackDir, nameContentID);
+ }
+
+ ///
+ /// Removes the footer.
+ ///
+ public void RemoveFooter()
+ {
+ this.footer = new byte[0];
+ wadHeader.FooterSize = 0;
+
+ this.keepOriginalFooter = true;
+ }
+
+ ///
+ /// Adds a footer.
+ ///
+ ///
+ public void AddFooter(byte[] footer)
+ {
+ ChangeFooter(footer);
+ }
+
+ ///
+ /// Changes the footer.
+ ///
+ ///
+ public void ChangeFooter(byte[] newFooter)
+ {
+ if (newFooter.Length % 64 != 0)
+ Array.Resize(ref newFooter, Shared.AddPadding(newFooter.Length));
+
+ this.footer = newFooter;
+ wadHeader.FooterSize = (uint)newFooter.Length;
+
+ this.keepOriginalFooter = true;
+ }
+ #endregion
+
+ #region Private Functions
+ private void writeToStream(Stream writeStream)
+ {
+ fireDebug("Writing Wad...");
+
+ //Create Footer Timestamp
+ if (!keepOriginalFooter)
+ {
+ fireDebug(" Building Footer Timestamp...");
+ createFooterTimestamp();
+ }
+
+ //Save Banner App
+ if (hasBanner)
+ {
+ //Compress icon.bin and banner.bin
+ if (lz77CompressBannerAndIcon || lz77DecompressBannerAndIcon)
+ {
+ for (int i = 0; i < bannerApp.Nodes.Count; i++)
+ {
+ if (bannerApp.StringTable[i].ToLower() == "icon.bin" ||
+ bannerApp.StringTable[i].ToLower() == "banner.bin")
+ {
+ if (!Lz77.IsLz77Compressed(bannerApp.Data[i]) && lz77CompressBannerAndIcon)
+ {
+ fireDebug(" Compressing {0}...", bannerApp.StringTable[i]);
+
+ //Get the data without the IMD5 Header
+ byte[] fileData = new byte[bannerApp.Data[i].Length - 32];
+ Array.Copy(bannerApp.Data[i], 32, fileData, 0, fileData.Length);
+
+ //Compress the data
+ Lz77 l = new Lz77();
+ fileData = l.Compress(fileData);
+
+ //Add a new IMD5 Header
+ fileData = Headers.IMD5.AddHeader(fileData);
+ bannerApp.Data[i] = fileData;
+
+ //Update the node
+ bannerApp.Nodes[i].SizeOfData = (uint)fileData.Length;
+ }
+ else if (Lz77.IsLz77Compressed(bannerApp.Data[i]) && lz77DecompressBannerAndIcon)
+ {
+ fireDebug(" Decompressing {0}...", bannerApp.StringTable[i]);
+
+ //Get the data without the IMD5 Header
+ byte[] fileData = new byte[bannerApp.Data[i].Length - 32];
+ Array.Copy(bannerApp.Data[i], 32, fileData, 0, fileData.Length);
+
+ //Decompress the data
+ Lz77 l = new Lz77();
+ fileData = l.Decompress(fileData);
+
+ //Add a new IMD5 Header
+ fileData = Headers.IMD5.AddHeader(fileData);
+ bannerApp.Data[i] = fileData;
+
+ //Update the node
+ bannerApp.Nodes[i].SizeOfData = (uint)fileData.Length;
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < contents.Count; i++)
+ if (tmd.Contents[i].Index == 0x0000)
+ { fireDebug(" Saving Banner App..."); contents[i] = bannerApp.ToByteArray(); break; }
+ }
+
+ //Update Header (Content Size)
+ fireDebug(" Updating Header...");
+ int contentSize = 0;
+ for (int i = 0; i < contents.Count - 1; i++)
+ contentSize += Shared.AddPadding(contents[i].Length);
+
+ contentSize += contents[contents.Count - 1].Length;
+
+ wadHeader.ContentSize = (uint)contentSize;
+ wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36);
+
+ //Update Contents
+ fireDebug(" Updating TMD Contents...");
+ tmd.UpdateContents(contents.ToArray());
+
+ //Write Header
+ fireDebug(" Writing Wad Header... (Offset: 0x{0})", writeStream.Position.ToString("x8").ToUpper());
+ writeStream.Seek(0, SeekOrigin.Begin);
+ wadHeader.Write(writeStream);
+
+ //Write Cert
+ fireDebug(" Writing Certificate Chain... (Offset: 0x{0})", writeStream.Position.ToString("x8").ToUpper());
+ writeStream.Seek(Shared.AddPadding((int)writeStream.Position), SeekOrigin.Begin);
+ byte[] temp = cert.ToByteArray();
+ writeStream.Write(temp, 0, temp.Length);
+
+ //Write Tik
+ fireDebug(" Writing Ticket... (Offset: 0x{0})", writeStream.Position.ToString("x8").ToUpper());
+ writeStream.Seek(Shared.AddPadding((int)writeStream.Position), SeekOrigin.Begin);
+ temp = tik.ToByteArray();
+ writeStream.Write(temp, 0, temp.Length);
+
+ //Write TMD
+ fireDebug(" Writing TMD... (Offset: 0x{0})", writeStream.Position.ToString("x8").ToUpper());
+ writeStream.Seek(Shared.AddPadding((int)writeStream.Position), SeekOrigin.Begin);
+ temp = tmd.ToByteArray();
+ writeStream.Write(temp, 0, temp.Length);
+
+ //Write Contents
+ List contentList = new List();
+ for (int i = 0; i < tmd.Contents.Length; i++)
+ contentList.Add(new ContentIndices(i, tmd.Contents[i].Index));
+
+ contentList.Sort();
+
+ for (int i = 0; i < contentList.Count; i++)
+ {
+ writeStream.Seek(Shared.AddPadding((int)writeStream.Position), SeekOrigin.Begin);
+
+ fireProgress((i + 1) * 100 / contents.Count);
+
+ fireDebug(" Writing Content #{1} of {2}... (Offset: 0x{0})", writeStream.Position.ToString("x8").ToUpper(), i + 1, contents.Count);
+ fireDebug(" -> Content ID: 0x{0}", tmd.Contents[contentList[i].Index].ContentID.ToString("x8"));
+ fireDebug(" -> Index: 0x{0}", tmd.Contents[contentList[i].Index].Index.ToString("x4"));
+ fireDebug(" -> Type: 0x{0} ({1})", ((ushort)tmd.Contents[contentList[i].Index].Type).ToString("x4"), tmd.Contents[contentList[i].Index].Type.ToString());
+ fireDebug(" -> Size: {0} bytes", tmd.Contents[contentList[i].Index].Size);
+ fireDebug(" -> Hash: {0}", Shared.ByteArrayToString(tmd.Contents[contentList[i].Index].Hash));
+
+ temp = encryptContent(contents[contentList[i].Index], contentList[i].Index);
+ writeStream.Write(temp, 0, temp.Length);
+ }
+
+ //Write Footer
+ if (wadHeader.FooterSize > 0)
+ {
+ fireDebug(" Writing Footer... (Offset: 0x{0})", writeStream.Position.ToString("x8").ToUpper());
+ writeStream.Seek(Shared.AddPadding((int)writeStream.Position), SeekOrigin.Begin);
+ writeStream.Write(footer, 0, footer.Length);
+ }
+
+ //Padding
+ while (writeStream.Position % 64 != 0)
+ writeStream.WriteByte(0x00);
+
+ fireDebug("Writing Wad Finished... (Written Bytes: {0})", writeStream.Position);
+ }
+
+ private void unpackAll(string unpackDir, bool nameContentId)
+ {
+ fireDebug("Unpacking Wad to: {0}", unpackDir);
+
+ if (!Directory.Exists(unpackDir)) Directory.CreateDirectory(unpackDir);
+ string titleID = tik.TitleID.ToString("x16");
+
+ //Save Cert
+ fireDebug(" Saving Certificate Chain: {0}.cert", titleID);
+ cert.Save(unpackDir + Path.DirectorySeparatorChar + titleID + ".cert");
+
+ //Save Tik
+ fireDebug(" Saving Ticket: {0}.tik", titleID);
+ tik.Save(unpackDir + Path.DirectorySeparatorChar + titleID + ".tik");
+
+ //Save TMD
+ fireDebug(" Saving TMD: {0}.tmd", titleID);
+ tmd.Save(unpackDir + Path.DirectorySeparatorChar + titleID + ".tmd");
+
+ //Save Contents
+ for (int i = 0; i < tmd.NumOfContents; i++)
+ {
+ fireProgress((i + 1) * 100 / tmd.NumOfContents);
+
+ fireDebug(" Saving Content #{0} of {1}: {2}.app", i + 1, tmd.NumOfContents, (nameContentId ? tmd.Contents[i].ContentID.ToString("x8") : tmd.Contents[i].Index.ToString("x8")));
+ fireDebug(" -> Content ID: 0x{0}", tmd.Contents[i].ContentID.ToString("x8"));
+ fireDebug(" -> Index: 0x{0}", tmd.Contents[i].Index.ToString("x4"));
+ fireDebug(" -> Type: 0x{0} ({1})", ((ushort)tmd.Contents[i].Type).ToString("x4"), tmd.Contents[i].Type.ToString());
+ fireDebug(" -> Size: {0} bytes", tmd.Contents[i].Size);
+ fireDebug(" -> Hash: {0}", Shared.ByteArrayToString(tmd.Contents[i].Hash));
+
+ using (FileStream fs = new FileStream(unpackDir + Path.DirectorySeparatorChar +
+ (nameContentId ? tmd.Contents[i].ContentID.ToString("x8") : tmd.Contents[i].Index.ToString("x8")) + ".app",
+ FileMode.Create))
+ fs.Write(contents[i], 0, contents[i].Length);
+ }
+
+ //Save Footer
+ fireDebug(" Saving Footer: {0}.footer", titleID);
+ using (FileStream fs = new FileStream(unpackDir + Path.DirectorySeparatorChar + titleID + ".footer", FileMode.Create))
+ fs.Write(footer, 0, footer.Length);
+
+ fireDebug("Unpacking Wad Finished...");
+ }
+
+ private void parseWad(Stream wadFile)
+ {
+ fireDebug("Parsing Wad...");
+
+ wadFile.Seek(0, SeekOrigin.Begin);
+ byte[] temp = new byte[4];
+
+ wadHeader = new WAD_Header();
+ contents = new List();
+
+ //Read Header
+ fireDebug(" Parsing Header... (Offset: 0x{0})", wadFile.Position.ToString("x8").ToUpper());
+ wadFile.Read(temp, 0, 4);
+ if (Shared.Swap(BitConverter.ToUInt32(temp, 0)) != wadHeader.HeaderSize)
+ throw new Exception("Invalid Headersize!");
+
+ wadFile.Read(temp, 0, 4);
+ wadHeader.WadType = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ wadFile.Seek(12, SeekOrigin.Current);
+
+ wadFile.Read(temp, 0, 4);
+ wadHeader.TmdSize = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ wadFile.Read(temp, 0, 4);
+ wadHeader.ContentSize = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ wadFile.Read(temp, 0, 4);
+ wadHeader.FooterSize = Shared.Swap(BitConverter.ToUInt32(temp, 0));
+
+ //Read Cert
+ fireDebug(" Parsing Certificate Chain... (Offset: 0x{0})", wadFile.Position.ToString("x8").ToUpper());
+ wadFile.Seek(Shared.AddPadding((int)wadFile.Position), SeekOrigin.Begin);
+
+ temp = new byte[wadHeader.CertSize];
+ wadFile.Read(temp, 0, temp.Length);
+ cert.LoadFile(temp);
+
+ //Read Tik
+ fireDebug(" Parsing Ticket... (Offset: 0x{0})", wadFile.Position.ToString("x8").ToUpper());
+ wadFile.Seek(Shared.AddPadding((int)wadFile.Position), SeekOrigin.Begin);
+
+ temp = new byte[wadHeader.TicketSize];
+ wadFile.Read(temp, 0, temp.Length);
+ tik.LoadFile(temp);
+
+ //Read Tmd
+ fireDebug(" Parsing TMD... (Offset: 0x{0})", wadFile.Position.ToString("x8").ToUpper());
+ wadFile.Seek(Shared.AddPadding((int)wadFile.Position), SeekOrigin.Begin);
+
+ temp = new byte[wadHeader.TmdSize];
+ wadFile.Read(temp, 0, temp.Length);
+ tmd.LoadFile(temp);
+
+ if (tmd.TitleID != tik.TitleID)
+ fireWarning("The Title ID in the Ticket doesn't match the one in the TMD!");
+
+ //Read Content
+ for (int i = 0; i < tmd.NumOfContents; i++)
+ {
+ fireProgress((i + 1) * 100 / tmd.NumOfContents);
+
+ fireDebug(" Reading Content #{0} of {1}... (Offset: 0x{2})", i + 1, tmd.NumOfContents, wadFile.Position.ToString("x8").ToUpper());
+ fireDebug(" -> Content ID: 0x{0}", tmd.Contents[i].ContentID.ToString("x8"));
+ fireDebug(" -> Index: 0x{0}", tmd.Contents[i].Index.ToString("x4"));
+ fireDebug(" -> Type: 0x{0} ({1})", ((ushort)tmd.Contents[i].Type).ToString("x4"), tmd.Contents[i].Type.ToString());
+ fireDebug(" -> Size: {0} bytes", tmd.Contents[i].Size);
+ fireDebug(" -> Hash: {0}", Shared.ByteArrayToString(tmd.Contents[i].Hash));
+
+ wadFile.Seek(Shared.AddPadding((int)wadFile.Position), SeekOrigin.Begin);
+
+ temp = new byte[Shared.AddPadding((int)tmd.Contents[i].Size, 16)];
+ wadFile.Read(temp, 0, temp.Length);
+
+ //Decrypt Content
+ temp = decryptContent(temp, i);
+ Array.Resize(ref temp, (int)tmd.Contents[i].Size);
+
+ byte[] tmdHash = tmd.Contents[i].Hash;
+ byte[] newHash = sha.ComputeHash(temp, 0, (int)tmd.Contents[i].Size);
+
+ if (!Shared.CompareByteArrays(tmdHash, newHash))
+ {
+ fireDebug(@"/!\ /!\ /!\ Hashes do not match /!\ /!\ /!\");
+ fireWarning(string.Format("Content #{0} (Content ID: 0x{1}; Index: 0x{2}): Hashes do not match! The content might be corrupted!", i + 1, tmd.Contents[i].ContentID.ToString("x8"), tmd.Contents[i].Index.ToString("x4")));
+ }
+
+ contents.Add(temp);
+
+ if (tmd.Contents[i].Index == 0x0000)
+ {
+ try { bannerApp.LoadFile(temp); hasBanner = true; }
+ catch { hasBanner = false; } //Probably System Wad => No Banner App...
+ }
+ }
+
+ //Read Footer
+ if (wadHeader.FooterSize > 0)
+ {
+ fireDebug(" Reading Footer... (Offset: 0x{0})", wadFile.Position.ToString("x8").ToUpper());
+ footer = new byte[wadHeader.FooterSize];
+
+ wadFile.Seek(Shared.AddPadding((int)wadFile.Position), SeekOrigin.Begin);
+ wadFile.Read(footer, 0, footer.Length);
+
+ parseFooterTimestamp();
+ }
+
+ fireDebug("Parsing Wad Finished...");
+ }
+
+ private byte[] decryptContent(byte[] content, int contentIndex)
+ {
+ int originalLength = content.Length;
+ Array.Resize(ref content, Shared.AddPadding(content.Length, 16));
+ byte[] titleKey = tik.TitleKey;
+ byte[] iv = new byte[16];
+
+ byte[] tmp = BitConverter.GetBytes(tmd.Contents[contentIndex].Index);
+ iv[0] = tmp[1];
+ iv[1] = tmp[0];
+
+ RijndaelManaged rm = new RijndaelManaged();
+ rm.Mode = CipherMode.CBC;
+ rm.Padding = PaddingMode.None;
+ rm.KeySize = 128;
+ rm.BlockSize = 128;
+ rm.Key = titleKey;
+ rm.IV = iv;
+
+ ICryptoTransform decryptor = rm.CreateDecryptor();
+
+ MemoryStream ms = new MemoryStream(content);
+ CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
+
+ byte[] decCont = new byte[originalLength];
+ cs.Read(decCont, 0, decCont.Length);
+
+ cs.Dispose();
+ ms.Dispose();
+
+ return decCont;
+ }
+
+ private byte[] encryptContent(byte[] content, int contentIndex)
+ {
+ Array.Resize(ref content, Shared.AddPadding(content.Length, 16));
+ byte[] titleKey = tik.TitleKey;
+ byte[] iv = new byte[16];
+
+ byte[] tmp = BitConverter.GetBytes(tmd.Contents[contentIndex].Index);
+ iv[0] = tmp[1];
+ iv[1] = tmp[0];
+
+ RijndaelManaged encrypt = new RijndaelManaged();
+ encrypt.Mode = CipherMode.CBC;
+ encrypt.Padding = PaddingMode.None;
+ encrypt.KeySize = 128;
+ encrypt.BlockSize = 128;
+ encrypt.Key = titleKey;
+ encrypt.IV = iv;
+
+ ICryptoTransform encryptor = encrypt.CreateEncryptor();
+
+ MemoryStream ms = new MemoryStream(content);
+ CryptoStream cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Read);
+
+ byte[] encCont = new byte[content.Length];
+ cs.Read(encCont, 0, encCont.Length);
+
+ cs.Dispose();
+ ms.Dispose();
+
+ return encCont;
+ }
+
+ private void createFooterTimestamp()
+ {
+ DateTime dtNow = DateTime.UtcNow;
+ TimeSpan tsTimestamp = (dtNow - new DateTime(1970, 1, 1, 0, 0, 0));
+
+ int timeStamp = (int)tsTimestamp.TotalSeconds;
+ System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
+
+ byte[] timeStampBytes = enc.GetBytes("TmStmp" + timeStamp.ToString());
+ Array.Resize(ref timeStampBytes, 64);
+
+ wadHeader.FooterSize = (uint)timeStampBytes.Length;
+ footer = timeStampBytes;
+ }
+
+ private void parseFooterTimestamp()
+ {
+ creationTimeUTC = new DateTime(1970, 1, 1);
+
+ if ((footer[0] == 'C' && footer[1] == 'M' && footer[2] == 'i' &&
+ footer[3] == 'i' && footer[4] == 'U' && footer[5] == 'T') ||
+ (footer[0] == 'T' && footer[1] == 'm' && footer[2] == 'S' &&
+ footer[3] == 't' && footer[4] == 'm' && footer[5] == 'p'))
+ {
+ System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
+ string stringSeconds = enc.GetString(footer, 6, 10);
+ int seconds = 0;
+
+ if (int.TryParse(stringSeconds, out seconds))
+ creationTimeUTC = creationTimeUTC.AddSeconds((double)seconds);
+ }
+ }
+ #endregion
+
+ #region Events
+ ///
+ /// Fires the Progress of various operations
+ ///
+ public event EventHandler Progress;
+ ///
+ /// Fires warnings (e.g. when hashes don't match)
+ ///
+ public event EventHandler Warning;
+ ///
+ /// Fires debugging messages. You may write them into a log file or log textbox.
+ ///
+ public event EventHandler Debug;
+
+ private void fireDebug(string debugMessage, params object[] args)
+ {
+ EventHandler debug = Debug;
+ if (debug != null)
+ debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
+ }
+
+ private void fireWarning(string warningMessage)
+ {
+ EventHandler warning = Warning;
+ if (warning != null)
+ warning(new object(), new MessageEventArgs(warningMessage));
+ }
+
+ private void fireProgress(int progressPercentage)
+ {
+ EventHandler progress = Progress;
+ if (progress != null)
+ progress(new object(), new ProgressChangedEventArgs(progressPercentage, string.Empty));
+ }
+
+ private void cert_Debug(object sender, MessageEventArgs e)
+ {
+ fireDebug(" Certificate Chain: {0}", e.Message);
+ }
+
+ private void tik_Debug(object sender, MessageEventArgs e)
+ {
+ fireDebug(" Ticket: {0}", e.Message);
+ }
+
+ private void tmd_Debug(object sender, MessageEventArgs e)
+ {
+ fireDebug(" TMD: {0}", e.Message);
+ }
+
+ void bannerApp_Debug(object sender, MessageEventArgs e)
+ {
+ fireDebug(" BannerApp: {0}", e.Message);
+ }
+
+ void bannerApp_Warning(object sender, MessageEventArgs e)
+ {
+ fireWarning(e.Message);
+ }
+ #endregion
+ }
+
+ public class WAD_Header
+ {
+ private uint headerSize = 0x20;
+ private uint wadType = 0x49730000;
+ private uint certSize = 0xA00;
+ private uint reserved = 0x00;
+ private uint tikSize = 0x2A4;
+ private uint tmdSize;
+ private uint contentSize;
+ private uint footerSize = 0x00;
+
+ public uint HeaderSize { get { return headerSize; } }
+ public uint WadType { get { return wadType; } set { wadType = value; } }
+ public uint CertSize { get { return certSize; } }
+ public uint Reserved { get { return reserved; } }
+ public uint TicketSize { get { return tikSize; } }
+ public uint TmdSize { get { return tmdSize; } set { tmdSize = value; } }
+ public uint ContentSize { get { return contentSize; } set { contentSize = value; } }
+ public uint FooterSize { get { return footerSize; } set { footerSize = value; } }
+
+ public void Write(Stream writeStream)
+ {
+ writeStream.Seek(0, SeekOrigin.Begin);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(headerSize)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(wadType)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(certSize)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(reserved)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(tikSize)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(tmdSize)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(contentSize)), 0, 4);
+ writeStream.Write(BitConverter.GetBytes(Shared.Swap(footerSize)), 0, 4);
+ }
+ }
+}