nusdownloader/NUS Downloader/Form1.cs

2365 lines
90 KiB
C#

using System;
using System.Windows.Forms;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Xml;
using System.Drawing;
namespace NUS_Downloader
{
public partial class Form1 : Form
{
const string NUSURL = "http://nus.cdn.shop.wii.com/ccs/download/";
const string DSiNUSURL = "http://nus.cdn.t.shop.nintendowifi.net/ccs/download/";
// TODO: Always remember to change version!
string version = "v1.2";
WebClient generalWC = new WebClient();
static RijndaelManaged rijndaelCipher;
static bool dsidecrypt = false;
const string certs_MD5 = "7677AD47BAA5D6E3E313E72661FBDC16";
// Images do not compare unless globalized...
Image green = Properties.Resources.bullet_green;
Image orange = Properties.Resources.bullet_orange;
Image redorb = Properties.Resources.bullet_red;
Image redgreen = Properties.Resources.bullet_redgreen;
Image redorange = Properties.Resources.bullet_redorange;
public struct WADHeader
{
public int HeaderSize;
public int WadType;
public int CertChainSize;
public int Reserved;
public int TicketSize;
public int TMDSize;
public int DataSize;
public int FooterSize;
};
// This is the standard entry to the GUI
public Form1()
{
InitializeComponent();
BootChecks();
}
// CLI Mode
public Form1(string[] args)
{
InitializeComponent();
// certs.sys / key.bin
if (BootChecks() == false)
return;
// Vars
bool startnow = false;
bool endafter = false;
// Fix'd
localuse.Checked = false;
// Switch through arguments
for (int i = 0; i < args.Length; i++)
{
switch (args[i])
{
case "-t":
if (args[i + 1].Length == 16)
titleidbox.Text = args[i + 1];
else
{
WriteStatus("Title ID: Your Doing It Wrong (c)");
WriteStatus("ex: -t 0000000100000002");
}
break;
case "-v":
titleversion.Text = args[i + 1];
break;
case "-s":
startnow = true;
break;
case "-close":
endafter = true;
break;
case "-d":
decryptbox.Checked = true;
break;
case "-ticket":
ignoreticket.Checked = true;
break;
case "-local":
localuse.Checked = true;
break;
case "-p":
packbox.Checked = true;
wadnamebox.Text = args[i + 1];
break;
case "-dsi":
radioButton2.Checked = true;
break;
default:
break;
}
}
// Start doing stuff...
if ((startnow) && (titleidbox.Text.Length != 0))
{
// Prevent mass deletion
if ((titleidbox.Text == "") && (titleversion.Text == ""))
{
WriteStatus("Please enter SOME info...");
return;
}
else
{
if (!statusbox.Lines[0].StartsWith(" ---"))
statusbox.Text = " --- " + titleidbox.Text + " ---";
}
// Running Downloads in background so no form freezing
NUSDownloader.RunWorkerAsync();
}
// Close if specified
while (NUSDownloader.IsBusy)
{
// Do nothing...
}
if ((NUSDownloader.IsBusy == false) && (endafter == true))
{
Application.Exit();
}
}
private void Form1_Load(object sender, EventArgs e)
{
this.Text = "NUSD - " + version + " - WB3000";
this.Size = this.MinimumSize;
}
private bool BootChecks()
{
// Success?
bool result = true;
// Directory stuff
string currentdir = Application.StartupPath;
if (currentdir.EndsWith(Convert.ToString(Path.DirectorySeparatorChar)) == false)
currentdir += Path.DirectorySeparatorChar;
// Check for certs file
if (File.Exists(currentdir + "cert.sys") == false)
{
foreach (Control ctrl in this.Controls)
{
ctrl.Enabled = false;
}
getcerts.Enabled = true;
WriteStatus("You do not have a certs file. Press the button below to generate a cert file!");
result = false;
}
else if (verifyMd5Hash(currentdir + "cert.sys", certs_MD5) == false)
{
foreach (Control ctrl in this.Controls)
{
ctrl.Enabled = false;
}
getcerts.Enabled = true;
WriteStatus("Your certs file is corrupted/invalid. Press the button below to generate a cert file!");
result = false;
}
else
{
getcerts.Visible = false;
WriteStatus("Certs file is present and intact.");
}
// Check for Wii common key bin file...
if (File.Exists(currentdir + "key.bin") == false)
{
WriteStatus("Common Key (key.bin) missing! Decryption disabled!");
decryptbox.Visible = false;
}
else
{
WriteStatus("Common Key detected.");
}
// Check for Wii KOR common key bin file...
if (File.Exists(currentdir + "kkey.bin") == false)
{
//WriteStatus("Korean Common Key (kkey.bin) missing! Decryption disabled!");
//decryptbox.Visible = false;
}
else
{
WriteStatus("Korean Common Key detected.");
}
// Check for DSi common key bin file...
if (File.Exists(currentdir + "dsikey.bin") == false)
{
// Do not pester about DSi key
}
else
{
WriteStatus("DSi Common Key detected.");
dsidecrypt = true;
}
// Check for database.xml
if (File.Exists(currentdir + "database.xml") == false)
{
WriteStatus("Database.xml not found. Title database not usable!");
databaseButton.Visible = false;
TMDButton.Size = new System.Drawing.Size(134, 20);
TMDButton.Anchor = AnchorStyles.Right;
}
else
{
// Read version of Database.xml
XmlDocument xDoc = new XmlDocument();
xDoc.Load("database.xml");
XmlNodeList DatabaseList = xDoc.GetElementsByTagName("database");
XmlAttributeCollection Attributes = DatabaseList[0].Attributes;
WriteStatus("Database.xml detected.");
WriteStatus(" - Version: " + Attributes[0].Value);
// Load it up...
ClearDatabaseStrip();
FillDatabaseStrip();
LoadRegionCodes();
ShowInnerToolTips(false);
}
return result;
}
private void button1_Click(object sender, EventArgs e)
{
// Show dialog for opening TMD file...
OpenFileDialog opentmd = new OpenFileDialog();
opentmd.Filter = "TMD Files|tmd";
opentmd.Title = "Open TMD";
if (opentmd.ShowDialog() != DialogResult.Cancel)
{
// Read the tmd as a stream...
byte[] tmd = FileLocationToByteArray(opentmd.FileName);
WriteStatus("TMD Loaded (" + tmd.Length + " bytes)");
// Read ID...
for (int x = 396; x < 404; x++)
{
titleidbox.Text += MakeProperLength(ConvertToHex(Convert.ToString(tmd[x])));
}
WriteStatus("Title ID: " + titleidbox.Text);
// Show TitleID Type/likelyhood of NUS existance...
ReadIDType(titleidbox.Text);
// Read Title Version...
string tmdversion = "";
for (int x = 476; x < 478; x++)
{
tmdversion += MakeProperLength(ConvertToHex(Convert.ToString(tmd[x])));
}
titleversion.Text = Convert.ToString(int.Parse(tmdversion, System.Globalization.NumberStyles.HexNumber));
// Read System Version (Needed IOS)
string sysversion = IOSNeededFromTMD(tmd);
if (sysversion != "0")
WriteStatus("Requires: IOS" + sysversion);
// Read Content #...
int nbr_cont = ContentCount(tmd);
/*string contentstrnum = "";
for (int x = 478; x < 480; x++)
{
contentstrnum += TrimLeadingZeros(Convert.ToString(tmd[x]));
}*/
WriteStatus("Content Count: " + nbr_cont);
string[] tmdcontents = GetContentNames(tmd, nbr_cont);
string[] tmdsizes = GetContentSizes(tmd, nbr_cont);
byte[] tmdhashes = GetContentHashes(tmd, nbr_cont);
byte[] tmdindices = GetContentIndices(tmd, nbr_cont);
// Loop through each content and display name, hash, index
for (int i = 0; i < nbr_cont; i++)
{
WriteStatus(" Content " + (i + 1) + ": " + tmdcontents[i] + " (" + Convert.ToString(int.Parse(tmdsizes[i], System.Globalization.NumberStyles.HexNumber)) + " bytes)");
byte[] hash = new byte[20];
for (int x = 0; x < 20; x++)
{
hash[x] = tmdhashes[(i*20)+x];
}
WriteStatus(" - Hash: " + DisplayBytes(hash, ""));
WriteStatus(" - Index: " + tmdindices[i]);
}
}
}
private string IOSNeededFromTMD(byte[] tmd)
{
string sysversion = "";
for (int i = 0; i < 8; i++)
{
sysversion += MakeProperLength(ConvertToHex(Convert.ToString(tmd[0x184 + i])));
}
sysversion = Convert.ToString(int.Parse(sysversion.Substring(14, 2), System.Globalization.NumberStyles.HexNumber));
return sysversion;
}
private int ContentCount(byte[] tmd)
{
// nbr_cont (0xDE) len=0x02
int nbr_cont = 0;
nbr_cont = (tmd[0x1DE] * 256) + tmd[0x1DF];
return nbr_cont;
}
private int BootIndex(byte[] tmd)
{
// nbr_cont (0xE0) len=0x02
int bootidx = 0;
bootidx = (tmd[0x1E0] * 256) + tmd[0x1E1];
return bootidx;
}
private void WriteStatus(string Update)
{
// Small function for writing text to the statusbox...
if (statusbox.Text == "")
statusbox.Text = Update;
else
statusbox.Text += "\r\n" + Update;
// Scroll to end of text box.
statusbox.SelectionStart = statusbox.TextLength;
statusbox.ScrollToCaret();
}
/// <summary>
/// Reads data from a stream until the end is reached. The
/// data is returned as a byte array. An IOException is
/// thrown if any of the underlying IO calls fail.
/// </summary>
/// <param name="stream">The stream to read data from</param>
/// <param name="initialLength">The initial buffer length</param>
public static byte[] ReadFully(Stream stream, int initialLength)
{
// If we've been passed an unhelpful initial length, just
// use 32K.
if (initialLength < 1)
{
initialLength = 32768;
}
byte[] buffer = new byte[initialLength];
int read = 0;
int chunk;
while ((chunk = stream.Read(buffer, read, buffer.Length - read)) > 0)
{
read += chunk;
// If we've reached the end of our buffer, check to see if there's
// any more information
if (read == buffer.Length)
{
int nextByte = stream.ReadByte();
// End of stream? If so, we're done
if (nextByte == -1)
{
return buffer;
}
// Nope. Resize the buffer, put in the byte we've just
// read, and continue
byte[] newBuffer = new byte[buffer.Length * 2];
Array.Copy(buffer, newBuffer, buffer.Length);
newBuffer[read] = (byte)nextByte;
buffer = newBuffer;
read++;
}
}
// Buffer is now too big. Shrink it.
byte[] ret = new byte[read];
Array.Copy(buffer, ret, read);
return ret;
}
private string MakeProperLength(string hex)
{
// If hex is like, 'A', makes it '0A', etc.
if (hex.Length == 1)
hex = "0" + hex;
return hex;
}
private string ConvertToHex(string decval)
{
// Convert text string to unsigned integer
int uiDecimal = System.Convert.ToInt32(decval);
return String.Format("{0:x2}", uiDecimal);
}
private void ReadIDType(string ttlid)
{
/* Wiibrew TitleID Info...
# 3 00000001: Essential system titles
# 4 00010000 and 00010004 : Disc-based games
# 5 00010001: Downloaded channels
* 5.1 000010001-Cxxx : Commodore 64 Games
* 5.2 000010001-Exxx : NeoGeo Games
* 5.3 000010001-Fxxx : NES Games
* 5.4 000010001-Hxxx : Channels
* 5.5 000010001-Jxxx : SNES Games
* 5.6 000010001-Nxxx : Nintendo 64 Games
* 5.7 000010001-Wxxx : WiiWare
# 6 00010002: System channels
# 7 00010004: Game channels and games that use them
# 8 00010005: Downloaded Game Content
# 9 00010008: "Hidden" channels
*/
if (ttlid.Substring(0, 8) == "00000001")
{
WriteStatus("ID Type: System Title. BE CAREFUL!");
}
else if ((ttlid.Substring(0, 8) == "00010000") || (ttlid.Substring(0, 8) == "00010004"))
{
WriteStatus("ID Type: Disc-Based Game. Unlikely NUS Content!");
}
else if (ttlid.Substring(0, 8) == "00010001")
{
WriteStatus("ID Type: Downloaded Channel. Possible NUS Content.");
}
else if (ttlid.Substring(0, 8) == "00010002")
{
WriteStatus("ID Type: System Channel. BE CAREFUL!");
}
else if (ttlid.Substring(0, 8) == "00010004")
{
WriteStatus("ID Type: Game Channel. Unlikely NUS Content!");
}
else if (ttlid.Substring(0, 8) == "00010005")
{
WriteStatus("ID Type: Downloaded Game Content. Unlikely NUS Content!");
}
else if (ttlid.Substring(0, 8) == "00010008")
{
WriteStatus("ID Type: 'Hidden' Channel. Unlikely NUS Content!");
}
else
{
WriteStatus("ID Type: Unknown. Unlikely NUS Content!");
}
}
private string TrimLeadingZeros(string num)
{
int startindex = 0;
for (int i = 0; i < num.Length; i++)
{
if ((num[i] == 0) || (num[i] == '0'))
startindex += 1;
else
break;
}
return num.Substring(startindex, (num.Length - startindex));
}
private string[] GetContentNames(byte[] tmdfile, int length)
{
string[] contentnames = new string[length];
int startoffset = 484;
for (int i = 0; i < length; i++)
{
contentnames[i] = MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset]))) +
MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset + 1]))) +
MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset + 2]))) +
MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset + 3])));
startoffset += 36;
}
return contentnames;
}
private string[] GetContentSizes(byte[] tmdfile, int length)
{
string[] contentsizes = new string[length];
int startoffset = 492;
for (int i = 0; i < length; i++)
{
contentsizes[i] = MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset]))) +
MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset + 1]))) +
MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset + 2]))) +
MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset + 3]))) +
MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset + 4]))) +
MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset + 5]))) +
MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset + 6]))) +
MakeProperLength(ConvertToHex(Convert.ToString(tmdfile[startoffset + 7])));
contentsizes[i] = TrimLeadingZeros(contentsizes[i]);
/*contentsizes[i] = Convert.ToString(tmdfile[startoffset]) +
Convert.ToString(tmdfile[startoffset + 1]) +
Convert.ToString(tmdfile[startoffset + 2]) +
Convert.ToString(tmdfile[startoffset + 3]) +
Convert.ToString(tmdfile[startoffset + 4]) +
Convert.ToString(tmdfile[startoffset + 5]) +
Convert.ToString(tmdfile[startoffset + 6]) +
Convert.ToString(tmdfile[startoffset + 7]);
contentsizes[i] = TrimLeadingZeros(contentsizes[i]); */
startoffset += 36;
}
return contentsizes;
}
private byte[] GetContentHashes(byte[] tmdfile, int length)
{
byte[] contenthashes = new byte[length*20];
int startoffset = 500;
for (int i = 0; i < length; i++)
{
for (int x = 0; x < 20; x++)
{
contenthashes[(i * 20) + x] = tmdfile[startoffset + x];
}
startoffset += 36;
}
return contenthashes;
}
private byte[] GetContentIndices(byte[] tmdfile, int length)
{
byte[] contentindices = new byte[length];
int startoffset = 0x1E9;
for (int i = 0; i < length; i++)
{
contentindices[i] = tmdfile[startoffset];
startoffset += 36;
}
return contentindices;
}
private void button3_Click(object sender, EventArgs e)
{
// Prevent mass deletion
if ((titleidbox.Text == "") && (titleversion.Text == ""))
{
WriteStatus("Please enter SOME info...");
return;
}
else
{
try
{
if (!statusbox.Lines[0].StartsWith(" ---"))
statusbox.Text = " --- " + titleidbox.Text + " ---";
}
catch // No lines present...
{
statusbox.Text = " --- " + titleidbox.Text + " ---";
}
}
// Running Downloads in background so no form freezing
NUSDownloader.RunWorkerAsync();
}
private void NUSDownloader_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
// Preparations for Downloading
Control.CheckForIllegalCrossThreadCalls = false;
WriteStatus("Starting NUS Download. Please be patient!");
SetEnableforDownload(false);
downloadstartbtn.Text = "Starting NUS Download!";
// Current directory...
string currentdir = Application.StartupPath;
if (!(currentdir.EndsWith(@"\")) || !(currentdir.EndsWith(@"/")))
currentdir += @"\";
// Prevent crossthread issues
string titleid = titleidbox.Text;
// Creates the directory
CreateTitleDirectory();
// Wii / DSi
bool wiimode = radioButton1.Checked;
// Set UserAgent to Wii value
generalWC.Headers.Add("User-Agent", "Opera/9.30 (Nintendo Wii; U; ; 2071; Wii Shop Channel/16.0(A); en)");
// Get placement directory early...
string titledirectory;
if (titleversion.Text == "")
titledirectory = currentdir + titleid + @"\";
else
titledirectory = currentdir + titleid + "v" + titleversion.Text + @"\";
downloadstartbtn.Text = "Prerequisites: (0/2)";
// Download TMD before the rest...
string tmdfull = "tmd";
if (titleversion.Text != "")
tmdfull += "." + titleversion.Text;
try
{
DownloadNUSFile(titleid, tmdfull, titledirectory, 0, wiimode);
}
catch (Exception ex)
{
WriteStatus("Download Failed: " + tmdfull);
WriteStatus(" - Reason: " + ex.Message.ToString().Replace("The remote server returned an error: ", ""));
SetEnableforDownload(true);
downloadstartbtn.Text = "Start NUS Download!";
dlprogress.Value = 0;
DeleteTitleDirectory();
return;
}
downloadstartbtn.Text = "Prerequisites: (1/2)";
dlprogress.Value = 50;
// Download CETK after tmd...
try
{
DownloadNUSFile(titleid, "cetk", titledirectory, 0, wiimode);
}
catch (Exception ex)
{
if (ignoreticket.Checked == false)
{
WriteStatus("Download Failed: cetk");
WriteStatus(" - Reason: " + ex.Message.ToString().Replace("The remote server returned an error: ", ""));
WriteStatus("You may be able to retrieve the contents by Ignoring the Ticket (Check below)");
SetEnableforDownload(true);
downloadstartbtn.Text = "Start NUS Download!";
dlprogress.Value = 0;
DeleteTitleDirectory();
return;
}
else
{
WriteStatus("Ticket not found! Continuing, however WAD packing and decryption are not possible!");
packbox.Checked = false;
decryptbox.Checked = false;
}
}
downloadstartbtn.Text = "Prerequisites: (2/2)";
dlprogress.Value = 100;
// Obtain TitleKey
byte[] titlekey = new byte[16];
if (decryptbox.Checked == true)
{
// Create ticket file holder
byte[] cetkbuf = FileLocationToByteArray(titledirectory + @"\cetk");
// Load TitleKey into it's byte[]
// It is currently encrypted...
for (int i = 0; i < 16; i++)
{
titlekey[i] = cetkbuf[0x1BF + i];
}
// IV (TITLEID+0000s)
byte[] iv = new byte[16];
for (int i = 0; i < 8; i++)
{
iv[i] = cetkbuf[0x1DC + i];
}
for (int i = 0; i < 8; i++)
{
iv[i+8] = 0x00;
}
// Standard/Korea Common Key
byte[] keyBytes;
if (cetkbuf[0x01F1] == 0x01)
{
WriteStatus("Key Type: Korean");
keyBytes = LoadCommonKey(@"\kkey.bin");
}
else
{
WriteStatus("Key Type: Standard");
if (wiimode)
keyBytes = LoadCommonKey(@"\key.bin");
else
keyBytes = LoadCommonKey(@"\dsikey.bin");
}
initCrypt(iv, keyBytes);
WriteStatus("Title Key: " + DisplayBytes(Decrypt(titlekey), ""));
titlekey = Decrypt(titlekey);
}
// Read the tmd as a stream...
byte[] tmd = FileLocationToByteArray(titledirectory + tmdfull);
// Read Title Version...
string tmdversion = "";
for (int x = 476; x < 478; x++)
{
tmdversion += MakeProperLength(ConvertToHex(Convert.ToString(tmd[x])));
}
titleversion.Text = Convert.ToString(int.Parse(tmdversion, System.Globalization.NumberStyles.HexNumber));
//Read System Version (Needed IOS)
string sysversion = IOSNeededFromTMD(tmd);
if (sysversion != "0")
WriteStatus("Requires: IOS" + sysversion);
// Renaming would be ideal, but gives too many errors...
/*if ((currentdir + titleid + "v" + titleversion.Text + @"\") != titledirectory)
{
Directory.Move(titledirectory, currentdir + titleid + "v" + titleversion.Text + @"\");
titledirectory = currentdir + titleid + "v" + titleversion.Text + @"\";
DirectoryInfo di = new DirectoryInfo(titledirectory);
} */
// Read Content #...
string contentstrnum = "";
for (int x = 478; x < 480; x++)
{
contentstrnum += TrimLeadingZeros(Convert.ToString(tmd[x]));
}
WriteStatus("Content #: " + contentstrnum);
downloadstartbtn.Text = "Content: (0/" + contentstrnum + ")";
dlprogress.Value = 0;
// Gather information...
string[] tmdcontents = GetContentNames(tmd, Convert.ToInt32(contentstrnum));
string[] tmdsizes = GetContentSizes(tmd, Convert.ToInt32(contentstrnum));
byte[] tmdhashes = GetContentHashes(tmd, Convert.ToInt32(contentstrnum));
byte[] tmdindices = GetContentIndices(tmd, Convert.ToInt32(contentstrnum));
// Progress bar total size tally info...
float totalcontentsize = 0;
float currentcontentlocation = 0;
for (int i = 0; i < tmdsizes.Length; i++)
{
totalcontentsize += int.Parse(tmdsizes[i], System.Globalization.NumberStyles.HexNumber);
}
WriteStatus("Total Size: " + (long)totalcontentsize + " bytes");
for (int i = 0; i < tmdcontents.Length; i++)
{
try
{
// If it exists we leave it...
if ((localuse.Checked) && (File.Exists(titledirectory + tmdcontents[i])))
{
WriteStatus("Leaving local " + tmdcontents[i] + ".");
}
else
{
DownloadNUSFile(titleid, tmdcontents[i], titledirectory, int.Parse(tmdsizes[i], System.Globalization.NumberStyles.HexNumber), wiimode);
}
}
catch (Exception ex)
{
WriteStatus("Download Failed: " + tmdcontents[i]);
WriteStatus(" - Reason: " + ex.Message.ToString().Replace("The remote server returned an error: ", ""));
SetEnableforDownload(true);
downloadstartbtn.Text = "Start NUS Download!";
dlprogress.Value = 0;
DeleteTitleDirectory();
return;
}
// Progress reporting advances...
downloadstartbtn.Text = "Content: (" + (i + 1) + @"/" + contentstrnum + ")";
currentcontentlocation += int.Parse(tmdsizes[i], System.Globalization.NumberStyles.HexNumber);
// Decrypt stuff...
if (decryptbox.Checked == true)
{
// Create content file holder
byte[] contbuf = FileLocationToByteArray(titledirectory + @"\" + tmdcontents[i]);
// Create ticket file holder
byte[] cetkbuf = FileLocationToByteArray(titledirectory + @"\cetk");
// IV (00+IDX+more000)
byte[] iv = new byte[16];
for (int x = 0; x < 8; x++)
{
iv[x] = 0x00;
}
for (int x = 0; x < 7; x++)
{
iv[x + 8] = 0x00;
}
iv[1] = tmdindices[i];
initCrypt(iv, titlekey);
// Create decrypted file
string zeros = "000000";
FileStream decfs = new FileStream(RemoveIllegalCharacters(titledirectory + @"\" + zeros + i.ToString("X2") + ".app"), FileMode.Create);
decfs.Write(Decrypt(contbuf), 0, int.Parse(tmdsizes[i], System.Globalization.NumberStyles.HexNumber));
decfs.Close();
WriteStatus(" - Decrypted: " + zeros + i.ToString("X2") + ".app");
// Hash Check...
byte[] hash = new byte[20];
for (int x = 0; x < 20; x++)
{
hash[x] = tmdhashes[(i * 20) + x];
}
byte[] deccont = Decrypt(contbuf);
Array.Resize(ref deccont, int.Parse(tmdsizes[i], System.Globalization.NumberStyles.HexNumber));
if ((Convert.ToBase64String(ComputeSHA(deccont))) == Convert.ToBase64String(hash))
{
WriteStatus(" - Hash Check: Pass");
}
else
{
WriteStatus(" - Hash Check: Fail");
WriteStatus(" - True Hash: " + DisplayBytes(hash, ""));
WriteStatus(" - You Have: " + DisplayBytes(ComputeSHA(Decrypt(contbuf)), ""));
}
}
dlprogress.Value = Convert.ToInt32(((currentcontentlocation / totalcontentsize) * 100));
}
WriteStatus("NUS Download Finished.");
// Trucha signing...
if ((truchabox.Checked == true) && (wiimode == true))
{
// Read information from TMD into signing GUI...
requiredIOSbox.Text = Convert.ToString(tmd[0x18B]);
tmdversiontrucha.Text = Convert.ToString((tmd[0x1DC]*256) + tmd[0x1DD]);
newtitleidbox.Text = titleid;
// Add contents to contentEdit...
FillContentInfo(tmd);
// Setup for NO IOS
if (requiredIOSbox.Text == "0")
requiredIOSbox.Enabled = false;
else
requiredIOSbox.Enabled = true;
// Read information from TIK into signing GUI...
// Create ticket file holder
byte[] cetkbuff = FileLocationToByteArray(titledirectory + @"\cetk");
// Titlekey
for (int i = 0; i < 16; i++)
{
titlekey[i] = cetkbuff[0x1BF + i];
}
//titlekeybox.Text = DisplayBytes(titlekey).Replace(" ", "");
titlekeybox.Text = System.Text.Encoding.UTF7.GetString(titlekey);
// IV (TITLEID+00000000s)
byte[] iv = new byte[16];
for (int i = 0; i < 8; i++)
{
iv[i] = cetkbuff[0x1DC + i];
}
for (int i = 0; i < 8; i++)
{
iv[i + 8] = 0x00;
}
titleIDIV.Text = DisplayBytes(iv, "");
//DLC
dlcamntbox.Text = Convert.ToString((cetkbuff[0x1E6]*256) + cetkbuff[0x1E7]);
//keyindex
if (cetkbuff[0x1F1] == 0x00)
ckeyindexcb.SelectedIndex = 0;
else if (cetkbuff[0x1F1] == 0x01)
ckeyindexcb.SelectedIndex = 1;
else
ckeyindexcb.SelectedIndex = 0;
//time enabled
if (cetkbuff[0x247] == 0x00)
timelimitenabledcb.SelectedIndex = 0;
else if (cetkbuff[0x247] == 0x01)
timelimitenabledcb.SelectedIndex = 1;
else
timelimitenabledcb.SelectedIndex = 0;
//time in seconds
byte[] timelimit = new byte[4];
for (int i = 0; i < timelimit.Length; i++)
{
timelimit[i] = cetkbuff[0x248 + 1];
}
timelimitsecs.Text = Convert.ToString(System.BitConverter.ToInt32(timelimit, 0));
// Resize form to max to show trucha options...
this.Size = this.MaximumSize;
shamelessvariablelabel.Text = String.Format("{0},{1},{2}", titledirectory, tmdfull, contentstrnum);
// Loop until user is finished...
while (this.Size == this.MaximumSize)
{
System.Threading.Thread.Sleep(1000);
}
/*
WriteStatus("Trucha Signing TMD...");
Array.Resize(ref tmd, 484 + (Convert.ToInt32(contentstrnum) * 36));
// DEBUG: Mii Channel Test...
tmd[0x18F] = 0x01;
tmd = ZeroSignature(tmd);
tmd = TruchaSign(tmd);
FileStream testtmd = new FileStream(titledirectory + tmdfull, FileMode.Open);
testtmd.Write(tmd, 0, tmd.Length);
testtmd.Close();
WriteStatus("Trucha Signing Ticket...");
// Create ticket file holder
FileStream cetkf = File.OpenRead(titledirectory + @"\cetk");
byte[] cetkbuff = ReadFully(cetkf, 20);
cetkf.Close();
Array.Resize(ref cetkbuff, 0x2A4);
// DEBUG: Mii Channel Test...
cetkbuff[0x1DF] = 0x01;
cetkbuff = ZeroSignature(cetkbuff);
cetkbuff = TruchaSign(cetkbuff);
FileStream testtik = new FileStream(titledirectory + "cetk", FileMode.Open);
testtik.Write(cetkbuff, 0, cetkbuff.Length);
testtik.Close(); */
}
if ((packbox.Checked == true) && (wiimode == true))
{
PackWAD(titleid, tmdfull, tmdcontents.Length, tmdcontents, tmdsizes, titledirectory);
}
SetEnableforDownload(true);
downloadstartbtn.Text = "Start NUS Download!";
dlprogress.Value = 0;
}
private void CreateTitleDirectory()
{
// Creates the directory for the downloaded title...
string currentdir = Application.StartupPath;
if (currentdir.EndsWith(Convert.ToString(Path.DirectorySeparatorChar)) == false)
currentdir += Path.DirectorySeparatorChar;
// Get placement directory early...
string titledirectory;
if (titleversion.Text == "")
titledirectory = Path.Combine(currentdir, titleidbox.Text + Path.DirectorySeparatorChar);
else
titledirectory = Path.Combine(currentdir, titleidbox.Text + "v" + titleversion.Text + Path.DirectorySeparatorChar);
// Keep local directory if present and checked out...
if ((localuse.Checked) && (Directory.Exists(titledirectory)))
{
//WriteStatus("Using Local Files");
}
else
{
if (Directory.Exists(titledirectory))
Directory.Delete(titledirectory, true);
Directory.CreateDirectory(titledirectory);
}
}
private void DeleteTitleDirectory()
{
string currentdir = Application.StartupPath;
if (currentdir.EndsWith(Convert.ToString(Path.DirectorySeparatorChar)) == false)
currentdir += Path.DirectorySeparatorChar;
// Get placement directory early...
string titledirectory;
if (titleversion.Text == "")
titledirectory = Path.Combine(currentdir, titleidbox.Text + Path.DirectorySeparatorChar);
else
titledirectory = Path.Combine(currentdir, titleidbox.Text + "v" + titleversion.Text + Path.DirectorySeparatorChar);
if (Directory.Exists(titledirectory))
Directory.Delete(titledirectory, true);
//Directory.CreateDirectory(currentdir + titleidbox.Text);
}
private void DownloadNUSFile(string titleid, string filename, string placementdir, int sizeinbytes, bool iswiititle)
{
// Create NUS URL...
string nusfileurl;
if (iswiititle)
nusfileurl = NUSURL + titleid + @"/" + filename;
else
nusfileurl = DSiNUSURL + titleid + @"/" + filename;
WriteStatus("Grabbing " + filename + "...");
// State size of file...
if (sizeinbytes != 0)
statusbox.Text += " (" + Convert.ToString(sizeinbytes) + " bytes)";
// Download NUS file...
generalWC.DownloadFile(nusfileurl, placementdir + filename);
}
public void PackWAD(string titleid, string tmdfilename, int contentcount, string[] contentnames, string[] contentsizes, string totaldirectory)
{
// Directory stuff
string currentdir = Application.StartupPath;
if (!(currentdir.EndsWith(@"\")) || !(currentdir.EndsWith(@"/")))
currentdir += @"\";
// Create cert file holder
byte[] certsbuf = FileLocationToByteArray(currentdir + @"\cert.sys");
// Create ticket file holder
byte[] cetkbuf = FileLocationToByteArray(totaldirectory + @"\cetk");
// Create tmd file holder
byte[] tmdbuf = FileLocationToByteArray(totaldirectory + @"\" + tmdfilename);
if (wadnamebox.Text.Contains("[v]") == true)
wadnamebox.Text = wadnamebox.Text.Replace("[v]", "v" + titleversion.Text);
// Create wad file
FileStream wadfs = new FileStream(RemoveIllegalCharacters(totaldirectory + @"\" + wadnamebox.Text), FileMode.Create);
// Add wad stuffs
WADHeader wad = new WADHeader();
wad.HeaderSize = 0x20;
wad.WadType = 0x49730000;
wad.CertChainSize = 0xA00;
// Write cert[] to 0x40.
wadfs.Seek(0x40, SeekOrigin.Begin);
wadfs.Write(certsbuf, 0, certsbuf.Length);
WriteStatus("Cert wrote (0x" + Convert.ToString(64, 16) + ")");
// Need 64 byte boundary...
wadfs.Seek(2624, SeekOrigin.Begin);
// Cert is 2560
// Write ticket at this point...
wad.TicketSize = 0x2A4;
wadfs.Write(cetkbuf, 0, wad.TicketSize);
WriteStatus("Ticket wrote (0x" + Convert.ToString((wadfs.Length - 0x2A4), 16) + ")");
// Need 64 byte boundary...
wadfs.Seek(ByteBoundary(Convert.ToInt32(wadfs.Length)), SeekOrigin.Begin);
// Write TMD at this point...
wad.TMDSize = 484 + (contentcount * 36);
wadfs.Write(tmdbuf, 0, 484 + (contentcount * 36));
WriteStatus("TMD wrote (0x" + Convert.ToString((wadfs.Length - (484 + (contentcount * 36))), 16) + ")");
// Preliminary data size of wad file.
wad.DataSize = 0;
// Loop n Add contents
for (int i = 0; i < contentcount; i++)
{
// Need 64 byte boundary...
wadfs.Seek(ByteBoundary(Convert.ToInt32(wadfs.Length)), SeekOrigin.Begin);
// Create content file holder
byte[] contbuf = FileLocationToByteArray(totaldirectory + @"\" + contentnames[i]);
wadfs.Write(contbuf, 0, contbuf.Length);
/*if (int.Parse(contentsizes[i], System.Globalization.NumberStyles.HexNumber) != contbuf.Length)
{
// Content size mismatch
WriteStatus(contentnames[i] + " wrote (0x" + Convert.ToString((wadfs.Length - contbuf.Length), 16) + ") (Mismatch)");
}
else
{
WriteStatus(contentnames[i] + " wrote (0x" + Convert.ToString((wadfs.Length - contbuf.Length), 16) + ")");
} */
WriteStatus(contentnames[i] + " wrote (0x" + Convert.ToString((wadfs.Length - contbuf.Length), 16) + ")");
HandleMismatch(int.Parse(contentsizes[i], System.Globalization.NumberStyles.HexNumber), contbuf.Length);
wad.DataSize += contbuf.Length;
}
// Seek the beginning of the WAD...
wadfs.Seek(0, SeekOrigin.Begin);
// Write initial part of header
byte[] start = new byte[8] { 0x00, 0x00, 0x00, 0x20, 0x49, 0x73, 0x00, 0x00 };
wadfs.Write(start, 0, start.Length);
// Write CertChainLength
wadfs.Seek(0x08, SeekOrigin.Begin);
byte[] chainsize = InttoByteArray(wad.CertChainSize, 4);
wadfs.Write(chainsize, 0, 4);
// Write res
byte[] reserved = new byte[4] { 0x00, 0x00, 0x00, 0x00 };
wadfs.Seek(0x0C, SeekOrigin.Begin);
wadfs.Write(reserved, 0, 4);
// Write ticketsize
byte[] ticketsize = new byte[4] { 0x00, 0x00, 0x02, 0xA4 };
wadfs.Seek(0x10, SeekOrigin.Begin);
wadfs.Write(ticketsize, 0, 4);
// Write tmdsize
int strippedtmd = 484 + (contentcount * 36);
byte[] tmdsize = InttoByteArray(strippedtmd, 4);
wadfs.Seek(0x14, SeekOrigin.Begin);
wadfs.Write(tmdsize, 0, 4);
// Write data size
wadfs.Seek(0x18, SeekOrigin.Begin);
byte[] datasize = InttoByteArray(wad.DataSize, 4);
wadfs.Write(datasize, 0, 4);
// Finished.
WriteStatus("WAD Created: " + wadnamebox.Text);
// Close filesystem...
wadfs.Close();
}
private long ByteBoundary(int currentlength)
{
// Gets the next 0x40 offset.
long thelength = currentlength - 1;
long remainder = 1;
while (remainder != 0)
{
thelength += 1;
remainder = thelength % 0x40;
}
//WriteStatus("Initial Size: " + currentlength);
//WriteStatus("0x40 Size: " + thelength);
return (long)thelength;
}
private byte[] InttoByteArray(int inte, int arraysize)
{
// Take integer and make into byte array
byte[] b = new byte[arraysize];
b = BitConverter.GetBytes(inte);
if (BitConverter.IsLittleEndian)
Array.Reverse(b);
return b;
}
private void radioButton2_CheckedChanged(object sender, EventArgs e)
{
if (radioButton2.Checked == true)
{
// Cannot Pack WADs
packbox.Checked = false;
packbox.Enabled = false;
// Can decrypt if key exists...lulz
if (dsidecrypt == false)
{
decryptbox.Checked = false;
decryptbox.Enabled = false;
}
wadnamebox.Enabled = false;
wadnamebox.Text = "";
}
}
private void radioButton1_CheckedChanged(object sender, EventArgs e)
{
if (radioButton1.Checked == true)
{
// Can pack WADs
// packbox.Checked = true;
packbox.Enabled = true;
decryptbox.Enabled = true;
}
}
private void button2_Click(object sender, EventArgs e)
{
// Display About Text...
statusbox.Text = "";
WriteStatus("NUS Downloader (NUSD)");
WriteStatus("You are running version: " + version);
WriteStatus("This program coded by WB3000");
WriteStatus("");
string currentdir = Application.StartupPath;
if (currentdir.EndsWith(Convert.ToString(Path.DirectorySeparatorChar)) == false)
currentdir += Path.DirectorySeparatorChar;
if (File.Exists(currentdir + "key.bin") == false)
{
WriteStatus("Wii Decryption: Need (key.bin)");
}
else
{
WriteStatus("Wii Decryption: OK");
}
if (File.Exists(currentdir + "kkey.bin") == false)
{
WriteStatus("Wii Korea Decryption: Need (kkey.bin)");
}
else
{
WriteStatus("Wii Korea Decryption: OK");
}
if (File.Exists(currentdir + "dsikey.bin") == false)
{
WriteStatus("DSi Decryption: Need (dsikey.bin)");
}
else
{
WriteStatus("DSi Decryption: OK");
}
if (File.Exists(currentdir + "database.xml") == false)
{
WriteStatus("Database: Need (database.xml)");
}
else
{
WriteStatus("Database: OK");
}
WriteStatus("");
WriteStatus("Special thanks to:");
WriteStatus(" * Crediar for his wadmaker tool + source, and for the advice!");
WriteStatus(" * SquidMan/Galaxy/comex/Xuzz for advice/sources.");
WriteStatus(" * Pasta for database compilation assistance.");
WriteStatus(" * #WiiDev for answering the tough questions.");
WriteStatus(" * Anyone who helped beta test on GBATemp!");
WriteStatus(" * Famfamfam for the Silk Icon Set.");
}
private void getcerts_Click(object sender, EventArgs e)
{
// Get a certs.sys from NUS...
// Directory stuff
string currentdir = Application.StartupPath;
if (currentdir.EndsWith(Convert.ToString(Path.DirectorySeparatorChar)) == false)
currentdir += Path.DirectorySeparatorChar;
// Create certs file
FileStream certsfs = new FileStream(RemoveIllegalCharacters(currentdir + @"\cert.sys"), FileMode.Create);
// Getting it from SystemMenu 3.2
DownloadNUSFile("0000000100000002", "tmd.289", currentdir + @"\", 0, true);
DownloadNUSFile("0000000100000002", "cetk", currentdir + @"\", 0, true);
// Create ticket file holder
byte[] cetkbuf = FileLocationToByteArray(currentdir + "cetk");
// Create tmd file holder
byte[] tmdbuf = FileLocationToByteArray(currentdir + "tmd.289");
// Write CA cert...
certsfs.Seek(0, SeekOrigin.Begin);
certsfs.Write(tmdbuf, 0x628, 0x400);
WriteStatus("Added CA Cert!");
// Write CACP cert...
certsfs.Seek(0x400, SeekOrigin.Begin);
certsfs.Write(tmdbuf, 0x328, 0x300);
WriteStatus("Added CACP Cert!");
// Write CAXS cert...
certsfs.Seek(0x700, SeekOrigin.Begin);
certsfs.Write(cetkbuf, 0x2A4, 0x300);
WriteStatus("Added CAXS Cert!");
certsfs.Close();
// Hash check the cert.sys...
if (verifyMd5Hash(currentdir + @"\cert.sys", certs_MD5) == true)
{
WriteStatus("Certs File Successfully Created!");
}
else
{
WriteStatus("Error in Creating Certs File!");
WriteStatus("Please report this error if you are sure it is not your internet connection");
}
// Re-enable controls...
foreach (Control ctrl in this.Controls)
{
ctrl.Enabled = true;
}
getcerts.Visible = false;
wadnamebox.Enabled = false;
// Cleanup...
File.Delete(currentdir + "cetk");
File.Delete(currentdir + "tmd.289");
}
static string getMd5Hash(string input)
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
FileStream fs = new FileStream(input, FileMode.Open);
MD5 md5 = new MD5CryptoServiceProvider();
byte[] hash = md5.ComputeHash(fs);
fs.Close();
fs.Dispose();
foreach (byte hex in hash)
{
//Returns hash in lower case.
sb.Append(hex.ToString("x2"));
}
return sb.ToString();
}
// Verify a hash against a string.
static bool verifyMd5Hash(string input, string hash)
{
// Hash the input.
string hashOfInput = getMd5Hash(input);
// Create a StringComparer an comare the hashes.
StringComparer comparer = StringComparer.OrdinalIgnoreCase;
if (0 == comparer.Compare(hashOfInput, hash))
{
return true;
}
else
{
return false;
}
}
private void packbox_CheckedChanged(object sender, EventArgs e)
{
if (packbox.Checked == true)
{
wadnamebox.Enabled = true;
// Change WAD name if applicable
UpdatePackedName();
}
else
{
wadnamebox.Enabled = false;
wadnamebox.Text = "";
}
}
private void titleidbox_TextChanged(object sender, EventArgs e)
{
UpdatePackedName();
}
private void titleversion_TextChanged(object sender, EventArgs e)
{
UpdatePackedName();
}
public void initCrypt(byte[] iv, byte[] key)
{
rijndaelCipher = new RijndaelManaged();
rijndaelCipher.Mode = CipherMode.CBC;
rijndaelCipher.Padding = PaddingMode.None;
rijndaelCipher.KeySize = 128;
rijndaelCipher.BlockSize = 128;
rijndaelCipher.Key = key;
rijndaelCipher.IV = iv;
}
public byte[] Encrypt(byte[] plainBytes)
{
ICryptoTransform transform = rijndaelCipher.CreateEncryptor();
using (MemoryStream ms = new MemoryStream(plainBytes))
{
using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Read))
{
return ReadFully(cs);
}
}
}
public byte[] Decrypt(byte[] encryptedData)
{
ICryptoTransform transform = rijndaelCipher.CreateDecryptor();
using (MemoryStream ms = new MemoryStream(encryptedData))
{
using (CryptoStream cs = new CryptoStream(ms, transform, CryptoStreamMode.Read))
{
return ReadFully(cs);
}
}
}
public byte[] ReadFully(Stream stream)
{
byte[] buffer = new byte[32768];
using (MemoryStream ms = new MemoryStream())
{
while (true)
{
int read = stream.Read(buffer, 0, buffer.Length);
if (read <= 0)
return ms.ToArray();
ms.Write(buffer, 0, read);
}
}
}
public string DisplayBytes(byte[] bytes, string spacer)
{
string output = "";
for (int i = 0; i < bytes.Length; ++i)
{
output += bytes[i].ToString("X2") + spacer;
}
return output;
}
static public byte[] ComputeSHA(byte[] data)
{
SHA1 sha = new SHA1CryptoServiceProvider();
// This is one implementation of the abstract class SHA1.
return sha.ComputeHash(data);
}
public byte[] LoadCommonKey(string keyfile)
{
// Directory stuff
string currentdir = Application.StartupPath;
if (!(currentdir.EndsWith(@"\")) || !(currentdir.EndsWith(@"/")))
currentdir += @"\";
if (File.Exists(currentdir + keyfile) == true)
{
// Read common key byte[]
return FileLocationToByteArray(currentdir + keyfile);
}
else
return null;
}
private void button4_Click(object sender, EventArgs e)
{
// Open Database button menu...
databaseStrip.Show(databaseButton, 2, 2);
}
private void ClearDatabaseStrip()
{
SystemMenuList.DropDownItems.Clear();
IOSMenuList.DropDownItems.Clear();
WiiWareMenuList.DropDownItems.Clear();
// VC Games Sections...
C64MenuList.DropDownItems.Clear();
NeoGeoMenuList.DropDownItems.Clear();
NESMenuList.DropDownItems.Clear();
SNESMenuList.DropDownItems.Clear();
N64MenuList.DropDownItems.Clear();
TurboGrafx16MenuList.DropDownItems.Clear();
TurboGrafxCDMenuList.DropDownItems.Clear();
MSXMenuList.DropDownItems.Clear();
SegaMSMenuList.DropDownItems.Clear();
GenesisMenuList.DropDownItems.Clear();
VCArcadeMenuList.DropDownItems.Clear();
}
private void FillDatabaseStrip()
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load("database.xml");
// Variables
string[] XMLNodeTypes = new string[4] { "SYS", "IOS", "VC", "WW" };
// Loop through XMLNodeTypes
for (int i = 0; i < XMLNodeTypes.Length; i++)
{
XmlNodeList XMLSpecificNodeTypeList = xDoc.GetElementsByTagName(XMLNodeTypes[i]);
for (int x = 0; x < XMLSpecificNodeTypeList.Count; x++)
{
ToolStripMenuItem XMLToolStripItem = new ToolStripMenuItem();
XmlAttributeCollection XMLAttributes = XMLSpecificNodeTypeList[x].Attributes;
string titleID = "";
string descname = "";
bool dangerous = false;
bool ticket = true;
// Lol.
XmlNodeList ChildrenOfTheNode = XMLSpecificNodeTypeList[x].ChildNodes;
for (int z = 0; z < ChildrenOfTheNode.Count; z++)
{
switch (ChildrenOfTheNode[z].Name)
{
case "name":
descname = ChildrenOfTheNode[z].InnerText;
break;
case "titleID":
titleID = ChildrenOfTheNode[z].InnerText;
break;
case "version":
string[] versions = ChildrenOfTheNode[z].InnerText.Split(',');
XMLToolStripItem.DropDownItems.Add("Latest Version");
if (ChildrenOfTheNode[z].InnerText != "")
{
for (int y = 0; y < versions.Length; y++)
{
XMLToolStripItem.DropDownItems.Add("v" + versions[y]);
}
}
break;
case "region":
string[] regions = ChildrenOfTheNode[z].InnerText.Split(',');
if (ChildrenOfTheNode[z].InnerText != "")
{
for (int y = 0; y < regions.Length; y++)
{
XMLToolStripItem.DropDownItems.Add(RegionFromIndex(Convert.ToInt32(regions[y]), xDoc));
}
}
break;
default:
break;
case "ticket":
ticket = Convert.ToBoolean(ChildrenOfTheNode[z].InnerText);
break;
case "danger":
dangerous = true;
XMLToolStripItem.ToolTipText = ChildrenOfTheNode[z].InnerText;
break;
}
XMLToolStripItem.Image = SelectItemImage(ticket, dangerous);
XMLToolStripItem.Text = String.Format("{0} - {1}", titleID, descname);
}
AddToolStripItemToStrip(i, XMLToolStripItem, XMLAttributes);
}
}
}
void AddToolStripItemToStrip(int type, ToolStripMenuItem additionitem, XmlAttributeCollection attributes)
{
// Deal with VC list depth...
if (type == 2)
{
switch (attributes[0].Value)
{
case "C64":
C64MenuList.DropDownItems.Add(additionitem);
break;
case "NEO":
NeoGeoMenuList.DropDownItems.Add(additionitem);
break;
case "NES":
NESMenuList.DropDownItems.Add(additionitem);
break;
case "SNES":
SNESMenuList.DropDownItems.Add(additionitem);
break;
case "N64":
N64MenuList.DropDownItems.Add(additionitem);
break;
case "TG16":
TurboGrafx16MenuList.DropDownItems.Add(additionitem);
break;
case "TGCD":
TurboGrafxCDMenuList.DropDownItems.Add(additionitem);
break;
case "MSX":
MSXMenuList.DropDownItems.Add(additionitem);
break;
case "SMS":
SegaMSMenuList.DropDownItems.Add(additionitem);
break;
case "GEN":
GenesisMenuList.DropDownItems.Add(additionitem);
break;
case "ARC":
VCArcadeMenuList.DropDownItems.Add(additionitem);
break;
default:
break;
}
additionitem.DropDownItemClicked += new ToolStripItemClickedEventHandler(wwitem_regionclicked);
}
else
{
// Add SYS, IOS, WW items
// I thought using index would work in .Items, but I
// guess this switch will have to do...
switch (type)
{
case 0:
SystemMenuList.DropDownItems.Add(additionitem);
break;
case 1:
IOSMenuList.DropDownItems.Add(additionitem);
break;
case 3:
WiiWareMenuList.DropDownItems.Add(additionitem);
break;
}
additionitem.DropDownItemClicked += new ToolStripItemClickedEventHandler(sysitem_versionclicked);
}
}
void wwitem_regionclicked(object sender, ToolStripItemClickedEventArgs e)
{
titleidbox.Text = e.ClickedItem.OwnerItem.Text.Substring(0, 16);
titleversion.Text = "";
titleidbox.Text = titleidbox.Text.Replace("XX", e.ClickedItem.Text.Substring(0, 2));
// Prepare StatusBox...
string titlename = e.ClickedItem.OwnerItem.Text.Substring(19, (e.ClickedItem.OwnerItem.Text.Length - 19));
statusbox.Text = " --- " + titlename + " ---";
// Check if a ticket is present...
if ((e.ClickedItem.OwnerItem.Image) == (orange) || (e.ClickedItem.OwnerItem.Image) == (redorange))
{
ignoreticket.Checked = true;
WriteStatus("Note: This title has no ticket and cannot be packed/decrypted!");
packbox.Checked = false;
decryptbox.Checked = false;
}
else
{
ignoreticket.Checked = false;
}
// Change WAD name if packed is already checked...
if (packbox.Checked)
{
if (titlename.Contains("IOS"))
wadnamebox.Text = titlename + "-64-[v].wad";
else
wadnamebox.Text = titlename + "-NUS-[v].wad";
if (titleversion.Text != "")
wadnamebox.Text = wadnamebox.Text.Replace("[v]", "v" + titleversion.Text);
}
// Check for danger item
if ((e.ClickedItem.OwnerItem.Image) == (redgreen) || (e.ClickedItem.OwnerItem.Image) == (redorange))
{
WriteStatus("\r\n" + e.ClickedItem.OwnerItem.ToolTipText);
}
}
void sysitem_versionclicked(object sender, ToolStripItemClickedEventArgs e)
{
titleidbox.Text = e.ClickedItem.OwnerItem.Text.Substring(0, 16);
if (e.ClickedItem.Text != "Latest Version")
{
if (e.ClickedItem.Text.Contains("v"))
{
if (e.ClickedItem.Text.Contains(" "))
titleversion.Text = e.ClickedItem.Text.Substring(1, e.ClickedItem.Text.IndexOf(' '));
else
titleversion.Text = e.ClickedItem.Text.Substring(1, e.ClickedItem.Text.Length - 1);
}
else
{
titleidbox.Text = titleidbox.Text.Replace("XX", e.ClickedItem.Text.Substring(0, 2));
titleversion.Text = "";
}
}
else
{
titleversion.Text = "";
}
// Prepare StatusBox...
string titlename = e.ClickedItem.OwnerItem.Text.Substring(19, (e.ClickedItem.OwnerItem.Text.Length - 19));
statusbox.Text = " --- " + titlename + " ---";
if ((e.ClickedItem.OwnerItem.Image) == (orange) || (e.ClickedItem.OwnerItem.Image) == (redorange))
{
ignoreticket.Checked = true;
WriteStatus("Note: This title has no ticket and cannot be packed/decrypted!");
packbox.Checked = false;
decryptbox.Checked = false;
}
else
{
ignoreticket.Checked = false;
}
// Change WAD name if packed is already checked...
if (packbox.Checked)
{
if (titlename.Contains("IOS"))
wadnamebox.Text = titlename + "-64-[v].wad";
else
wadnamebox.Text = titlename + "-NUS-[v].wad";
if (titleversion.Text != "")
wadnamebox.Text = wadnamebox.Text.Replace("[v]", "v" + titleversion.Text);
}
// Check for danger item
if ((e.ClickedItem.OwnerItem.Image) == (redgreen) || (e.ClickedItem.OwnerItem.Image) == (redorange))
{
WriteStatus("\n" + e.ClickedItem.OwnerItem.ToolTipText);
}
}
void HandleMismatch(int contentsize, int actualsize)
{
if (contentsize != actualsize)
{
if ((contentsize - actualsize) > 16)
{
statusbox.Text += " (BAD Mismatch)";
}
else
{
statusbox.Text += " (Safe Mismatch)";
}
}
}
string RegionFromIndex(int index, XmlDocument databasexml)
{
/* Typical Region XML
* <REGIONS>
<region index="0">41 (All/System)</region>
<region index=1>44 (German)</region>
<region index=2>45 (USA/NTSC)</region>
<region index=3>46 (French)</region>
<region index=4>4A (Japan)</region>
<region index=5>4B (Korea)</region>
<region index=6>4C (Japanese Import to Europe/Australia/PAL)</region>
<region index=7>4D (American Import to Europe/Australia/PAL)</region>
<region index=8>4E (Japanese Import to USA/NTSC)</region>
<region index=9>50 (Europe/PAL)</region>
<region index=10>51 (Korea w/ Japanese Language)</region>
<region index=11>54 (Korea w/ English Language)</region>
<region index=12>58 (Some Homebrew)</region>
</REGIONS>
*/
XmlNodeList XMLRegionList = databasexml.GetElementsByTagName("REGIONS");
XmlNodeList ChildrenOfTheNode = XMLRegionList[0].ChildNodes;
// For each child node (region node)
for (int z = 0; z < ChildrenOfTheNode.Count; z++)
{
// Gather attributes (index='x')
XmlAttributeCollection XMLAttributes = ChildrenOfTheNode[z].Attributes;
// Return value of node if index matches
if (Convert.ToInt32(XMLAttributes[0].Value) == index)
return ChildrenOfTheNode[z].InnerText;
}
return "XX (Error)";
}
private void LoadRegionCodes()
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load("database.xml");
XmlNodeList XMLRegionList = xDoc.GetElementsByTagName("REGIONS");
XmlNodeList ChildrenOfTheNode = XMLRegionList[0].ChildNodes;
// For each child node (region node)
for (int z = 0; z < ChildrenOfTheNode.Count; z++)
{
RegionCodesList.DropDownItems.Add(ChildrenOfTheNode[z].InnerText);
}
}
private void RegionCodesList_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (titleidbox.Text.Length == 16)
titleidbox.Text = titleidbox.Text.Substring(0, 14) + e.ClickedItem.Text.Substring(0, 2);
}
private string RemoveIllegalCharacters(string databasestr)
{
// Database strings must contain filename-legal characters.
foreach (char illegalchar in System.IO.Path.GetInvalidFileNameChars())
{
if (databasestr.Contains(illegalchar.ToString()))
databasestr = databasestr.Replace(illegalchar, '-');
}
return databasestr;
}
private byte[] ZeroSignature(byte[] tmdortik)
{
// Write all 0x00 to signature...
// Sig starts at 0x04 in both TMD/TIK
for (int i = 0; i < 256; i++)
{
tmdortik[i + 4] = 0x00;
}
WriteStatus(" - Signature Emptied...");
return tmdortik;
}
private byte[] TruchaSign(byte[] tmdortik)
{
// Loop through 2 bytes worth of numbers until hash starts with 0x00...
// Padding starts at 0x104 in both TMD/TIK, seems like a good place to me...
byte[] payload = new byte[2];
byte[] hashobject = new byte[tmdortik.Length - 0x104];
for (int i = 0; i < 65535; i++)
{
payload = incrementAtIndex(payload, 1);
tmdortik[0x104] = payload[0];
tmdortik[0x105] = payload[1];
for (int x = 0; x < (tmdortik.Length - 0x104); x++)
{
hashobject[x] = tmdortik[0x104 + x];
}
if (ComputeSHA(hashobject)[0] == 0x00)
{
// DEBUG:
//WriteStatus(DisplayBytes(ComputeSHA(hashobject)));
WriteStatus(" - Successfully Trucha Signed.");
return tmdortik;
}
}
WriteStatus(" - Sign FAIL!");
return tmdortik;
}
static public byte[] incrementAtIndex(byte[] array, int index)
{
if (array[index] == byte.MaxValue)
{
array[index] = 0;
if (index > 0)
incrementAtIndex(array, index - 1);
}
else
{
array[index]++;
}
return array;
}
private void button6_Click(object sender, EventArgs e)
{
// Revert to TMD information...
string[] fileinfo = shamelessvariablelabel.Text.Split(',');
// Read the tmd as a stream...
byte[] tmd = FileLocationToByteArray(fileinfo[0] + fileinfo[1]);
// Read information from TMD into signing GUI...
requiredIOSbox.Text = Convert.ToString(tmd[0x18B]);
// Lulzy cheap way of getting version... *256
tmdversiontrucha.Text = Convert.ToString(((tmd[0x1DC]*256) + tmd[0x1DD]));
newtitleidbox.Text = titleidbox.Text;
// Setup for NO IOS
if (requiredIOSbox.Text == "0")
requiredIOSbox.Enabled = false;
else
requiredIOSbox.Enabled = true;
}
private void button5_Click(object sender, EventArgs e)
{
// Revert to Ticket information...
string[] fileinfo = shamelessvariablelabel.Text.Split(',');
// Create ticket file holder
byte[] cetkbuff = FileLocationToByteArray(fileinfo[0] + @"\cetk");
// Titlekey
byte[] titlekey = new byte[16];
for (int i = 0; i < 16; i++)
{
titlekey[i] = cetkbuff[0x1BF + i];
}
titlekeybox.Text = System.Text.Encoding.UTF7.GetString(titlekey);
// IV (TITLEID+00000000s)
byte[] iv = new byte[16];
for (int i = 0; i < 8; i++)
{
iv[i] = cetkbuff[0x1DC + i];
}
for (int i = 0; i < 8; i++)
{
iv[i + 8] = 0x00;
}
titleIDIV.Text = DisplayBytes(iv, "");
//DLC
dlcamntbox.Text = Convert.ToString((cetkbuff[0x1E6]*256) + cetkbuff[0x1E7]);
//keyindex
if (cetkbuff[0x1F1] == 0x00)
ckeyindexcb.SelectedIndex = 0;
else if (cetkbuff[0x1F1] == 0x01)
ckeyindexcb.SelectedIndex = 1;
else
ckeyindexcb.SelectedIndex = 0;
//time enabled
if (cetkbuff[0x247] == 0x00)
timelimitenabledcb.SelectedIndex = 0;
else if (cetkbuff[0x247] == 0x01)
timelimitenabledcb.SelectedIndex = 1;
else
timelimitenabledcb.SelectedIndex = 0;
//time in seconds
byte[] timelimit = new byte[4];
for (int i = 0; i < timelimit.Length; i++)
{
timelimit[i] = cetkbuff[0x248 + i];
}
timelimitsecs.Text = Convert.ToString(System.BitConverter.ToInt32(timelimit, 0));
}
private void button1_Click_1(object sender, EventArgs e)
{
// Write Trucha changes to TMD...
WriteStatus("Trucha Signing TMD...");
// Cheezy file info
string[] fileinfo = shamelessvariablelabel.Text.Split(',');
// Read the tmd as a stream...
byte[] tmd = FileLocationToByteArray(fileinfo[0] + fileinfo[1]);
// Resize to just TMD...
Array.Resize(ref tmd, 484 + (Convert.ToInt32(fileinfo[2]) * 36));
// Change Required IOS
if (requiredIOSbox.Text != "0")
{
tmd[0x18B] = Convert.ToByte(requiredIOSbox.Text);
}
// Change Title Version
byte[] version = new byte[2];
version = InttoByteArray(Convert.ToInt32(tmdversiontrucha.Text), version.Length);
tmd[0x1DC] = version[version.Length - 2];
tmd[0x1DD] = version[version.Length - 1];
// Change Title ID
tmd[0x18C] = byte.Parse(newtitleidbox.Text.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
tmd[0x18D] = byte.Parse(newtitleidbox.Text.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
tmd[0x18E] = byte.Parse(newtitleidbox.Text.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
tmd[0x18F] = byte.Parse(newtitleidbox.Text.Substring(6, 2), System.Globalization.NumberStyles.HexNumber);
tmd[0x190] = byte.Parse(newtitleidbox.Text.Substring(8, 2), System.Globalization.NumberStyles.HexNumber);
tmd[0x191] = byte.Parse(newtitleidbox.Text.Substring(10, 2), System.Globalization.NumberStyles.HexNumber);
tmd[0x192] = byte.Parse(newtitleidbox.Text.Substring(12, 2), System.Globalization.NumberStyles.HexNumber);
tmd[0x193] = byte.Parse(newtitleidbox.Text.Substring(14, 2), System.Globalization.NumberStyles.HexNumber);
tmd = ZeroSignature(tmd);
tmd = TruchaSign(tmd);
FileStream testtmd = new FileStream(RemoveIllegalCharacters(fileinfo[0] + fileinfo[1]), FileMode.Open);
testtmd.Write(tmd, 0, tmd.Length);
testtmd.Close();
}
private void button4_Click_1(object sender, EventArgs e)
{
// Write Trucha changes to Ticket...
WriteStatus("Trucha Signing Ticket...");
// Cheezy file info
string[] fileinfo = shamelessvariablelabel.Text.Split(',');
// Create ticket file holder
byte[] cetkbuff = FileLocationToByteArray(fileinfo[0] + @"\cetk");
// Resize Ticket to actual size.
Array.Resize(ref cetkbuff, 0x2A4);
// TODO: Title Key and IV changes!
// Write DLC Amount.
byte[] dlcamount = new byte[2];
dlcamount = InttoByteArray(Convert.ToInt32(dlcamntbox.Text), dlcamount.Length);
cetkbuff[0x1E6] = dlcamount[dlcamount.Length - 2];
cetkbuff[0x1E7] = dlcamount[dlcamount.Length - 1];
// Common Key index.
if (ckeyindexcb.SelectedIndex == 0)
cetkbuff[0x1F1] = 0x00;
else if (ckeyindexcb.SelectedIndex == 1)
cetkbuff[0x1F1] = 0x01;
else
cetkbuff[0x1F1] = 0x00;
// Time limit enable.
if (timelimitenabledcb.SelectedIndex == 0)
cetkbuff[0x247] = 0x00;
else if (timelimitenabledcb.SelectedIndex == 1)
cetkbuff[0x247] = 0x01;
else
cetkbuff[0x247] = 0x00;
// The amount of time for the limit.
byte[] limitseconds = new byte[4];
limitseconds = InttoByteArray(Convert.ToInt32(timelimitsecs.Text), 4);
//DEBUG
//WriteStatus(DisplayBytes(limitseconds, " "));
for (int i = 0; i < 4; i++)
{
cetkbuff[0x248 + i] = limitseconds[i];
}
// Trucha (Fake) Sign
cetkbuff = ZeroSignature(cetkbuff);
cetkbuff = TruchaSign(cetkbuff);
// Write changes to cetk.
FileStream testtik = new FileStream(RemoveIllegalCharacters(fileinfo[0] + "cetk"), FileMode.Open);
testtik.Write(cetkbuff, 0, cetkbuff.Length);
testtik.Close();
}
// C# to convert a string to a byte array.
public static byte[] StrToByteArray(string str)
{
System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
return encoding.GetBytes(str);
}
private void button7_Click(object sender, EventArgs e)
{
// Proceed with process (BG worker waits for form to resize)
WriteStatus("Trucha modifications complete.");
this.Size = this.MinimumSize;
}
private void button3_Click_1(object sender, EventArgs e)
{
// Clear Statusbox.text
statusbox.Text = "";
}
private void SetEnableforDownload(bool enabled)
{
// Disable things the user should not mess with during download...
downloadstartbtn.Enabled = enabled;
titleidbox.Enabled = enabled;
titleversion.Enabled = enabled;
TMDButton.Enabled = enabled;
databaseButton.Enabled = enabled;
packbox.Enabled = enabled;
localuse.Enabled = enabled;
ignoreticket.Enabled = enabled;
truchabox.Enabled = enabled;
decryptbox.Enabled = enabled;
}
private void ShowInnerToolTips(bool enabled)
{
// Force tooltips to GTFO in sub menus...
foreach (ToolStripItem item in databaseStrip.Items)
{
try
{
ToolStripMenuItem menuitem = (ToolStripMenuItem)item;
menuitem.DropDown.ShowItemToolTips = false;
}
catch (Exception)
{
// Do nothing, some objects will not cast.
}
}
}
private System.Drawing.Image SelectItemImage(bool ticket, bool danger)
{
// All is good, go green...
if ((ticket) && (!danger))
return green;
// There's no ticket, but danger is clear...
if ((!ticket) && (!danger))
return orange;
// DANGER WILL ROBINSON...
if ((ticket) && (danger))
return redgreen;
// Double bad...
if ((!ticket) && (danger))
return redorange;
return null;
}
private byte[] FileLocationToByteArray(string filename)
{
FileStream fs = File.OpenRead(filename);
byte[] filebytearray = ReadFully(fs, 460);
fs.Close();
return filebytearray;
}
private void UpdatePackedName()
{
// Change WAD name if applicable
if ((titleidbox.Enabled == true) && (packbox.Checked == true))
{
if (titleversion.Text != "")
{
wadnamebox.Text = titleidbox.Text + "-NUS-v" + titleversion.Text + ".wad";
}
else
{
wadnamebox.Text = titleidbox.Text + "-NUS-[v]" + titleversion.Text + ".wad";
}
}
wadnamebox.Text = RemoveIllegalCharacters(wadnamebox.Text);
}
// This is WIP code/theory...
private byte[] GenerateTicket(byte[] EncTitleKey, byte[] TitleID)
{
byte[] Ticket = new byte[0x2A4];
// RSA Signature Heading...
Ticket[1] = 0x01; Ticket[3] = 0x01;
// Signature Issuer... (Root-CA00000001-XS00000003)
byte[] SignatureIssuer = new byte[0x1A] { 0x52, 0x6F, 0x6F, 0x74, 0x2D, 0x43, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x2D, 0x58, 0x53, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33 };
for (int a = 0; a < 0x40; a++)
{
Ticket[0x140 + a] = SignatureIssuer[a];
}
// Encrypted TitleKey...
for (int b = 0; b < 0x10; b++)
{
Ticket[0x1BF + b] = EncTitleKey[b];
}
// Ticket ID...
for (int c = 0; c < 0x08; c++)
{
Ticket[0x1D0 + c] = 0x49;
}
// Title ID...
for (int d = 0; d < 0x08; d++)
{
Ticket[0x1DC + d] = TitleID[d];
}
// Misc FF...
Ticket[0x1E4] = 0xFF; Ticket[0x1E5] = 0xFF;
Ticket[0x1E6] = 0xFF; Ticket[0x1E7] = 0xFF;
// Unknown 0x01...
Ticket[0x221] = 0x01;
// Misc FF...
for (int e = 0; e < 0x20; e++)
{
Ticket[0x222 + e] = 0xFF;
}
return Ticket;
}
private void button15_Click(object sender, EventArgs e)
{
// Read Content info from TMD again (revert)
string[] fileinfo = shamelessvariablelabel.Text.Split(',');
// Read the tmd as a stream...
byte[] tmd = FileLocationToByteArray(fileinfo[0] + fileinfo[1]);
FillContentInfo(tmd);
}
private void FillContentInfo(byte[] tmd)
{
// Clear anything existing...
contentsEdit.Items.Clear();
// # of Contents and BootIndex
int nbr_cont = ContentCount(tmd);
int boot_idx = BootIndex(tmd);
string[] tmdcontents = GetContentNames(tmd, nbr_cont);
byte[] tmdindices = GetContentIndices(tmd, nbr_cont);
// Loop and add contents to listbox...
for (int a = 0; a < nbr_cont; a++)
{
contentsEdit.Items.Add(String.Format("[{0}] [{1}]", tmdindices[a], tmdcontents[a]));
}
// Identify Boot Content...
contentsEdit.Items[boot_idx] += " [BOOT]";
}
private void button8_Click(object sender, EventArgs e)
{
// Move selected content upwards (down an index)...
if (contentsEdit.SelectedIndex <= 0)
return;
int sel_idx = contentsEdit.SelectedIndex;
string sel_item = contentsEdit.Items[sel_idx].ToString();
string lower_item = contentsEdit.Items[sel_idx - 1].ToString();
contentsEdit.Items[sel_idx] = String.Format("[{0}]{1}", sel_idx, lower_item.Substring(3, lower_item.Length - 3));
contentsEdit.Items[sel_idx - 1] = String.Format("[{0}]{1}", sel_idx - 1, sel_item.Substring(3, sel_item.Length - 3));
contentsEdit.SelectedIndex = sel_idx - 1;
}
private void button9_Click(object sender, EventArgs e)
{
// Move selected content down (up an index)...
if (contentsEdit.SelectedIndex >= contentsEdit.Items.Count - 1)
return;
int sel_idx = contentsEdit.SelectedIndex;
string sel_item = contentsEdit.Items[sel_idx].ToString();
string upper_item = contentsEdit.Items[sel_idx + 1].ToString();
contentsEdit.Items[sel_idx] = String.Format("[{0}]{1}", sel_idx, upper_item.Substring(3, upper_item.Length - 3));
contentsEdit.Items[sel_idx + 1] = String.Format("[{0}]{1}", sel_idx + 1, sel_item.Substring(3, sel_item.Length - 3));
contentsEdit.SelectedIndex = sel_idx + 1;
}
private void button12_Click(object sender, EventArgs e)
{
// Set a new boot index...
if (contentsEdit.SelectedIndex <= 0)
return;
for (int a = 0; a < contentsEdit.Items.Count; a++)
{
if (contentsEdit.Items[a].ToString().Contains(" [BOOT]"))
contentsEdit.Items[a] = contentsEdit.Items[a].ToString().Substring(0, contentsEdit.Items[a].ToString().Length - 7);
}
contentsEdit.Items[contentsEdit.SelectedIndex] += " [BOOT]";
}
private void button11_Click(object sender, EventArgs e)
{
// Add a file to the contents...
OpenFileDialog opencont = new OpenFileDialog();
opencont.Filter = "All Files|*";
opencont.Multiselect = false;
opencont.Title = "Locate a Content";
if (opencont.ShowDialog() != DialogResult.Cancel)
{
if ((opencont.SafeFileName.Length != 8) && (OnlyHexInString(opencont.SafeFileName) == false))
{
MessageBox.Show("Please locate/rename a file to be (8 HEX CHARACTERS) long!", "Bad!", MessageBoxButtons.OK);
return;
}
for (int i = 0; i < contentsEdit.Items.Count; i++)
{
if (contentsEdit.Items[i].ToString().Contains(opencont.SafeFileName))
{
MessageBox.Show("A file already exists in the title with that filename!", "Bad!", MessageBoxButtons.OK);
return;
}
}
// D: TODO?
string[] fileinfo = shamelessvariablelabel.Text.Split(',');
if (fileinfo[0] + opencont.SafeFileName != opencont.FileName)
{
// Move the file into the directory...
File.Copy(opencont.FileName, fileinfo[0] + opencont.SafeFileName);
}
contentsEdit.Items.Add(String.Format("[{0}] [{1}]", contentsEdit.Items.Count, opencont.SafeFileName));
}
}
public bool OnlyHexInString(string test)
{
return System.Text.RegularExpressions.Regex.IsMatch(test, @"\A\b[0-9a-fA-F]+\b\Z");
}
private void button10_Click(object sender, EventArgs e)
{
// Remove a content from the list...
if ((contentsEdit.SelectedIndex <= 0) || (contentsEdit.Items.Count <= 1))
return;
string[] fileinfo = shamelessvariablelabel.Text.Split(',');
DialogResult question = MessageBox.Show("Delete the actual file as well?", "Delete content?", MessageBoxButtons.YesNoCancel);
if (question == DialogResult.Yes)
File.Delete(fileinfo[0] + contentsEdit.SelectedItem.ToString().Substring(contentsEdit.SelectedItem.ToString().IndexOf("] [") + 3, 8));
if (question != DialogResult.Cancel)
contentsEdit.Items.RemoveAt(contentsEdit.SelectedIndex);
}
}
}