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(); } /// /// 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. /// /// The stream to read data from /// The initial buffer length 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 * 41 (All/System) 44 (German) 45 (USA/NTSC) 46 (French) 4A (Japan) 4B (Korea) 4C (Japanese Import to Europe/Australia/PAL) 4D (American Import to Europe/Australia/PAL) 4E (Japanese Import to USA/NTSC) 50 (Europe/PAL) 51 (Korea w/ Japanese Language) 54 (Korea w/ English Language) 58 (Some Homebrew) */ 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); } } }