/* This file is part of libWiiSharp
* Copyright (C) 2009 Leathl
* Copyright (C) 2020 - 2022 TheShadowEevee, Github Contributors
*
* libWiiSharp is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* libWiiSharp is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
using System;
using System.IO;
using System.Security.Cryptography;
namespace libWiiSharp
{
public enum CommonKeyType : byte
{
Standard = 0x00,
Korean = 0x01,
}
public class Ticket : IDisposable
{
private byte newKeyIndex;
private byte[] decryptedTitleKey = new byte[16];
private bool fakeSign;
private bool titleKeyChanged;
private byte[] newEncryptedTitleKey = new byte[0];
private bool reDecrypt;
private uint signatureExponent = 65537;
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 = ushort.MaxValue;
private ushort numOfDlc;
private ulong unknown4;
private byte padding2;
private byte commonKeyIndex;
private byte[] unknown5 = new byte[48];
private byte[] unknown6 = new byte[32];
private ushort padding3;
private uint enableTimeLimit;
private uint timeLimit;
private byte[] padding4 = new byte[88];
private bool isDisposed;
public byte[] TitleKey
{
get => decryptedTitleKey;
set
{
decryptedTitleKey = value;
titleKeyChanged = true;
reDecrypt = false;
}
}
public CommonKeyType CommonKeyIndex
{
get => (CommonKeyType)newKeyIndex;
set => newKeyIndex = (byte)value;
}
public ulong TicketID
{
get => ticketId;
set => ticketId = value;
}
public uint ConsoleID
{
get => consoleId;
set => consoleId = value;
}
public ulong TitleID
{
get => titleId;
set
{
titleId = value;
if (!reDecrypt)
{
return;
}
PrivReDecryptTitleKey();
}
}
public ushort NumOfDLC
{
get => numOfDlc;
set => numOfDlc = value;
}
public bool FakeSign
{
get => fakeSign;
set => fakeSign = value;
}
public bool TitleKeyChanged => titleKeyChanged;
public event EventHandler Debug;
~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;
}
public static Ticket Load(string pathToTicket)
{
return Load(File.ReadAllBytes(pathToTicket));
}
public static Ticket Load(byte[] ticket)
{
Ticket ticket1 = new Ticket();
MemoryStream memoryStream = new MemoryStream(ticket);
try
{
ticket1.PrivParseTicket(memoryStream);
}
catch
{
memoryStream.Dispose();
throw;
}
memoryStream.Dispose();
return ticket1;
}
public static Ticket Load(Stream ticket)
{
Ticket ticket1 = new Ticket();
ticket1.PrivParseTicket(ticket);
return ticket1;
}
public void LoadFile(string pathToTicket)
{
LoadFile(File.ReadAllBytes(pathToTicket));
}
public void LoadFile(byte[] ticket)
{
MemoryStream memoryStream = new MemoryStream(ticket);
try
{
PrivParseTicket(memoryStream);
}
catch
{
memoryStream.Dispose();
throw;
}
memoryStream.Dispose();
}
public void LoadFile(Stream ticket)
{
PrivParseTicket(ticket);
}
public void Save(string savePath)
{
Save(savePath, false);
}
public void Save(string savePath, bool fakeSign)
{
if (fakeSign)
{
this.fakeSign = true;
}
if (File.Exists(savePath))
{
File.Delete(savePath);
}
using FileStream fileStream = new FileStream(savePath, FileMode.Create);
PrivWriteToStream(fileStream);
}
public MemoryStream ToMemoryStream()
{
return ToMemoryStream(false);
}
public MemoryStream ToMemoryStream(bool fakeSign)
{
if (fakeSign)
{
this.fakeSign = true;
}
MemoryStream memoryStream = new MemoryStream();
try
{
PrivWriteToStream(memoryStream);
return memoryStream;
}
catch
{
memoryStream.Dispose();
throw;
}
}
public byte[] ToByteArray()
{
return ToByteArray(false);
}
public byte[] ToByteArray(bool fakeSign)
{
if (fakeSign)
{
this.fakeSign = true;
}
MemoryStream memoryStream = new MemoryStream();
try
{
PrivWriteToStream(memoryStream);
}
catch
{
memoryStream.Dispose();
throw;
}
byte[] array = memoryStream.ToArray();
memoryStream.Dispose();
return array;
}
public void SetTitleKey(string newTitleKey)
{
SetTitleKey(newTitleKey.ToCharArray());
}
public void SetTitleKey(char[] newTitleKey)
{
if (newTitleKey.Length != 16)
{
throw new Exception("The title key must be 16 characters long!");
}
for (int index = 0; index < 16; ++index)
{
encryptedTitleKey[index] = (byte)newTitleKey[index];
}
PrivDecryptTitleKey();
titleKeyChanged = true;
reDecrypt = true;
newEncryptedTitleKey = encryptedTitleKey;
}
public void SetTitleKey(byte[] newTitleKey)
{
encryptedTitleKey = newTitleKey.Length == 16 ? newTitleKey : throw new Exception("The title key must be 16 characters long!");
PrivDecryptTitleKey();
titleKeyChanged = true;
reDecrypt = true;
newEncryptedTitleKey = newTitleKey;
}
public string GetUpperTitleID()
{
byte[] bytes = BitConverter.GetBytes(Shared.Swap((uint)titleId));
return new string(new char[4]
{
(char) bytes[0],
(char) bytes[1],
(char) bytes[2],
(char) bytes[3]
});
}
private void PrivWriteToStream(Stream writeStream)
{
FireDebug("Writing Ticket...");
FireDebug(" Encrypting Title Key...");
PrivEncryptTitleKey();
FireDebug(" -> Decrypted Title Key: {0}", (object)Shared.ByteArrayToString(decryptedTitleKey));
FireDebug(" -> Encrypted Title Key: {0}", (object)Shared.ByteArrayToString(encryptedTitleKey));
if (fakeSign)
{
FireDebug(" Clearing Signature...");
signature = new byte[256];
}
MemoryStream memoryStream = new MemoryStream();
memoryStream.Seek(0L, SeekOrigin.Begin);
FireDebug(" Writing Signature Exponent... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(BitConverter.GetBytes(Shared.Swap(signatureExponent)), 0, 4);
FireDebug(" Writing Signature... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(signature, 0, signature.Length);
FireDebug(" Writing Padding... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(padding, 0, padding.Length);
FireDebug(" Writing Issuer... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(issuer, 0, issuer.Length);
FireDebug(" Writing Unknown... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(unknown, 0, unknown.Length);
FireDebug(" Writing Title Key... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(encryptedTitleKey, 0, encryptedTitleKey.Length);
FireDebug(" Writing Unknown2... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.WriteByte(unknown2);
FireDebug(" Writing Ticket ID... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(BitConverter.GetBytes(Shared.Swap(ticketId)), 0, 8);
FireDebug(" Writing Console ID... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(BitConverter.GetBytes(Shared.Swap(consoleId)), 0, 4);
FireDebug(" Writing Title ID... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(BitConverter.GetBytes(Shared.Swap(titleId)), 0, 8);
FireDebug(" Writing Unknwon3... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(BitConverter.GetBytes(Shared.Swap(unknown3)), 0, 2);
FireDebug(" Writing NumOfDLC... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(BitConverter.GetBytes(Shared.Swap(numOfDlc)), 0, 2);
FireDebug(" Writing Unknwon4... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.Write(BitConverter.GetBytes(Shared.Swap(unknown4)), 0, 8);
FireDebug(" Writing Padding2... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.WriteByte(padding2);
FireDebug(" Writing Common Key Index... (Offset: 0x{0})", (object)memoryStream.Position.ToString("x8").ToUpper());
memoryStream.WriteByte(commonKeyIndex);
object[] objArray1 = new object[1];
long position = memoryStream.Position;
objArray1[0] = position.ToString("x8").ToUpper();
FireDebug(" Writing Unknown5... (Offset: 0x{0})", objArray1);
memoryStream.Write(unknown5, 0, unknown5.Length);
object[] objArray2 = new object[1];
position = memoryStream.Position;
objArray2[0] = position.ToString("x8").ToUpper();
FireDebug(" Writing Unknown6... (Offset: 0x{0})", objArray2);
memoryStream.Write(unknown6, 0, unknown6.Length);
object[] objArray3 = new object[1];
position = memoryStream.Position;
objArray3[0] = position.ToString("x8").ToUpper();
FireDebug(" Writing Padding3... (Offset: 0x{0})", objArray3);
memoryStream.Write(BitConverter.GetBytes(Shared.Swap(padding3)), 0, 2);
object[] objArray4 = new object[1];
position = memoryStream.Position;
objArray4[0] = position.ToString("x8").ToUpper();
FireDebug(" Writing Enable Time Limit... (Offset: 0x{0})", objArray4);
memoryStream.Write(BitConverter.GetBytes(Shared.Swap(enableTimeLimit)), 0, 4);
object[] objArray5 = new object[1];
position = memoryStream.Position;
objArray5[0] = position.ToString("x8").ToUpper();
FireDebug(" Writing Time Limit... (Offset: 0x{0})", objArray5);
memoryStream.Write(BitConverter.GetBytes(Shared.Swap(timeLimit)), 0, 4);
object[] objArray6 = new object[1];
position = memoryStream.Position;
objArray6[0] = position.ToString("x8").ToUpper();
FireDebug(" Writing Padding4... (Offset: 0x{0})", objArray6);
memoryStream.Write(padding4, 0, padding4.Length);
byte[] array = memoryStream.ToArray();
memoryStream.Dispose();
if (fakeSign)
{
FireDebug(" Fakesigning Ticket...");
//byte[] numArray = new byte[20];
SHA1 shA1 = SHA1.Create();
for (ushort index = 0; index < ushort.MaxValue; ++index)
{
byte[] bytes = BitConverter.GetBytes(index);
array[498] = bytes[1];
array[499] = bytes[0];
if (shA1.ComputeHash(array)[0] == 0)
{
FireDebug(" -> Signed ({0})", (object)index);
break;
}
if (index == 65534)
{
FireDebug(" -> Signing Failed...");
throw new Exception("Fakesigning failed...");
}
}
shA1.Clear();
}
writeStream.Seek(0L, SeekOrigin.Begin);
writeStream.Write(array, 0, array.Length);
FireDebug("Writing Ticket Finished...");
}
private void PrivParseTicket(Stream ticketFile)
{
FireDebug("Parsing Ticket...");
ticketFile.Seek(0L, SeekOrigin.Begin);
byte[] buffer = new byte[8];
FireDebug(" Reading Signature Exponent... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(buffer, 0, 4);
signatureExponent = Shared.Swap(BitConverter.ToUInt32(buffer, 0));
FireDebug(" Reading Signature... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(signature, 0, signature.Length);
FireDebug(" Reading Padding... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(padding, 0, padding.Length);
FireDebug(" Reading Issuer... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(issuer, 0, issuer.Length);
FireDebug(" Reading Unknown... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(unknown, 0, unknown.Length);
FireDebug(" Reading Title Key... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(encryptedTitleKey, 0, encryptedTitleKey.Length);
FireDebug(" Reading Unknown2... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
unknown2 = (byte)ticketFile.ReadByte();
FireDebug(" Reading Ticket ID.. (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(buffer, 0, 8);
ticketId = Shared.Swap(BitConverter.ToUInt64(buffer, 0));
FireDebug(" Reading Console ID... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(buffer, 0, 4);
consoleId = Shared.Swap(BitConverter.ToUInt32(buffer, 0));
FireDebug(" Reading Title ID... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(buffer, 0, 8);
titleId = Shared.Swap(BitConverter.ToUInt64(buffer, 0));
FireDebug(" Reading Unknown3... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
FireDebug(" Reading NumOfDLC... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(buffer, 0, 4);
unknown3 = Shared.Swap(BitConverter.ToUInt16(buffer, 0));
numOfDlc = Shared.Swap(BitConverter.ToUInt16(buffer, 2));
FireDebug(" Reading Unknown4... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(buffer, 0, 8);
unknown4 = Shared.Swap(BitConverter.ToUInt64(buffer, 0));
FireDebug(" Reading Padding2... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
padding2 = (byte)ticketFile.ReadByte();
FireDebug(" Reading Common Key Index... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
commonKeyIndex = (byte)ticketFile.ReadByte();
newKeyIndex = commonKeyIndex;
FireDebug(" Reading Unknown5... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(unknown5, 0, unknown5.Length);
FireDebug(" Reading Unknown6... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(unknown6, 0, unknown6.Length);
FireDebug(" Reading Padding3... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(buffer, 0, 2);
padding3 = Shared.Swap(BitConverter.ToUInt16(buffer, 0));
FireDebug(" Reading Enable Time Limit... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
FireDebug(" Reading Time Limit... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(buffer, 0, 8);
enableTimeLimit = Shared.Swap(BitConverter.ToUInt32(buffer, 0));
timeLimit = Shared.Swap(BitConverter.ToUInt32(buffer, 4));
FireDebug(" Reading Padding4... (Offset: 0x{0})", (object)ticketFile.Position.ToString("x8").ToUpper());
ticketFile.Read(padding4, 0, padding4.Length);
FireDebug(" Decrypting Title Key...");
PrivDecryptTitleKey();
FireDebug(" -> Encrypted Title Key: {0}", (object)Shared.ByteArrayToString(encryptedTitleKey));
FireDebug(" -> Decrypted Title Key: {0}", (object)Shared.ByteArrayToString(decryptedTitleKey));
FireDebug("Parsing Ticket Finished...");
}
private void PrivDecryptTitleKey()
{
byte[] numArray = commonKeyIndex == 1 ? CommonKey.GetKoreanKey() : CommonKey.GetStandardKey();
byte[] bytes = BitConverter.GetBytes(Shared.Swap(titleId));
Array.Resize(ref bytes, 16);
#pragma warning disable SYSLIB0022 // Type or member is obsolete
RijndaelManaged rijndaelManaged = new RijndaelManaged
{
Mode = CipherMode.CBC,
Padding = PaddingMode.None,
KeySize = 128,
BlockSize = 128,
Key = numArray,
IV = bytes
};
#pragma warning restore SYSLIB0022 // Type or member is obsolete
ICryptoTransform decryptor = rijndaelManaged.CreateDecryptor();
MemoryStream memoryStream = new MemoryStream(encryptedTitleKey);
CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
cryptoStream.Read(decryptedTitleKey, 0, decryptedTitleKey.Length);
cryptoStream.Dispose();
memoryStream.Dispose();
decryptor.Dispose();
rijndaelManaged.Clear();
}
private void PrivEncryptTitleKey()
{
commonKeyIndex = newKeyIndex;
byte[] numArray = commonKeyIndex == 1 ? CommonKey.GetKoreanKey() : CommonKey.GetStandardKey();
byte[] bytes = BitConverter.GetBytes(Shared.Swap(titleId));
Array.Resize(ref bytes, 16);
#pragma warning disable SYSLIB0022 // Type or member is obsolete
RijndaelManaged rijndaelManaged = new RijndaelManaged
{
Mode = CipherMode.CBC,
Padding = PaddingMode.None,
KeySize = 128,
BlockSize = 128,
Key = numArray,
IV = bytes
};
#pragma warning restore SYSLIB0022 // Type or member is obsolete
ICryptoTransform encryptor = rijndaelManaged.CreateEncryptor();
MemoryStream memoryStream = new MemoryStream(decryptedTitleKey);
CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Read);
cryptoStream.Read(encryptedTitleKey, 0, encryptedTitleKey.Length);
cryptoStream.Dispose();
memoryStream.Dispose();
encryptor.Dispose();
rijndaelManaged.Clear();
}
private void PrivReDecryptTitleKey()
{
encryptedTitleKey = newEncryptedTitleKey;
PrivDecryptTitleKey();
}
private void FireDebug(string debugMessage, params object[] args)
{
EventHandler debug = Debug;
if (debug == null)
{
return;
}
debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
}
}
}