mirror of
https://github.com/WiiDatabase/nusdownloader.git
synced 2024-11-27 12:44:13 +01:00
Added libwiisharp components
common key now from libwiisharp
This commit is contained in:
parent
f4d321b377
commit
c67e219ac3
494
NUS Downloader/CertificateChain.cs
Normal file
494
NUS Downloader/CertificateChain.cs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If false, the Certificate Chain is not complete (i.e. at least one certificate is missing).
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a cert file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToCert"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static CertificateChain Load(string pathToCert)
|
||||||
|
{
|
||||||
|
return Load(File.ReadAllBytes(pathToCert));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a cert file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certFile"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a cert file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cert"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static CertificateChain Load(Stream cert)
|
||||||
|
{
|
||||||
|
CertificateChain c = new CertificateChain();
|
||||||
|
c.parseCert(cert);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grabs certificates from Ticket and Tmd.
|
||||||
|
/// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToTik"></param>
|
||||||
|
/// <param name="pathToTmd"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static CertificateChain FromTikTmd(string pathToTik, string pathToTmd)
|
||||||
|
{
|
||||||
|
return FromTikTmd(File.ReadAllBytes(pathToTik), File.ReadAllBytes(pathToTmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grabs certificates from Ticket and Tmd.
|
||||||
|
/// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tikFile"></param>
|
||||||
|
/// <param name="tmdFile"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grabs certificates from Ticket and Tmd.
|
||||||
|
/// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tik"></param>
|
||||||
|
/// <param name="tmd"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static CertificateChain FromTikTmd(Stream tik, Stream tmd)
|
||||||
|
{
|
||||||
|
CertificateChain c = new CertificateChain();
|
||||||
|
c.grabFromTik(tik);
|
||||||
|
c.grabFromTmd(tmd);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a cert file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToCert"></param>
|
||||||
|
public void LoadFile(string pathToCert)
|
||||||
|
{
|
||||||
|
LoadFile(File.ReadAllBytes(pathToCert));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a cert file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certFile"></param>
|
||||||
|
public void LoadFile(byte[] certFile)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream(certFile);
|
||||||
|
|
||||||
|
try { parseCert(ms); }
|
||||||
|
catch { ms.Dispose(); throw; }
|
||||||
|
|
||||||
|
ms.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a cert file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cert"></param>
|
||||||
|
public void LoadFile(Stream cert)
|
||||||
|
{
|
||||||
|
parseCert(cert);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grabs certificates from Ticket and Tmd.
|
||||||
|
/// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToTik"></param>
|
||||||
|
/// <param name="pathToTmd"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public void LoadFromTikTmd(string pathToTik, string pathToTmd)
|
||||||
|
{
|
||||||
|
LoadFromTikTmd(File.ReadAllBytes(pathToTik), File.ReadAllBytes(pathToTmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grabs certificates from Ticket and Tmd.
|
||||||
|
/// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tikFile"></param>
|
||||||
|
/// <param name="tmdFile"></param>
|
||||||
|
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!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Grabs certificates from Ticket and Tmd.
|
||||||
|
/// Ticket and Tmd must contain certs! (They do when they're downloaded from NUS!)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tik"></param>
|
||||||
|
/// <param name="tmd"></param>
|
||||||
|
public void LoadFromTikTmd(Stream tik, Stream tmd)
|
||||||
|
{
|
||||||
|
grabFromTik(tik);
|
||||||
|
grabFromTmd(tmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the Certificate Chain.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="savePath"></param>
|
||||||
|
public void Save(string savePath)
|
||||||
|
{
|
||||||
|
if (File.Exists(savePath)) File.Delete(savePath);
|
||||||
|
|
||||||
|
using (FileStream fs = new FileStream(savePath, FileMode.Create))
|
||||||
|
writeToStream(fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Certificate Chain as a memory stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MemoryStream ToMemoryStream()
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
|
||||||
|
try { writeToStream(ms); }
|
||||||
|
catch { ms.Dispose(); throw; }
|
||||||
|
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Certificate Chain as a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Fires debugging messages. You may write them into a log file or log textbox.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<MessageEventArgs> Debug;
|
||||||
|
|
||||||
|
private void fireDebug(string debugMessage, params object[] args)
|
||||||
|
{
|
||||||
|
EventHandler<MessageEventArgs> debug = Debug;
|
||||||
|
if (debug != null)
|
||||||
|
debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
35
NUS Downloader/CommonKey.cs
Normal file
35
NUS Downloader/CommonKey.cs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
NUS Downloader/ContentIndices.cs
Normal file
44
NUS Downloader/ContentIndices.cs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
NUS Downloader/Form1.Designer.cs
generated
38
NUS Downloader/Form1.Designer.cs
generated
@ -63,10 +63,6 @@
|
|||||||
this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();
|
this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();
|
||||||
this.proxySettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
this.proxySettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.toolStripSeparator6 = new System.Windows.Forms.ToolStripSeparator();
|
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.aboutNUSDToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
this.proxyBox = new System.Windows.Forms.GroupBox();
|
this.proxyBox = new System.Windows.Forms.GroupBox();
|
||||||
this.label13 = new System.Windows.Forms.Label();
|
this.label13 = new System.Windows.Forms.Label();
|
||||||
@ -339,11 +335,9 @@
|
|||||||
this.toolStripSeparator3,
|
this.toolStripSeparator3,
|
||||||
this.proxySettingsToolStripMenuItem,
|
this.proxySettingsToolStripMenuItem,
|
||||||
this.toolStripSeparator6,
|
this.toolStripSeparator6,
|
||||||
//this.getCommonKeyMenuItem,
|
|
||||||
this.toolStripSeparator5,
|
|
||||||
this.aboutNUSDToolStripMenuItem});
|
this.aboutNUSDToolStripMenuItem});
|
||||||
this.extrasStrip.Name = "extrasStrip";
|
this.extrasStrip.Name = "extrasStrip";
|
||||||
this.extrasStrip.Size = new System.Drawing.Size(183, 110);
|
this.extrasStrip.Size = new System.Drawing.Size(183, 82);
|
||||||
//
|
//
|
||||||
// loadInfoFromTMDToolStripMenuItem
|
// loadInfoFromTMDToolStripMenuItem
|
||||||
//
|
//
|
||||||
@ -371,35 +365,6 @@
|
|||||||
this.toolStripSeparator6.Name = "toolStripSeparator6";
|
this.toolStripSeparator6.Name = "toolStripSeparator6";
|
||||||
this.toolStripSeparator6.Size = new System.Drawing.Size(179, 6);
|
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
|
// aboutNUSDToolStripMenuItem
|
||||||
//
|
//
|
||||||
this.aboutNUSDToolStripMenuItem.Image = global::NUS_Downloader.Properties.Resources.information;
|
this.aboutNUSDToolStripMenuItem.Image = global::NUS_Downloader.Properties.Resources.information;
|
||||||
@ -916,7 +881,6 @@
|
|||||||
private System.Windows.Forms.ToolStripMenuItem PALMassUpdate;
|
private System.Windows.Forms.ToolStripMenuItem PALMassUpdate;
|
||||||
private System.Windows.Forms.ToolStripMenuItem NTSCMassUpdate;
|
private System.Windows.Forms.ToolStripMenuItem NTSCMassUpdate;
|
||||||
private System.Windows.Forms.ToolStripMenuItem KoreaMassUpdate;
|
private System.Windows.Forms.ToolStripMenuItem KoreaMassUpdate;
|
||||||
private System.Windows.Forms.ToolStripSeparator toolStripSeparator5;
|
|
||||||
private System.Windows.Forms.ToolStripMenuItem aboutNUSDToolStripMenuItem;
|
private System.Windows.Forms.ToolStripMenuItem aboutNUSDToolStripMenuItem;
|
||||||
private System.Windows.Forms.CheckBox checkBox1;
|
private System.Windows.Forms.CheckBox checkBox1;
|
||||||
private System.Windows.Forms.Button SaveProxyPwdPermanentBtn;
|
private System.Windows.Forms.Button SaveProxyPwdPermanentBtn;
|
||||||
|
@ -114,6 +114,7 @@ namespace NUS_Downloader
|
|||||||
// Scripts Thread
|
// Scripts Thread
|
||||||
private BackgroundWorker scriptsWorker;
|
private BackgroundWorker scriptsWorker;
|
||||||
|
|
||||||
|
/*
|
||||||
// Common Key hash
|
// Common Key hash
|
||||||
private static byte[] wii_commonkey = new byte[16]
|
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
|
0x63, 0xb8, 0x2b, 0xb4, 0xf4, 0x61, 0x4e, 0x2e, 0x13, 0xf2, 0xfe, 0xfb, 0xba, 0x4c, 0x9b, 0x7e
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
/*
|
/*
|
||||||
public struct WADHeader
|
public struct WADHeader
|
||||||
{
|
{
|
||||||
@ -262,7 +263,7 @@ namespace NUS_Downloader
|
|||||||
{
|
{
|
||||||
WriteStatus("Korean Common Key detected.");
|
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...
|
// Check for DSi common key bin file...
|
||||||
if (NUSDFileExists("dsikey.bin") == true)
|
if (NUSDFileExists("dsikey.bin") == true)
|
||||||
@ -1011,15 +1012,15 @@ namespace NUS_Downloader
|
|||||||
if (cetkbuf[0x01F1] == 0x01)
|
if (cetkbuf[0x01F1] == 0x01)
|
||||||
{
|
{
|
||||||
WriteStatus("Key Type: Korean");
|
WriteStatus("Key Type: Korean");
|
||||||
keyBytes = LoadCommonKey("kkey.bin");
|
keyBytes = libWiiSharp.CommonKey.GetKoreanKey();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WriteStatus("Key Type: Standard");
|
WriteStatus("Key Type: Standard");
|
||||||
if (wiimode)
|
if (wiimode)
|
||||||
keyBytes = LoadCommonKey("key.bin");
|
keyBytes = libWiiSharp.CommonKey.GetStandardKey();
|
||||||
else
|
else
|
||||||
keyBytes = LoadCommonKey("dsikey.bin");
|
keyBytes = File.ReadAllBytes(Path.Combine(CURRENT_DIR, "dsikey.bin"));
|
||||||
}
|
}
|
||||||
|
|
||||||
initCrypt(iv, keyBytes);
|
initCrypt(iv, keyBytes);
|
||||||
@ -1530,6 +1531,7 @@ namespace NUS_Downloader
|
|||||||
return sha.ComputeHash(data);
|
return sha.ComputeHash(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Loads the common key from disc.
|
/// Loads the common key from disc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1552,8 +1554,9 @@ namespace NUS_Downloader
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
return null;
|
return null;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
/*
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes/overwrites the common key onto disc.
|
/// Writes/overwrites the common key onto disc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1579,7 +1582,7 @@ namespace NUS_Downloader
|
|||||||
WriteStatus(String.Format("Error: Couldn't write {0}: {1}", keyfile, e.Message));
|
WriteStatus(String.Format("Error: Couldn't write {0}: {1}", keyfile, e.Message));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
private void button4_Click(object sender, EventArgs e)
|
private void button4_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
747
NUS Downloader/Headers.cs
Normal file
747
NUS Downloader/Headers.cs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
namespace libWiiSharp
|
||||||
|
{
|
||||||
|
public class Headers
|
||||||
|
{
|
||||||
|
private static uint imd5Magic = 0x494d4435;
|
||||||
|
private static uint imetMagic = 0x494d4554;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert HeaderType to int to get it's Length.
|
||||||
|
/// </summary>
|
||||||
|
public enum HeaderType
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
/// <summary>
|
||||||
|
/// Used in opening.bnr
|
||||||
|
/// </summary>
|
||||||
|
ShortIMET = 1536,
|
||||||
|
/// <summary>
|
||||||
|
/// Used in 00000000.app
|
||||||
|
/// </summary>
|
||||||
|
IMET = 1600,
|
||||||
|
/// <summary>
|
||||||
|
/// Used in banner.bin / icon.bin
|
||||||
|
/// </summary>
|
||||||
|
IMD5 = 32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Public Functions
|
||||||
|
/// <summary>
|
||||||
|
/// Checks a file for Headers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToFile"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static HeaderType DetectHeader(string pathToFile)
|
||||||
|
{
|
||||||
|
return DetectHeader(File.ReadAllBytes(pathToFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks the byte array for Headers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks the stream for Headers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Short IMET has a padding of 64 bytes at the beginning while Long IMET has 128.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsShortIMET { get { return isShortImet; } set { isShortImet = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The size of uncompressed icon.bin
|
||||||
|
/// </summary>
|
||||||
|
public uint IconSize { get { return iconSize; } set { iconSize = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The size of uncompressed banner.bin
|
||||||
|
/// </summary>
|
||||||
|
public uint BannerSize { get { return bannerSize; } set { bannerSize = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The size of uncompressed sound.bin
|
||||||
|
/// </summary>
|
||||||
|
public uint SoundSize { get { return soundSize; } set { soundSize = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The japanese Title.
|
||||||
|
/// </summary>
|
||||||
|
public string JapaneseTitle { get { return returnTitleAsString(japaneseTitle); } set { setTitleFromString(value, 0); } }
|
||||||
|
/// <summary>
|
||||||
|
/// The english Title.
|
||||||
|
/// </summary>
|
||||||
|
public string EnglishTitle { get { return returnTitleAsString(englishTitle); } set { setTitleFromString(value, 1); } }
|
||||||
|
/// <summary>
|
||||||
|
/// The german Title.
|
||||||
|
/// </summary>
|
||||||
|
public string GermanTitle { get { return returnTitleAsString(germanTitle); } set { setTitleFromString(value, 2); } }
|
||||||
|
/// <summary>
|
||||||
|
/// The french Title.
|
||||||
|
/// </summary>
|
||||||
|
public string FrenchTitle { get { return returnTitleAsString(frenchTitle); } set { setTitleFromString(value, 3); } }
|
||||||
|
/// <summary>
|
||||||
|
/// The spanish Title.
|
||||||
|
/// </summary>
|
||||||
|
public string SpanishTitle { get { return returnTitleAsString(spanishTitle); } set { setTitleFromString(value, 4); } }
|
||||||
|
/// <summary>
|
||||||
|
/// The italian Title.
|
||||||
|
/// </summary>
|
||||||
|
public string ItalianTitle { get { return returnTitleAsString(italianTitle); } set { setTitleFromString(value, 5); } }
|
||||||
|
/// <summary>
|
||||||
|
/// The dutch Title.
|
||||||
|
/// </summary>
|
||||||
|
public string DutchTitle { get { return returnTitleAsString(dutchTitle); } set { setTitleFromString(value, 6); } }
|
||||||
|
/// <summary>
|
||||||
|
/// The korean Title.
|
||||||
|
/// </summary>
|
||||||
|
public string KoreanTitle { get { return returnTitleAsString(koreanTitle); } set { setTitleFromString(value, 7); } }
|
||||||
|
/// <summary>
|
||||||
|
/// All Titles as a string array.
|
||||||
|
/// </summary>
|
||||||
|
public string[] AllTitles { get { return new string[] { JapaneseTitle, EnglishTitle, GermanTitle, FrenchTitle, SpanishTitle, ItalianTitle, DutchTitle, KoreanTitle }; } }
|
||||||
|
/// <summary>
|
||||||
|
/// When parsing an IMET header, this value will turn false if the hash stored in the header doesn't match the headers hash.
|
||||||
|
/// </summary>
|
||||||
|
public bool HashesMatch { get { return hashesMatch; } }
|
||||||
|
|
||||||
|
#region Public Functions
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the IMET Header of a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToFile"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IMET Load(string pathToFile)
|
||||||
|
{
|
||||||
|
return Load(File.ReadAllBytes(pathToFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the IMET Header of a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileOrHeader"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the IMET Header of a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileOrHeader"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IMET Header.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isShortImet"></param>
|
||||||
|
/// <param name="iconSize"></param>
|
||||||
|
/// <param name="bannerSize"></param>
|
||||||
|
/// <param name="soundSize"></param>
|
||||||
|
/// <param name="titles"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the IMET Header of a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToFile"></param>
|
||||||
|
public static void RemoveHeader(string pathToFile)
|
||||||
|
{
|
||||||
|
byte[] fileWithoutHeader = RemoveHeader(File.ReadAllBytes(pathToFile));
|
||||||
|
File.Delete(pathToFile);
|
||||||
|
|
||||||
|
File.WriteAllBytes(pathToFile, fileWithoutHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the IMET Header of a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets all title to the given string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newTitle"></param>
|
||||||
|
public void SetAllTitles(string newTitle)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
setTitleFromString(newTitle, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Header as a memory stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MemoryStream ToMemoryStream()
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
|
||||||
|
try { writeToStream(ms); }
|
||||||
|
catch { ms.Dispose(); throw; }
|
||||||
|
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Header as a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public byte[] ToByteArray()
|
||||||
|
{
|
||||||
|
return ToMemoryStream().ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the Header to the given stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writeStream"></param>
|
||||||
|
public void Write(Stream writeStream)
|
||||||
|
{
|
||||||
|
writeToStream(writeStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Changes the Titles.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newTitles"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a string array with the Titles.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the file without the IMD5 Header.
|
||||||
|
/// </summary>
|
||||||
|
public uint FileSize { get { return fileSize; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The hash of the file without the IMD5 Header.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] Hash { get { return hash; } }
|
||||||
|
|
||||||
|
private IMD5() { }
|
||||||
|
|
||||||
|
#region Public Functions
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the IMD5 Header of a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToFile"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IMD5 Load(string pathToFile)
|
||||||
|
{
|
||||||
|
return Load(File.ReadAllBytes(pathToFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the IMD5 Header of a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileOrHeader"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the IMD5 Header of a stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileOrHeader"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new IMD5 Header.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static IMD5 Create(byte[] file)
|
||||||
|
{
|
||||||
|
IMD5 h = new IMD5();
|
||||||
|
|
||||||
|
h.fileSize = (uint)file.Length;
|
||||||
|
h.computeHash(file);
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an IMD5 Header to a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToFile"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an IMD5 Header to a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the IMD5 Header of a file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToFile"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the IMD5 Header of a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the IMD5 Header as a memory stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MemoryStream ToMemoryStream()
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
|
||||||
|
try { writeToStream(ms); }
|
||||||
|
catch { ms.Dispose(); throw; }
|
||||||
|
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the IMD5 Header as a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public byte[] ToByteArray()
|
||||||
|
{
|
||||||
|
return ToMemoryStream().ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the IMD5 Header to the given stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="writeStream"></param>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
313
NUS Downloader/IosPatcher.cs
Normal file
313
NUS Downloader/IosPatcher.cs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//Patches based on Dop-Mii - Thanks to Arikado and Lunatik
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace libWiiSharp
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An IOS patcher which can patch fakesigning, es_identify and nand permissions.
|
||||||
|
/// </summary>
|
||||||
|
public class IosPatcher
|
||||||
|
{
|
||||||
|
private WAD wadFile;
|
||||||
|
private int esIndex = -1;
|
||||||
|
|
||||||
|
#region Public Functions
|
||||||
|
/// <summary>
|
||||||
|
/// Loads an IOS wad to patch the es module.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="iosWad"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patches fakesigning.
|
||||||
|
/// Returns the number of applied patches.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int PatchFakeSigning()
|
||||||
|
{
|
||||||
|
if (esIndex < 0) return -1;
|
||||||
|
return patchFakeSigning(ref wadFile.Contents[esIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patches es_identify.
|
||||||
|
/// Returns the number of applied patches.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int PatchEsIdentify()
|
||||||
|
{
|
||||||
|
if (esIndex < 0) return -1;
|
||||||
|
return patchEsIdentify(ref wadFile.Contents[esIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patches nand permissions.
|
||||||
|
/// Returns the number of applied patches.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int PatchNandPermissions()
|
||||||
|
{
|
||||||
|
if (esIndex < 0) return -1;
|
||||||
|
return patchNandPermissions(ref wadFile.Contents[esIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patches fakesigning, es_identify and nand permissions.
|
||||||
|
/// Returns the number of applied patches.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int PatchAll()
|
||||||
|
{
|
||||||
|
if (esIndex < 0) return -1;
|
||||||
|
return patchAll(ref wadFile.Contents[esIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patches fakesigning.
|
||||||
|
/// Returns the number of applied patches.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int PatchFakeSigning(ref byte[] esModule)
|
||||||
|
{
|
||||||
|
return patchFakeSigning(ref esModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patches es_identify.
|
||||||
|
/// Returns the number of applied patches.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int PatchEsIdentify(ref byte[] esModule)
|
||||||
|
{
|
||||||
|
return patchEsIdentify(ref esModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patches nand permissions.
|
||||||
|
/// Returns the number of applied patches.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public int PatchNandPermissions(ref byte[] esModule)
|
||||||
|
{
|
||||||
|
return patchNandPermissions(ref esModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Patches fakesigning, es_identify and nand permissions.
|
||||||
|
/// Returns the number of applied patches.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Fires the Progress of various operations
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ProgressChangedEventArgs> Progress;
|
||||||
|
/// <summary>
|
||||||
|
/// Fires debugging messages. You may write them into a log file or log textbox.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<MessageEventArgs> Debug;
|
||||||
|
|
||||||
|
private void fireDebug(string debugMessage, params object[] args)
|
||||||
|
{
|
||||||
|
EventHandler<MessageEventArgs> debug = Debug;
|
||||||
|
if (debug != null)
|
||||||
|
debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireProgress(int progressPercentage)
|
||||||
|
{
|
||||||
|
EventHandler<ProgressChangedEventArgs> progress = Progress;
|
||||||
|
if (progress != null)
|
||||||
|
progress(new object(), new ProgressChangedEventArgs(progressPercentage, string.Empty));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
400
NUS Downloader/Lz77.cs
Normal file
400
NUS Downloader/Lz77.cs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lz77 Magic.
|
||||||
|
/// </summary>
|
||||||
|
public static uint Lz77Magic { get { return lz77Magic; } }
|
||||||
|
|
||||||
|
#region Public Functions
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether a file is Lz77 compressed or not.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsLz77Compressed(string file)
|
||||||
|
{
|
||||||
|
return IsLz77Compressed(File.ReadAllBytes(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether a file is Lz77 compressed or not.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool IsLz77Compressed(byte[] file)
|
||||||
|
{
|
||||||
|
Headers.HeaderType h = Headers.DetectHeader(file);
|
||||||
|
return (Shared.Swap(BitConverter.ToUInt32(file, (int)h)) == lz77Magic) ;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether a file is Lz77 compressed or not.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compresses a file using the Lz77 algorithm.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inFile"></param>
|
||||||
|
/// <param name="outFile"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compresses the byte array using the Lz77 algorithm.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public byte[] Compress(byte[] file)
|
||||||
|
{
|
||||||
|
return ((MemoryStream)compress(new MemoryStream(file))).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compresses the stream using the Lz77 algorithm.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Stream Compress(Stream file)
|
||||||
|
{
|
||||||
|
return compress(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decompresses a file using the Lz77 algorithm.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inFile"></param>
|
||||||
|
/// <param name="outFile"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decompresses the byte array using the Lz77 algorithm.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -68,7 +68,10 @@
|
|||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="CertificateChain.cs" />
|
||||||
|
<Compile Include="CommonKey.cs" />
|
||||||
<Compile Include="COMTypes.cs" />
|
<Compile Include="COMTypes.cs" />
|
||||||
|
<Compile Include="ContentIndices.cs" />
|
||||||
<Compile Include="Form1.cs">
|
<Compile Include="Form1.cs">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<SubType>Form</SubType>
|
<SubType>Form</SubType>
|
||||||
@ -77,6 +80,9 @@
|
|||||||
<DependentUpon>Form1.cs</DependentUpon>
|
<DependentUpon>Form1.cs</DependentUpon>
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Headers.cs" />
|
||||||
|
<Compile Include="IosPatcher.cs" />
|
||||||
|
<Compile Include="Lz77.cs" />
|
||||||
<Compile Include="NusClient.cs" />
|
<Compile Include="NusClient.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
@ -108,6 +114,11 @@
|
|||||||
<DependentUpon>Settings.settings</DependentUpon>
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Shared.cs" />
|
||||||
|
<Compile Include="Ticket.cs" />
|
||||||
|
<Compile Include="TMD.cs" />
|
||||||
|
<Compile Include="U8.cs" />
|
||||||
|
<Compile Include="WAD.cs" />
|
||||||
<Compile Include="WADPacker.cs" />
|
<Compile Include="WADPacker.cs" />
|
||||||
<Compile Include="WaterMarkTextBox.cs">
|
<Compile Include="WaterMarkTextBox.cs">
|
||||||
<SubType>Component</SubType>
|
<SubType>Component</SubType>
|
||||||
|
272
NUS Downloader/Shared.cs
Normal file
272
NUS Downloader/Shared.cs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace libWiiSharp
|
||||||
|
{
|
||||||
|
public static class Shared
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Merges two string arrays into one without double entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="a"></param>
|
||||||
|
/// <param name="b"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string[] MergeStringArrays(string[] a, string[] b)
|
||||||
|
{
|
||||||
|
List<string> sList = new List<string>(a);
|
||||||
|
|
||||||
|
foreach (string currentString in b)
|
||||||
|
if (!sList.Contains(currentString)) sList.Add(currentString);
|
||||||
|
|
||||||
|
sList.Sort();
|
||||||
|
return sList.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two byte arrays.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="first"></param>
|
||||||
|
/// <param name="firstIndex"></param>
|
||||||
|
/// <param name="second"></param>
|
||||||
|
/// <param name="secondIndex"></param>
|
||||||
|
/// <param name="length"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares two byte arrays.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="first"></param>
|
||||||
|
/// <param name="second"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Turns a byte array into a string, default separator is a space.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="byteArray"></param>
|
||||||
|
/// <param name="separator"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Turns a hex string into a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hexString"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Counts how often the given char exists in the given string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="theString"></param>
|
||||||
|
/// <param name="theChar"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static int CountCharsInString(string theString, char theChar)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
foreach (char thisChar in theString)
|
||||||
|
if (thisChar == theChar)
|
||||||
|
count++;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pads the given value to a multiple of the given padding value, default padding value is 64.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static long AddPadding(long value)
|
||||||
|
{
|
||||||
|
return AddPadding(value, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pads the given value to a multiple of the given padding value, default padding value is 64.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="padding"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static long AddPadding(long value, int padding)
|
||||||
|
{
|
||||||
|
if (value % padding != 0)
|
||||||
|
{
|
||||||
|
value = value + (padding - (value % padding));
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pads the given value to a multiple of the given padding value, default padding value is 64.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static int AddPadding(int value)
|
||||||
|
{
|
||||||
|
return AddPadding(value, 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pads the given value to a multiple of the given padding value, default padding value is 64.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <param name="padding"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static int AddPadding(int value, int padding)
|
||||||
|
{
|
||||||
|
if (value % padding != 0)
|
||||||
|
{
|
||||||
|
value = value + (padding - (value % padding));
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swaps endianness.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ushort Swap(ushort value)
|
||||||
|
{
|
||||||
|
return (ushort)IPAddress.HostToNetworkOrder((short)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swaps endianness.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static uint Swap(uint value)
|
||||||
|
{
|
||||||
|
return (uint)IPAddress.HostToNetworkOrder((int)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Swaps endianness
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ulong Swap(ulong value)
|
||||||
|
{
|
||||||
|
return (ulong)IPAddress.HostToNetworkOrder((long)value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Turns a ushort array into a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static byte[] UShortArrayToByteArray(ushort[] array)
|
||||||
|
{
|
||||||
|
List<byte> results = new List<byte>();
|
||||||
|
foreach (ushort value in array)
|
||||||
|
{
|
||||||
|
byte[] converted = BitConverter.GetBytes(value);
|
||||||
|
results.AddRange(converted);
|
||||||
|
}
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Turns a uint array into a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static byte[] UIntArrayToByteArray(uint[] array)
|
||||||
|
{
|
||||||
|
List<byte> results = new List<byte>();
|
||||||
|
foreach (uint value in array)
|
||||||
|
{
|
||||||
|
byte[] converted = BitConverter.GetBytes(value);
|
||||||
|
results.AddRange(converted);
|
||||||
|
}
|
||||||
|
return results.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Turns a byte array into a uint array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Turns a byte array into a ushort array.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="array"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
659
NUS Downloader/TMD.cs
Normal file
659
NUS Downloader/TMD.cs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<TMD_Content> contents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The region of the title.
|
||||||
|
/// </summary>
|
||||||
|
public Region Region { get { return (Region)region; } set { region = (ushort)value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The IOS the title is launched with.
|
||||||
|
/// </summary>
|
||||||
|
public ulong StartupIOS { get { return startupIos; } set { startupIos = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The Title ID.
|
||||||
|
/// </summary>
|
||||||
|
public ulong TitleID { get { return titleId; } set { titleId = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The Title Version.
|
||||||
|
/// </summary>
|
||||||
|
public ushort TitleVersion { get { return titleVersion; } set { titleVersion = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The Number of Contents.
|
||||||
|
/// </summary>
|
||||||
|
public ushort NumOfContents { get { return numOfContents; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The boot index. Represents the index of the nand loader.
|
||||||
|
/// </summary>
|
||||||
|
public ushort BootIndex { get { return bootIndex; } set { if (value <= numOfContents) bootIndex = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The content descriptions in the TMD.
|
||||||
|
/// </summary>
|
||||||
|
public TMD_Content[] Contents { get { return contents.ToArray(); } set { contents = new List<TMD_Content>(value); numOfContents = (ushort)value.Length; } }
|
||||||
|
/// <summary>
|
||||||
|
/// If true, the TMD will be fakesigned while saving.
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tmd file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToTmd"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static TMD Load(string pathToTmd)
|
||||||
|
{
|
||||||
|
return Load(File.ReadAllBytes(pathToTmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tmd file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tmdFile"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tmd file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tmd"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static TMD Load(Stream tmd)
|
||||||
|
{
|
||||||
|
TMD t = new TMD();
|
||||||
|
t.parseTmd(tmd);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tmd file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToTmd"></param>
|
||||||
|
public void LoadFile(string pathToTmd)
|
||||||
|
{
|
||||||
|
LoadFile(File.ReadAllBytes(pathToTmd));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tmd file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tmdFile"></param>
|
||||||
|
public void LoadFile(byte[] tmdFile)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream(tmdFile);
|
||||||
|
|
||||||
|
try { parseTmd(ms); }
|
||||||
|
catch { ms.Dispose(); throw; }
|
||||||
|
|
||||||
|
ms.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tmd file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tmd"></param>
|
||||||
|
public void LoadFile(Stream tmd)
|
||||||
|
{
|
||||||
|
parseTmd(tmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the TMD.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="savePath"></param>
|
||||||
|
public void Save(string savePath)
|
||||||
|
{
|
||||||
|
Save(savePath, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the TMD. If fakeSign is true, the Ticket will be fakesigned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="savePath"></param>
|
||||||
|
/// <param name="fakeSign"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the TMD as a memory stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MemoryStream ToMemoryStream()
|
||||||
|
{
|
||||||
|
return ToMemoryStream(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the TMD as a memory stream. If fakeSign is true, the Ticket will be fakesigned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fakeSign"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MemoryStream ToMemoryStream(bool fakeSign)
|
||||||
|
{
|
||||||
|
if (fakeSign) this.fakeSign = true;
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
|
||||||
|
try { writeToStream(ms); }
|
||||||
|
catch { ms.Dispose(); throw; }
|
||||||
|
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the TMD as a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public byte[] ToByteArray()
|
||||||
|
{
|
||||||
|
return ToByteArray(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the TMD as a byte array. If fakeSign is true, the Ticket will be fakesigned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fakeSign"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the content entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentDir"></param>
|
||||||
|
/// <param name="namedContentId">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)</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the content entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentDir"></param>
|
||||||
|
/// <param name="namedContentId">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)</param>
|
||||||
|
public void UpdateContents(byte[][] contents)
|
||||||
|
{
|
||||||
|
updateContents(contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Upper Title ID as a string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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] });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Number of memory blocks the content will take.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetNandBlocks()
|
||||||
|
{
|
||||||
|
return calculateNandBlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a TMD content.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="content"></param>
|
||||||
|
public void AddContent(TMD_Content content)
|
||||||
|
{
|
||||||
|
contents.Add(content);
|
||||||
|
|
||||||
|
numOfContents = (ushort)contents.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the content with the given index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentIndex"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the content with the given ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="contentId"></param>
|
||||||
|
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<ContentIndices> contentList = new List<ContentIndices>();
|
||||||
|
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<TMD_Content>();
|
||||||
|
|
||||||
|
//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
|
||||||
|
/// <summary>
|
||||||
|
/// Fires debugging messages. You may write them into a log file or log textbox.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<MessageEventArgs> Debug;
|
||||||
|
|
||||||
|
private void fireDebug(string debugMessage, params object[] args)
|
||||||
|
{
|
||||||
|
EventHandler<MessageEventArgs> 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; } }
|
||||||
|
}
|
||||||
|
}
|
609
NUS Downloader/Ticket.cs
Normal file
609
NUS Downloader/Ticket.cs
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Title Key the WADs content is encrypted with.
|
||||||
|
/// </summary>
|
||||||
|
public byte[] TitleKey { get { return decryptedTitleKey; } set { decryptedTitleKey = value; titleKeyChanged = true; reDecrypt = false; } }
|
||||||
|
/// <summary>
|
||||||
|
/// Defines which Common Key is used (Standard / Korean).
|
||||||
|
/// </summary>
|
||||||
|
public CommonKeyType CommonKeyIndex { get { return (CommonKeyType)newKeyIndex; } set { newKeyIndex = (byte)value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The Ticket ID.
|
||||||
|
/// </summary>
|
||||||
|
public ulong TicketID { get { return ticketId; } set { ticketId = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The Console ID.
|
||||||
|
/// </summary>
|
||||||
|
public uint ConsoleID { get { return consoleId; } set { consoleId = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// The Title ID.
|
||||||
|
/// </summary>
|
||||||
|
public ulong TitleID { get { return titleId; } set { titleId = value; if (reDecrypt) reDecryptTitleKey(); } }
|
||||||
|
/// <summary>
|
||||||
|
/// Number of DLC.
|
||||||
|
/// </summary>
|
||||||
|
public ushort NumOfDLC { get { return numOfDlc; } set { numOfDlc = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// If true, the Ticket will be fakesigned while saving.
|
||||||
|
/// </summary>
|
||||||
|
public bool FakeSign { get { return fakeSign; } set { fakeSign = value; } }
|
||||||
|
/// <summary>
|
||||||
|
/// True if the Title Key was changed.
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tik file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToTicket"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Ticket Load(string pathToTicket)
|
||||||
|
{
|
||||||
|
return Load(File.ReadAllBytes(pathToTicket));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tik file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ticket"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tik file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ticket"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Ticket Load(Stream ticket)
|
||||||
|
{
|
||||||
|
Ticket tik = new Ticket();
|
||||||
|
tik.parseTicket(ticket);
|
||||||
|
return tik;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tik file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pathToTicket"></param>
|
||||||
|
public void LoadFile(string pathToTicket)
|
||||||
|
{
|
||||||
|
LoadFile(File.ReadAllBytes(pathToTicket));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tik file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ticket"></param>
|
||||||
|
public void LoadFile(byte[] ticket)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream(ticket);
|
||||||
|
|
||||||
|
try { parseTicket(ms); }
|
||||||
|
catch { ms.Dispose(); throw; }
|
||||||
|
|
||||||
|
ms.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads a tik file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ticket"></param>
|
||||||
|
public void LoadFile(Stream ticket)
|
||||||
|
{
|
||||||
|
parseTicket(ticket);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the Ticket.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="savePath"></param>
|
||||||
|
public void Save(string savePath)
|
||||||
|
{
|
||||||
|
Save(savePath, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the Ticket. If fakeSign is true, the Ticket will be fakesigned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="savePath"></param>
|
||||||
|
/// <param name="fakeSign"></param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Ticket as a memory stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MemoryStream ToMemoryStream()
|
||||||
|
{
|
||||||
|
return ToMemoryStream(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Ticket as a memory stream. If fakeSign is true, the Ticket will be fakesigned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fakeSign"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MemoryStream ToMemoryStream(bool fakeSign)
|
||||||
|
{
|
||||||
|
if (fakeSign) this.fakeSign = true;
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
|
||||||
|
try { writeToStream(ms); }
|
||||||
|
catch { ms.Dispose(); throw; }
|
||||||
|
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Ticket as a byte array.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public byte[] ToByteArray()
|
||||||
|
{
|
||||||
|
return ToByteArray(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Ticket as a byte array. If fakeSign is true, the Ticket will be fakesigned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fakeSign"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This will set a new encrypted Title Key (i.e. the one that you can "read" in the Ticket).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newTitleKey"></param>
|
||||||
|
public void SetTitleKey(string newTitleKey)
|
||||||
|
{
|
||||||
|
SetTitleKey(newTitleKey.ToCharArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This will set a new encrypted Title Key (i.e. the one that you can "read" in the Ticket).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newTitleKey"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This will set a new encrypted Title Key (i.e. the one that you can "read" in the Ticket).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newTitleKey"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the Upper Title ID as a string.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Fires debugging messages. You may write them into a log file or log textbox.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<MessageEventArgs> Debug;
|
||||||
|
|
||||||
|
private void fireDebug(string debugMessage, params object[] args)
|
||||||
|
{
|
||||||
|
EventHandler<MessageEventArgs> debug = Debug;
|
||||||
|
if (debug != null)
|
||||||
|
debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
1120
NUS Downloader/U8.cs
Normal file
1120
NUS Downloader/U8.cs
Normal file
File diff suppressed because it is too large
Load Diff
1269
NUS Downloader/WAD.cs
Normal file
1269
NUS Downloader/WAD.cs
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user