diff --git a/NUS Downloader/Form1.cs b/NUS Downloader/Form1.cs index f44e763..6c73775 100644 --- a/NUS Downloader/Form1.cs +++ b/NUS Downloader/Form1.cs @@ -48,7 +48,7 @@ namespace NUS_Downloader // TODO: Always remember to change version! private string version = "v2.0 Beta"; - private WebClient generalWC = new WebClient(); + private static RijndaelManaged rijndaelCipher; private static bool dsidecrypt = false; @@ -409,7 +409,7 @@ namespace NUS_Downloader if (opentmd.ShowDialog() != DialogResult.Cancel) { // Read the tmd as a stream... - byte[] tmd = FileLocationToByteArray(opentmd.FileName); + byte[] tmd = File.ReadAllBytes(opentmd.FileName); WriteStatus("TMD Loaded (" + tmd.Length + " bytes)"); // Read ID... @@ -813,7 +813,8 @@ namespace NUS_Downloader } private void button3_Click(object sender, EventArgs e) - { if (titleidbox.Text == String.Empty) + { + if (titleidbox.Text == String.Empty) { // Prevent mass deletion and fail WriteStatus("Please enter a Title ID!"); @@ -864,7 +865,6 @@ namespace NUS_Downloader if (!(script_mode)) WriteStatus("Starting NUS Download. Please be patient!"); SetEnableforDownload(false); - downloadstartbtn.Text = "Starting NUS Download!"; // Creates the directory @@ -873,41 +873,51 @@ namespace NUS_Downloader // Wii / DSi bool wiimode = (consoleCBox.SelectedIndex == 0); + string activeNusUrl; + if (wiimode) + activeNusUrl = WII_NUS_URL; + else + activeNusUrl = DSI_NUS_URL; string titleid = titleidbox.Text; + string title_v = titleversion.Text; - // Set UserAgent to Wii value - generalWC.Headers.Add("User-Agent", "wii libnup/1.0"); - - // Proxy - generalWC = ConfigureWithProxy(generalWC); + // WebClient configuration + WebClient nusWC = new WebClient(); + nusWC = ConfigureWithProxy(nusWC); + nusWC.Headers.Add("User-Agent", "wii libnup/1.0"); // Set UserAgent to Wii value + // Create\Configure NusClient + libWiiSharp.NusClient nusClient = new libWiiSharp.NusClient(); + nusClient.ConfigureNusClient(nusWC); + nusClient.UseLocalFiles = localuse.Checked; + // Get placement directory early... string titledirectory; if (titleversion.Text == "") titledirectory = Path.Combine(CURRENT_DIR, titleid); else - titledirectory = Path.Combine(CURRENT_DIR, (titleid + "v" + titleversion.Text)); + titledirectory = Path.Combine(CURRENT_DIR, (titleid + "v" + title_v)); if (script_mode) titledirectory = Path.Combine(CURRENT_DIR, "output_" + Path.GetFileNameWithoutExtension(script_filename)); downloadstartbtn.Text = "Prerequisites: (0/2)"; - // Windows 7? + // Windows 7? Windows 7 Taskbar progress can be used. if (IsWin7()) - { - // Windows 7 Taskbar progress can be used. dlprogress.ShowInTaskbar = true; - } // Download TMD before the rest... string tmdfull = "tmd"; - if (titleversion.Text != "") - tmdfull += "." + titleversion.Text; + if (String.IsNullOrEmpty(title_v) == false) + tmdfull += "." + title_v; + + byte[] tmd; try { - DownloadNUSFile(titleid, tmdfull, titledirectory, 0, wiimode); + tmd = nusClient.DownloadTMD(titleid, title_v, activeNusUrl); + //DownloadNUSFile(titleid, tmdfull, titledirectory, 0, wiimode); } catch (Exception ex) { @@ -930,11 +940,13 @@ namespace NUS_Downloader wadnamebox.Enabled = false; } - // Download CETK after tmd... - bool ticket_exists = true; + // Download cetk (ticket) after tmd... + //bool ticket_exists = true; + byte[] cetkbuf = new byte[0]; try { - DownloadNUSFile(titleid, "cetk", titledirectory, 0, wiimode); + cetkbuf = nusClient.DownloadTicket(titleid, activeNusUrl); + //DownloadNUSFile(titleid, "cetk", titledirectory, 0, wiimode); } catch (Exception ex) { @@ -953,17 +965,18 @@ namespace NUS_Downloader packbox.Checked = false; decryptbox.Checked = false; WAD_Saveas_Filename = String.Empty; - ticket_exists = false; + //ticket_exists = false; } + downloadstartbtn.Text = "Prerequisites: (2/2)"; dlprogress.Value = 100; - byte[] cetkbuf = new byte[0]; + //byte[] cetkbuf = new byte[0]; byte[] titlekey = new byte[0]; - if (ticket_exists) + if ((cetkbuf.Length > 0) == true) { // Create ticket file holder - cetkbuf = FileLocationToByteArray(Path.Combine(titledirectory, "cetk")); + //cetkbuf = FileLocationToByteArray(Path.Combine(titledirectory, "cetk")); // Obtain TitleKey titlekey = new byte[16]; @@ -1011,9 +1024,9 @@ namespace NUS_Downloader } // Read the tmd as a stream... - byte[] tmd = FileLocationToByteArray(Path.Combine(titledirectory, tmdfull)); + //byte[] tmd = FileLocationToByteArray(Path.Combine(titledirectory, tmdfull)); - if (ticket_exists == true) + if ((cetkbuf.Length > 0) == true) { // Locate Certs ************************************** if (!(CertsValid())) @@ -1028,25 +1041,22 @@ namespace NUS_Downloader } // Read Title Version... - string tmdversion = ""; - for (int x = 476; x < 478; x++) + if (String.IsNullOrEmpty(title_v)) { - tmdversion += MakeProperLength(ConvertToHex(Convert.ToString(tmd[x]))); + 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)); + title_v = tmdversion; } - 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 permission errors... - /*if ((CURRENT_DIR + titleid + "v" + titleversion.Text + Path.DirectorySeparatorChar.ToString()) != titledirectory) - { - Directory.Move(titledirectory, CURRENT_DIR + titleid + "v" + titleversion.Text + Path.DirectorySeparatorChar.ToString()); - titledirectory = CURRENT_DIR + titleid + "v" + titleversion.Text + Path.DirectorySeparatorChar.ToString(); - } */ - // Read Content #... string contentstrnum = ""; for (int x = 478; x < 480; x++) @@ -1054,7 +1064,7 @@ namespace NUS_Downloader contentstrnum += TrimLeadingZeros(Convert.ToString(tmd[x])); } WriteStatus("Content #: " + contentstrnum); - downloadstartbtn.Text = "Content: (0/" + contentstrnum + ")"; + downloadstartbtn.Text = String.Format("Content: (0/{0})", contentstrnum); dlprogress.Value = 0; // Gather information... @@ -1070,21 +1080,29 @@ namespace NUS_Downloader { totalcontentsize += int.Parse(tmdsizes[i], System.Globalization.NumberStyles.HexNumber); } - WriteStatus("Total Size: " + (long) totalcontentsize + " bytes"); + WriteStatus("Total Size: " + (long)totalcontentsize + " bytes"); + // Write files from NUS out... + File.WriteAllBytes(Path.Combine(titledirectory, tmdfull), tmd); + File.WriteAllBytes(Path.Combine(titledirectory, "cetk"), cetkbuf); + + // Download each content for (int i = 0; i < tmdcontents.Length; i++) { + byte[] contbuf; try { // If it exists we leave it... - if ((localuse.Checked) && (File.Exists(Path.Combine(titledirectory, tmdcontents[i])))) + if ((nusClient.UseLocalFiles) && (File.Exists(Path.Combine(titledirectory, tmdcontents[i])))) { WriteStatus("Leaving local " + tmdcontents[i] + "."); + contbuf = File.ReadAllBytes(Path.Combine(titledirectory, tmdcontents[i])); } else { - DownloadNUSFile(titleid, tmdcontents[i], titledirectory, - int.Parse(tmdsizes[i], System.Globalization.NumberStyles.HexNumber), wiimode); + contbuf = nusClient.DownloadSingleContent(titleid, title_v, tmdcontents[i], activeNusUrl); + //DownloadNUSFile(titleid, tmdcontents[i], titledirectory, + //int.Parse(tmdsizes[i], System.Globalization.NumberStyles.HexNumber), wiimode); } } catch (Exception ex) @@ -1099,6 +1117,9 @@ namespace NUS_Downloader return; } + if (File.Exists(Path.Combine(titledirectory, tmdcontents[i])) == false) + File.WriteAllBytes(Path.Combine(titledirectory, tmdcontents[i]), contbuf); + // Progress reporting advances... downloadstartbtn.Text = String.Format("Content: ({0} / {1})", (i + 1), contentstrnum); currentcontentlocation += int.Parse(tmdsizes[i], System.Globalization.NumberStyles.HexNumber); @@ -1107,8 +1128,8 @@ namespace NUS_Downloader if (decryptbox.Checked == true) { // Create content file holder - byte[] contbuf = - FileLocationToByteArray(Path.Combine(titledirectory, tmdcontents[i])); + //byte[] contbuf = + //FileLocationToByteArray(Path.Combine(titledirectory, tmdcontents[i])); // IV (00+IDX+more000) byte[] iv = new byte[16]; @@ -1164,6 +1185,11 @@ namespace NUS_Downloader { PackWAD(titleid, tmdfull, titledirectory); } + } + + private void NUSDownloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + WAD_Saveas_Filename = String.Empty; SetEnableforDownload(true); downloadstartbtn.Text = "Start NUS Download!"; @@ -1174,11 +1200,6 @@ namespace NUS_Downloader if (script_mode) statusbox.Text = ""; - } - - private void NUSDownloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) - { - WAD_Saveas_Filename = String.Empty; } /// @@ -1225,6 +1246,7 @@ namespace NUS_Downloader Directory.Delete(titledirectory, true); } + /* /// /// Downloads the NUS file. /// @@ -1252,7 +1274,7 @@ namespace NUS_Downloader // Download NUS file... generalWC.DownloadFile(nusfileurl, Path.Combine(placementdir, filename)); } - + */ private void StatusChange(string status) { WriteStatus(status); @@ -1293,8 +1315,8 @@ namespace NUS_Downloader packer.Certs = certsbuf; // Read TMD/TIK into Packer. - packer.Ticket = FileLocationToByteArray(Path.Combine(totaldirectory, "cetk")); - packer.TMD = FileLocationToByteArray(Path.Combine(totaldirectory, tmdfilename)); + packer.Ticket = File.ReadAllBytes(Path.Combine(totaldirectory, "cetk")); + packer.TMD = File.ReadAllBytes(Path.Combine(totaldirectory, tmdfilename)); // Get the TMD variables in here instead... int contentcount = ContentCount(packer.TMD); @@ -1325,7 +1347,7 @@ namespace NUS_Downloader byte[][] contents_array = new byte[contentcount][]; for (int a = 0; a < contentcount; a++) { - contents_array[a] = FileLocationToByteArray(Path.Combine(totaldirectory, contentnames[a])); + contents_array[a] = File.ReadAllBytes(Path.Combine(totaldirectory, contentnames[a])); } packer.Contents = contents_array; @@ -1512,7 +1534,7 @@ namespace NUS_Downloader if (File.Exists(Path.Combine(CURRENT_DIR, keyfile)) == true) { // Read common key byte[] - return FileLocationToByteArray(Path.Combine(CURRENT_DIR, keyfile)); + return File.ReadAllBytes(Path.Combine(CURRENT_DIR, keyfile)); } else return null; @@ -2188,7 +2210,8 @@ namespace NUS_Downloader return null; } - + + /* /// /// Loads a file into a byte[] /// @@ -2200,7 +2223,7 @@ namespace NUS_Downloader byte[] filebytearray = ReadFully(fs, 460); fs.Close(); return filebytearray; - } + }*/ /// /// Updates the name of the packed WAD in the textbox. @@ -3234,12 +3257,13 @@ namespace NUS_Downloader WriteStatus("Special thanks to:"); WriteStatus(" * Crediar for his wadmaker tool + source, and for the advice!"); WriteStatus(" * SquidMan/Galaxy/comex/Xuzz for advice/sources."); + WriteStatus(" * Leathl for portions of libWiiSharp."); 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."); - WriteStatus(" * Wyatt O'Day for the Windows7ProgressBar Control."); WriteStatus(" * Napo7 for testing proxy usage."); + WriteStatus(" * Wyatt O'Day for the Windows7ProgressBar Control."); + WriteStatus(" * Famfamfam for the Silk Icon Set."); + WriteStatus(" * #WiiDev for answering the tough questions."); + WriteStatus(" * Anyone who helped beta test!"); } private void checkBox1_CheckedChanged(object sender, EventArgs e) diff --git a/NUS Downloader/NUS Downloader.csproj b/NUS Downloader/NUS Downloader.csproj index 518c49c..fdb54bc 100644 --- a/NUS Downloader/NUS Downloader.csproj +++ b/NUS Downloader/NUS Downloader.csproj @@ -77,6 +77,7 @@ Form1.cs PreserveNewest + diff --git a/NUS Downloader/NusClient.cs b/NUS Downloader/NusClient.cs new file mode 100644 index 0000000..edf355d --- /dev/null +++ b/NUS Downloader/NusClient.cs @@ -0,0 +1,473 @@ +/* This file is part of libWiiSharp + * Copyright (C) 2009 Leathl + * + * libWiiSharp is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libWiiSharp is distributed in the hope that it will be + * useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Security.Cryptography; + +namespace libWiiSharp +{ + public class NusClient : IDisposable + { + //private const string nusUrl = "http://nus.cdn.shop.wii.com/ccs/download/"; + private WebClient wcNus = new WebClient(); + private bool useLocalFiles = false; + //private bool continueWithoutTicket = false; + + /// + /// If true, existing local files will be used. + /// + public bool UseLocalFiles { get { return useLocalFiles; } set { useLocalFiles = value; } } + /// + /// If true, the download will be continued even if no ticket for the title is avaiable (WAD packaging and decryption are disabled). + /// + //public bool ContinueWithoutTicket { get { return continueWithoutTicket; } set { continueWithoutTicket = value; } } + + #region IDisposable Members + private bool isDisposed = false; + + ~NusClient() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing && !isDisposed) + { + wcNus.Dispose(); + } + + isDisposed = true; + } + #endregion + + #region Public Functions + + public void ConfigureNusClient(WebClient wcReady) + { + wcNus = wcReady; + } + + /// + /// Grabs a TMD from NUS. + /// Leave the title version empty for the latest. + /// + /// + /// + /// + public byte[] DownloadTMD(string titleId, string titleVersion, string nusUrl) + { + if (titleId.Length != 16) throw new Exception("Title ID must be 16 characters long!"); + return downloadTmd(titleId, titleVersion, nusUrl); + } + + /// + /// Grabs a Ticket from NUS. + /// + /// + /// + public byte[] DownloadTicket(string titleId, string nusUrl) + { + if (titleId.Length != 16) throw new Exception("Title ID must be 16 characters long!"); + return downloadTicket(titleId, nusUrl); + } + + /// + /// Grabs a single content file and decrypts it. + /// Leave the title version empty for the latest. + /// + /// + /// + /// + /// + public byte[] DownloadSingleContent(string titleId, string titleVersion, string contentId, string nusUrl) + { + if (titleId.Length != 16) throw new Exception("Title ID must be 16 characters long!"); + return downloadSingleContent(titleId, titleVersion, contentId, nusUrl); + } + #endregion + + #region Private Functions + private byte[] downloadSingleContent(string titleId, string titleVersion, string contentId, string nusUrl) + { + uint cId = uint.Parse(contentId, System.Globalization.NumberStyles.HexNumber); + contentId = cId.ToString("x8"); + + fireDebug("Downloading Content (Content ID: {0}) of Title {1} v{2}...", contentId, titleId, (string.IsNullOrEmpty(titleVersion)) ? "[Latest]" : titleVersion); + + fireDebug(" Checking for Internet connection..."); + if (!CheckInet()) + { fireDebug(" Connection not found..."); throw new Exception("You're not connected to the internet!"); } + + fireProgress(0); + + //string tmdFile = "tmd" + (string.IsNullOrEmpty(titleVersion) ? string.Empty : string.Format(".{0}", titleVersion)); + string titleUrl = string.Format("{0}{1}/", nusUrl, titleId); + string contentIdString = contentId; + //int cIndex = 0; + + /*Download TMD + fireDebug(" Downloading TMD..."); + byte[] tmdArray = wcNus.DownloadData(titleUrl + tmdFile); + fireDebug(" Parsing TMD..."); + TMD tmd = TMD.Load(tmdArray);*/ + + //fireProgress(20); + + /*Search for Content ID in TMD + fireDebug(" Looking for Content ID {0} in TMD...", contentId); + bool foundContentId = false; + for (int i = 0; i < tmd.Contents.Length; i++) + if (tmd.Contents[i].ContentID == cId) + { + fireDebug(" Content ID {0} found in TMD...", contentId); + foundContentId = true; + contentIdString = tmd.Contents[i].ContentID.ToString("x8"); + cIndex = i; + break; + } + + if (!foundContentId) + { fireDebug(" Content ID {0} wasn't found in TMD...", contentId); throw new Exception("Content ID wasn't found in the TMD!"); } + + //Download Ticket + fireDebug(" Downloading Ticket..."); + byte[] tikArray = wcNus.DownloadData(titleUrl + "cetk"); + fireDebug(" Parsing Ticket..."); + Ticket tik = Ticket.Load(tikArray); + + fireProgress(40); + + fireDebug(" Downloading Content... ({0} bytes)", tmd.Contents[cIndex].Size); */ + + byte[] encryptedContent = wcNus.DownloadData(titleUrl + contentIdString); + + fireProgress(80); + + /* + fireDebug(" Decrypting Content..."); + byte[] decryptedContent = decryptContent(encryptedContent, cIndex, tik, tmd); + Array.Resize(ref decryptedContent, (int)tmd.Contents[cIndex].Size); + + //Check SHA1 + SHA1 s = SHA1.Create(); + byte[] newSha = s.ComputeHash(decryptedContent); + + if (!Shared.CompareByteArrays(newSha, tmd.Contents[cIndex].Hash)) + { fireDebug(@"/!\ /!\ /!\ Hashes do not match /!\ /!\ /!\"); throw new Exception("Hashes do not match!"); } + + fireProgress(100); + + fireDebug("Downloading Content (Content ID: {0}) of Title {1} v{2} Finished...", contentId, titleId, (string.IsNullOrEmpty(titleVersion)) ? "[Latest]" : titleVersion); + return decryptedContent;*/ + return encryptedContent; + } + + private byte[] downloadTicket(string titleId, string nusUrl) + { + if (!CheckInet()) + throw new Exception("You're not connected to the internet!"); + + string titleUrl = string.Format("{0}{1}/", nusUrl, titleId); + byte[] tikArray = wcNus.DownloadData(titleUrl + "cetk"); + + return tikArray; + } + + private byte[] downloadTmd(string titleId, string titleVersion, string nusUrl) + { + if (!CheckInet()) + throw new Exception("You're not connected to the internet!"); + + string titleUrl = string.Format("{0}{1}/", nusUrl, titleId); + string tmdFile = "tmd" + (string.IsNullOrEmpty(titleVersion) ? string.Empty : string.Format(".{0}", titleVersion)); + + byte[] tmdArray = wcNus.DownloadData(titleUrl + tmdFile); + + return tmdArray; + } + + /*private void downloadTitle(string titleId, string titleVersion, string outputDir, StoreType[] storeTypes) + { + fireDebug("Downloading Title {0} v{1}...", titleId, (string.IsNullOrEmpty(titleVersion)) ? "[Latest]" : titleVersion); + + if (storeTypes.Length < 1) + { fireDebug(" No store types were defined..."); throw new Exception("You must at least define one store type!"); } + + string titleUrl = string.Format("{0}{1}/", nusUrl, titleId); + bool storeEncrypted = false; + bool storeDecrypted = false; + bool storeWad = false; + + fireProgress(0); + + foreach (StoreType st in storeTypes) + { + switch (st) + { + case StoreType.DecryptedContent: + fireDebug(" -> Storing Decrypted Content..."); + storeDecrypted = true; + break; + case StoreType.EncryptedContent: + fireDebug(" -> Storing Encrypted Content..."); + storeEncrypted = true; + break; + case StoreType.WAD: + fireDebug(" -> Storing WAD..."); + storeWad = true; + break; + case StoreType.All: + fireDebug(" -> Storing Decrypted Content..."); + fireDebug(" -> Storing Encrypted Content..."); + fireDebug(" -> Storing WAD..."); + storeDecrypted = true; + storeEncrypted = true; + storeWad = true; + break; + } + } + + fireDebug(" Checking for Internet connection..."); + if (!CheckInet()) + { fireDebug(" Connection not found..."); throw new Exception("You're not connected to the internet!"); } + + if (outputDir[outputDir.Length - 1] != Path.DirectorySeparatorChar) outputDir += Path.DirectorySeparatorChar; + if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir); + + string tmdFile = "tmd" + (string.IsNullOrEmpty(titleVersion) ? string.Empty : string.Format(".{0}", titleVersion)); + + //Download TMD + fireDebug(" Downloading TMD..."); + try + { + wcNus.DownloadFile(titleUrl + tmdFile, outputDir + tmdFile); + } + catch (Exception ex) { fireDebug(" Downloading TMD Failed..."); throw new Exception("Downloading TMD Failed:\n" + ex.Message); } + + fireProgress(5); + + //Download cetk + fireDebug(" Downloading Ticket..."); + try + { + wcNus.DownloadFile(titleUrl + "cetk", outputDir + "cetk"); + } + catch (Exception ex) + { + if (!continueWithoutTicket || !storeEncrypted) + { + fireDebug(" Downloading Ticket Failed..."); + throw new Exception("Downloading Ticket Failed:\n" + ex.Message); + } + + storeDecrypted = false; + storeWad = false; + } + + fireProgress(10); + + //Parse TMD and Ticket + fireDebug(" Parsing TMD..."); + TMD tmd = TMD.Load(outputDir + tmdFile); + + if (string.IsNullOrEmpty(titleVersion)) { fireDebug(" -> Title Version: {0}", tmd.TitleVersion); } + fireDebug(" -> {0} Contents", tmd.NumOfContents); + + fireDebug(" Parsing Ticket..."); + Ticket tik = Ticket.Load(outputDir + "cetk"); + + string[] encryptedContents = new string[tmd.NumOfContents]; + + //Download Content + for (int i = 0; i < tmd.NumOfContents; i++) + { + fireDebug(" Downloading Content #{0} of {1}... ({2} bytes)", i + 1, tmd.NumOfContents, tmd.Contents[i].Size); + fireProgress(((i + 1) * 60 / tmd.NumOfContents) + 10); + + if (useLocalFiles && File.Exists(outputDir + tmd.Contents[i].ContentID.ToString("x8"))) + { fireDebug(" Using Local File, Skipping..."); continue; } + + try + { + wcNus.DownloadFile(titleUrl + tmd.Contents[i].ContentID.ToString("x8"), + outputDir + tmd.Contents[i].ContentID.ToString("x8")); + + encryptedContents[i] = tmd.Contents[i].ContentID.ToString("x8"); + } + catch (Exception ex) { fireDebug(" Downloading Content #{0} of {1} failed...", i + 1, tmd.NumOfContents); throw new Exception("Downloading Content Failed:\n" + ex.Message); } + } + + //Decrypt Content + if (storeDecrypted || storeWad) + { + SHA1 s = SHA1.Create(); + + for (int i = 0; i < tmd.NumOfContents; i++) + { + fireDebug(" Decrypting Content #{0} of {1}...", i + 1, tmd.NumOfContents); + fireProgress(((i + 1) * 20 / tmd.NumOfContents) + 75); + + //Decrypt Content + byte[] decryptedContent = + decryptContent(File.ReadAllBytes(outputDir + tmd.Contents[i].ContentID.ToString("x8")), i, tik, tmd); + Array.Resize(ref decryptedContent, (int)tmd.Contents[i].Size); + + //Check SHA1 + byte[] newSha = s.ComputeHash(decryptedContent); + if (!Shared.CompareByteArrays(newSha, tmd.Contents[i].Hash)) + { fireDebug(@"/!\ /!\ /!\ Hashes do not match /!\ /!\ /!\"); throw new Exception(string.Format("Content #{0}: Hashes do not match!", i)); } + + //Write Decrypted Content + File.WriteAllBytes(outputDir + tmd.Contents[i].ContentID.ToString("x8") + ".app", decryptedContent); + } + + s.Clear(); + } + + //Pack Wad + if (storeWad) + { + fireDebug(" Building Certificate Chain..."); + CertificateChain cert = CertificateChain.FromTikTmd(outputDir + "cetk", outputDir + tmdFile); + + byte[][] contents = new byte[tmd.NumOfContents][]; + + for (int i = 0; i < tmd.NumOfContents; i++) + contents[i] = File.ReadAllBytes(outputDir + tmd.Contents[i].ContentID.ToString("x8") + ".app"); + + fireDebug(" Creating WAD..."); + WAD wad = WAD.Create(cert, tik, tmd, contents); + wad.Save(outputDir + tmd.TitleID.ToString("x16") + "v" + tmd.TitleVersion.ToString() + ".wad"); + } + + //Delete not wanted files + if (!storeEncrypted) + { + fireDebug(" Deleting Encrypted Contents..."); + for (int i = 0; i < encryptedContents.Length; i++) + if (File.Exists(outputDir + encryptedContents[i])) File.Delete(outputDir + encryptedContents[i]); + } + + if (storeWad && !storeDecrypted) + { + fireDebug(" Deleting Decrypted Contents..."); + for (int i = 0; i < encryptedContents.Length; i++) + if (File.Exists(outputDir + encryptedContents[i] + ".app")) File.Delete(outputDir + encryptedContents[i] + ".app"); + } + + if (!storeDecrypted && !storeEncrypted) + { + fireDebug(" Deleting TMD and Ticket..."); + File.Delete(outputDir + tmdFile); + File.Delete(outputDir + "cetk"); + } + + fireDebug("Downloading Title {0} v{1} Finished...", titleId, (string.IsNullOrEmpty(titleVersion)) ? "[Latest]" : titleVersion); + fireProgress(100); + } + + /*private byte[] decryptContent(byte[] content, int contentIndex, Ticket tik, TMD tmd) + { + Array.Resize(ref content, Shared.AddPadding(content.Length, 16)); + byte[] titleKey = tik.TitleKey; + byte[] iv = new byte[16]; + + byte[] tmp = BitConverter.GetBytes(tmd.Contents[contentIndex].Index); + iv[0] = tmp[1]; + iv[1] = tmp[0]; + + RijndaelManaged rm = new RijndaelManaged(); + rm.Mode = CipherMode.CBC; + rm.Padding = PaddingMode.None; + rm.KeySize = 128; + rm.BlockSize = 128; + rm.Key = titleKey; + rm.IV = iv; + + ICryptoTransform decryptor = rm.CreateDecryptor(); + + MemoryStream ms = new MemoryStream(content); + CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read); + + byte[] decCont = new byte[content.Length]; + cs.Read(decCont, 0, decCont.Length); + + cs.Dispose(); + ms.Dispose(); + + return decCont; + }*/ + + private bool CheckInet() + { + try + { + System.Net.IPHostEntry ipHost = System.Net.Dns.GetHostEntry("www.google.com"); + return true; + } + catch { return false; } + } + #endregion + + #region Events + /// + /// Fires the Progress of various operations + /// + public event EventHandler Progress; + /// + /// Fires debugging messages. You may write them into a log file or log textbox. + /// + public event EventHandler Debug; + + private void fireDebug(string debugMessage, params object[] args) + { + EventHandler debug = Debug; + if (debug != null) + debug(new object(), new MessageEventArgs(string.Format(debugMessage, args))); + } + + private void fireProgress(int progressPercentage) + { + EventHandler progress = Progress; + if (progress != null) + progress(new object(), new ProgressChangedEventArgs(progressPercentage, string.Empty)); + } + #endregion + } +} + +namespace libWiiSharp +{ + public class MessageEventArgs : EventArgs + { + private string message; + public string Message { get { return message; } } + + public MessageEventArgs(string message) { this.message = message; } + } +} +