/* This file is part of libWiiSharp * Copyright (C) 2009 Leathl * Copyright (C) 2020 - 2022 TheShadowEevee, Github Contributors * * libWiiSharp is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * libWiiSharp is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Security.Cryptography; using System.Text; namespace libWiiSharp { public enum LowerTitleID : uint { SystemTitles = 0x00000001, SystemChannels = 0x00010002, Channel = 0x00010001, GameChannel = 0x00010004, DLC = 0x00010005, HiddenChannels = 0x00010008, } public class WAD : IDisposable { private SHA1 sha = SHA1.Create(); private DateTime creationTimeUTC = new DateTime(1970, 1, 1); private bool hasBanner; private bool lz77CompressBannerAndIcon = true; private bool lz77DecompressBannerAndIcon; private bool keepOriginalFooter; private WAD_Header wadHeader; private CertificateChain cert = new CertificateChain(); private Ticket tik = new Ticket(); private TMD tmd = new TMD(); private List contents; private U8 bannerApp = new U8(); private byte[] footer = new byte[0]; private bool isDisposed; public Region Region { get => tmd.Region; set => tmd.Region = value; } public int NumOfContents => tmd.NumOfContents; public byte[][] Contents => contents.ToArray(); public bool FakeSign { get => tik.FakeSign && tmd.FakeSign; set { tik.FakeSign = value; tmd.FakeSign = value; } } public U8 BannerApp { get => bannerApp; set => bannerApp = value; } public ulong StartupIOS { get => tmd.StartupIOS; set => tmd.StartupIOS = value; } public ulong TitleID { get => tik.TitleID; set { tik.TitleID = value; tmd.TitleID = value; } } public string UpperTitleID => tik.GetUpperTitleID(); public ushort TitleVersion { get => tmd.TitleVersion; set => tmd.TitleVersion = value; } public ushort BootIndex { get => tmd.BootIndex; set => tmd.BootIndex = value; } public DateTime CreationTimeUTC => creationTimeUTC; public bool HasBanner => hasBanner; public bool Lz77CompressBannerAndIcon { get => lz77CompressBannerAndIcon; set { lz77CompressBannerAndIcon = value; if (!value) { return; } lz77DecompressBannerAndIcon = false; } } public bool Lz77DecompressBannerAndIcon { get => lz77DecompressBannerAndIcon; set { lz77DecompressBannerAndIcon = value; if (!value) { return; } lz77CompressBannerAndIcon = false; } } public string NandBlocks => tmd.GetNandBlocks(); public string[] ChannelTitles { get => hasBanner ? ((Headers.IMET)bannerApp.Header).AllTitles : new string[0]; set => ChangeChannelTitles(value); } public bool KeepOriginalFooter { get => keepOriginalFooter; set => keepOriginalFooter = value; } public TMD_Content[] TmdContents => tmd.Contents; public CommonKeyType CommonKeyType { get => tik.CommonKeyIndex; set => tik.CommonKeyIndex = value; } public bool SortContents { get => tmd.SortContents; set => tmd.SortContents = value; } public event EventHandler Progress; public event EventHandler Warning; public event EventHandler Debug; public WAD() { cert.Debug += new EventHandler(Cert_Debug); tik.Debug += new EventHandler(Tik_Debug); tmd.Debug += new EventHandler(Tmd_Debug); bannerApp.Debug += new EventHandler(BannerApp_Debug); bannerApp.Warning += new EventHandler(BannerApp_Warning); } ~WAD() => Dispose(false); public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing && !isDisposed) { sha.Clear(); sha = null; wadHeader = null; cert.Dispose(); tik.Dispose(); tmd.Dispose(); contents.Clear(); contents = null; bannerApp.Dispose(); footer = null; } isDisposed = true; } public static WAD Load(string pathToWad) { return Load(File.ReadAllBytes(pathToWad)); } public static WAD Load(byte[] wadFile) { WAD wad = new WAD(); MemoryStream memoryStream = new MemoryStream(wadFile); try { wad.ParseWad(memoryStream); } catch { memoryStream.Dispose(); throw; } memoryStream.Dispose(); return wad; } public static WAD Load(Stream wad) { WAD wad1 = new WAD(); wad1.ParseWad(wad); return wad1; } public static WAD Create(string contentDir) { string[] files1 = Directory.GetFiles(contentDir, "*cert*"); string[] files2 = Directory.GetFiles(contentDir, "*tik*"); string[] files3 = Directory.GetFiles(contentDir, "*tmd*"); CertificateChain cert = CertificateChain.Load(files1[0]); Ticket tik = Ticket.Load(files2[0]); TMD tmd = TMD.Load(files3[0]); bool flag = true; for (int index = 0; index < tmd.Contents.Length; ++index) { if (!File.Exists(contentDir + Path.DirectorySeparatorChar.ToString() + tmd.Contents[index].ContentID.ToString("x8") + ".app")) { flag = false; break; } } if (!flag) { for (int index = 0; index < tmd.Contents.Length; ++index) { if (!File.Exists(contentDir + Path.DirectorySeparatorChar.ToString() + tmd.Contents[index].Index.ToString("x8") + ".app")) { throw new Exception("Couldn't find all content files!"); } } } byte[][] contents = new byte[tmd.Contents.Length][]; for (int index = 0; index < tmd.Contents.Length; ++index) { string path = contentDir + Path.DirectorySeparatorChar.ToString() + (flag ? tmd.Contents[index].ContentID.ToString("x8") : tmd.Contents[index].Index.ToString("x8")) + ".app"; contents[index] = File.ReadAllBytes(path); } return Create(cert, tik, tmd, contents); } public static WAD Create( string pathToCert, string pathToTik, string pathToTmd, string contentDir) { CertificateChain cert = CertificateChain.Load(pathToCert); Ticket tik = Ticket.Load(pathToTik); TMD tmd = TMD.Load(pathToTmd); bool flag = true; for (int index = 0; index < tmd.Contents.Length; ++index) { if (!File.Exists(contentDir + Path.DirectorySeparatorChar.ToString() + tmd.Contents[index].ContentID.ToString("x8") + ".app")) { flag = false; break; } } if (!flag) { for (int index = 0; index < tmd.Contents.Length; ++index) { if (!File.Exists(contentDir + Path.DirectorySeparatorChar.ToString() + tmd.Contents[index].Index.ToString("x8") + ".app")) { throw new Exception("Couldn't find all content files!"); } } } byte[][] contents = new byte[tmd.Contents.Length][]; for (int index = 0; index < tmd.Contents.Length; ++index) { string path = contentDir + Path.DirectorySeparatorChar.ToString() + (flag ? tmd.Contents[index].ContentID.ToString("x8") : tmd.Contents[index].Index.ToString("x8")) + ".app"; contents[index] = File.ReadAllBytes(path); } return Create(cert, tik, tmd, contents); } public static WAD Create(byte[] cert, byte[] tik, byte[] tmd, byte[][] contents) { CertificateChain cert1 = CertificateChain.Load(cert); Ticket ticket = Ticket.Load(tik); TMD tmd1 = TMD.Load(tmd); Ticket tik1 = ticket; TMD tmd2 = tmd1; byte[][] contents1 = contents; return Create(cert1, tik1, tmd2, contents1); } public static WAD Create(CertificateChain cert, Ticket tik, TMD tmd, byte[][] contents) { WAD wad = new WAD() { cert = cert, tik = tik, tmd = tmd, contents = new List(contents), wadHeader = new WAD_Header() }; wad.wadHeader.TmdSize = (uint)(484 + tmd.Contents.Length * 36); int num1 = 0; for (int index = 0; index < contents.Length - 1; ++index) { num1 += Shared.AddPadding(contents[index].Length); } int num2 = num1 + contents[contents.Length - 1].Length; wad.wadHeader.ContentSize = (uint)num2; for (int index = 0; index < wad.tmd.Contents.Length; ++index) { if (wad.tmd.Contents[index].Index == 0) { try { wad.bannerApp.LoadFile(contents[index]); wad.hasBanner = true; return wad; } catch { wad.hasBanner = false; return wad; } } } return wad; } public void LoadFile(string pathToWad) { LoadFile(File.ReadAllBytes(pathToWad)); } public void LoadFile(byte[] wadFile) { MemoryStream memoryStream = new MemoryStream(wadFile); try { ParseWad(memoryStream); } catch { memoryStream.Dispose(); throw; } memoryStream.Dispose(); } public void LoadFile(Stream wad) { ParseWad(wad); } public void CreateNew(string contentDir) { string[] files1 = Directory.GetFiles(contentDir, "*cert*"); string[] files2 = Directory.GetFiles(contentDir, "*tik*"); string[] files3 = Directory.GetFiles(contentDir, "*tmd*"); CertificateChain cert = CertificateChain.Load(files1[0]); Ticket tik = Ticket.Load(files2[0]); TMD tmd = TMD.Load(files3[0]); bool flag = true; for (int index = 0; index < tmd.Contents.Length; ++index) { if (!File.Exists(contentDir + Path.DirectorySeparatorChar.ToString() + tmd.Contents[index].ContentID.ToString("x8") + ".app")) { flag = false; break; } } if (!flag) { for (int index = 0; index < tmd.Contents.Length; ++index) { if (!File.Exists(contentDir + Path.DirectorySeparatorChar.ToString() + tmd.Contents[index].Index.ToString("x8") + ".app")) { throw new Exception("Couldn't find all content files!"); } } } byte[][] contents = new byte[tmd.Contents.Length][]; for (int index = 0; index < tmd.Contents.Length; ++index) { string path = contentDir + Path.DirectorySeparatorChar.ToString() + (flag ? tmd.Contents[index].ContentID.ToString("x8") : tmd.Contents[index].Index.ToString("x8")) + ".app"; contents[index] = File.ReadAllBytes(path); } CreateNew(cert, tik, tmd, contents); } public void CreateNew( string pathToCert, string pathToTik, string pathToTmd, string contentDir) { CertificateChain cert = CertificateChain.Load(pathToCert); Ticket tik = Ticket.Load(pathToTik); TMD tmd = TMD.Load(pathToTmd); bool flag = true; for (int index = 0; index < tmd.Contents.Length; ++index) { if (!File.Exists(contentDir + Path.DirectorySeparatorChar.ToString() + tmd.Contents[index].ContentID.ToString("x8") + ".app")) { flag = false; break; } } if (!flag) { for (int index = 0; index < tmd.Contents.Length; ++index) { if (!File.Exists(contentDir + Path.DirectorySeparatorChar.ToString() + tmd.Contents[index].Index.ToString("x8") + ".app")) { throw new Exception("Couldn't find all content files!"); } } } byte[][] contents = new byte[tmd.Contents.Length][]; for (int index = 0; index < tmd.Contents.Length; ++index) { string path = contentDir + Path.DirectorySeparatorChar.ToString() + (flag ? tmd.Contents[index].ContentID.ToString("x8") : tmd.Contents[index].Index.ToString("x8")) + ".app"; contents[index] = File.ReadAllBytes(path); } CreateNew(cert, tik, tmd, contents); } public void CreateNew(byte[] cert, byte[] tik, byte[] tmd, byte[][] contents) { CreateNew(CertificateChain.Load(cert), Ticket.Load(tik), TMD.Load(tmd), contents); } public void CreateNew(CertificateChain cert, Ticket tik, TMD tmd, byte[][] contents) { this.cert = cert; this.tik = tik; this.tmd = tmd; this.contents = new List(contents); wadHeader = new WAD_Header { TmdSize = (uint)(484 + tmd.Contents.Length * 36) }; int num = 0; for (int index = 0; index < contents.Length - 1; ++index) { num += Shared.AddPadding(contents[index].Length); } wadHeader.ContentSize = (uint)(num + contents[contents.Length - 1].Length); for (int index = 0; index < this.tmd.Contents.Length; ++index) { if (this.tmd.Contents[index].Index == 0) { try { bannerApp.LoadFile(contents[index]); hasBanner = true; break; } catch { hasBanner = false; break; } } } } public void Save(string savePath) { if (File.Exists(savePath)) { File.Delete(savePath); } using FileStream fileStream = new FileStream(savePath, FileMode.Create); WriteToStream(fileStream); } public MemoryStream ToMemoryStream() { MemoryStream memoryStream = new MemoryStream(); try { WriteToStream(memoryStream); return memoryStream; } catch { memoryStream.Dispose(); throw; } } public byte[] ToByteArray() { MemoryStream memoryStream = new MemoryStream(); try { WriteToStream(memoryStream); } catch { memoryStream.Dispose(); throw; } byte[] array = memoryStream.ToArray(); memoryStream.Dispose(); return array; } public void ChangeTitleID(LowerTitleID lowerID, string upperID) { uint num1 = upperID.Length == 4 ? BitConverter.ToUInt32(new byte[4] { (byte) upperID[3], (byte) upperID[2], (byte) upperID[1], (byte) upperID[0] }, 0) : throw new Exception("Upper Title ID must be 4 characters long!"); ulong num2 = (ulong)lowerID << 32 | num1; tik.TitleID = num2; tmd.TitleID = num2; } public void ChangeStartupIOS(int newIos) { StartupIOS = 4294967296UL | (uint)newIos; } public void ChangeTitleKey(string newTitleKey) { tik.SetTitleKey(newTitleKey); } public void ChangeTitleKey(char[] newTitleKey) { tik.SetTitleKey(newTitleKey); } public void ChangeTitleKey(byte[] newTitleKey) { tik.SetTitleKey(newTitleKey); } public byte[] GetContentByIndex(int index) { for (int index1 = 0; index1 < tmd.NumOfContents; ++index1) { if (tmd.Contents[index1].Index == index) { return contents[index1]; } } throw new Exception(string.Format("Content with index {0} not found!", index)); } public byte[] GetContentByID(int contentID) { for (int index = 0; index < tmd.NumOfContents; ++index) { if (tmd.Contents[index].Index == contentID) { return contents[index]; } } throw new Exception(string.Format("Content with content ID {0} not found!", contentID)); } public void ChangeChannelTitles(params string[] newTitles) { if (!hasBanner) { return; } ((Headers.IMET)bannerApp.Header).ChangeTitles(newTitles); } public void AddContent(byte[] newContent, int contentID, int index, ContentType type = ContentType.Normal) { tmd.AddContent(new TMD_Content() { ContentID = (uint)contentID, Index = (ushort)index, Type = type, Size = (ulong)newContent.Length, Hash = sha.ComputeHash(newContent) }); contents.Add(newContent); wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36); } public void RemoveContent(int index) { for (int index1 = 0; index1 < tmd.Contents.Length; ++index1) { if (tmd.Contents[index1].Index == index) { tmd.RemoveContent(index); contents.RemoveAt(index1); wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36); return; } } throw new Exception(string.Format("Content with index {0} not found!", index)); } public void RemoveContentByID(int contentID) { for (int index = 0; index < tmd.Contents.Length; ++index) { if (tmd.Contents[index].Index == contentID) { tmd.RemoveContentByID(contentID); contents.RemoveAt(index); wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36); return; } } throw new Exception(string.Format("Content with content ID {0} not found!", contentID)); } public void RemoveAllContents() { if (!hasBanner) { tmd.Contents = new TMD_Content[0]; contents = new List(); wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36); } else { for (int index = 0; index < tmd.NumOfContents; ++index) { if (tmd.Contents[index].Index == 0) { byte[] content1 = contents[index]; TMD_Content content2 = tmd.Contents[index]; tmd.Contents = new TMD_Content[0]; contents = new List(); tmd.AddContent(content2); contents.Add(content1); wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36); break; } } } } public void Unpack(string unpackDir, bool nameContentID = false) { UnpackAll(unpackDir, nameContentID); } public void RemoveFooter() { footer = new byte[0]; wadHeader.FooterSize = 0U; keepOriginalFooter = true; } public void AddFooter(byte[] footer) { ChangeFooter(footer); } public void ChangeFooter(byte[] newFooter) { if (newFooter.Length % 64 != 0) { Array.Resize(ref newFooter, Shared.AddPadding(newFooter.Length)); } footer = newFooter; wadHeader.FooterSize = (uint)newFooter.Length; keepOriginalFooter = true; } private void WriteToStream(Stream writeStream) { FireDebug("Writing Wad..."); if (!keepOriginalFooter) { FireDebug(" Building Footer Timestamp..."); CreateFooterTimestamp(); } if (hasBanner) { if (lz77CompressBannerAndIcon || lz77DecompressBannerAndIcon) { for (int index = 0; index < bannerApp.Nodes.Count; ++index) { if (bannerApp.StringTable[index].ToLower() == "icon.bin" || bannerApp.StringTable[index].ToLower() == "banner.bin") { if (!Lz77.IsLz77Compressed(bannerApp.Data[index]) && lz77CompressBannerAndIcon) { FireDebug(" Compressing {0}...", (object)bannerApp.StringTable[index]); byte[] file = new byte[bannerApp.Data[index].Length - 32]; Array.Copy(bannerApp.Data[index], 32, file, 0, file.Length); byte[] numArray = Headers.IMD5.AddHeader(new Lz77().Compress(file)); bannerApp.Data[index] = numArray; bannerApp.Nodes[index].SizeOfData = (uint)numArray.Length; } else if (Lz77.IsLz77Compressed(bannerApp.Data[index]) && lz77DecompressBannerAndIcon) { FireDebug(" Decompressing {0}...", (object)bannerApp.StringTable[index]); byte[] file = new byte[bannerApp.Data[index].Length - 32]; Array.Copy(bannerApp.Data[index], 32, file, 0, file.Length); byte[] numArray = Headers.IMD5.AddHeader(new Lz77().Decompress(file)); bannerApp.Data[index] = numArray; bannerApp.Nodes[index].SizeOfData = (uint)numArray.Length; } } } } for (int index = 0; index < contents.Count; ++index) { if (tmd.Contents[index].Index == 0) { FireDebug(" Saving Banner App..."); contents[index] = bannerApp.ToByteArray(); break; } } } FireDebug(" Updating Header..."); int num = 0; for (int index = 0; index < contents.Count - 1; ++index) { num += Shared.AddPadding(contents[index].Length); } wadHeader.ContentSize = (uint)(num + contents[contents.Count - 1].Length); wadHeader.TmdSize = (uint)(484 + tmd.NumOfContents * 36); FireDebug(" Updating TMD Contents..."); tmd.UpdateContents(contents.ToArray()); FireDebug(" Writing Wad Header... (Offset: 0x{0})", (object)writeStream.Position.ToString("x8").ToUpper()); writeStream.Seek(0L, SeekOrigin.Begin); wadHeader.Write(writeStream); FireDebug(" Writing Certificate Chain... (Offset: 0x{0})", (object)writeStream.Position.ToString("x8").ToUpper()); writeStream.Seek(Shared.AddPadding((int)writeStream.Position), SeekOrigin.Begin); byte[] byteArray1 = cert.ToByteArray(); writeStream.Write(byteArray1, 0, byteArray1.Length); FireDebug(" Writing Ticket... (Offset: 0x{0})", (object)writeStream.Position.ToString("x8").ToUpper()); writeStream.Seek(Shared.AddPadding((int)writeStream.Position), SeekOrigin.Begin); byte[] byteArray2 = tik.ToByteArray(); writeStream.Write(byteArray2, 0, byteArray2.Length); FireDebug(" Writing TMD... (Offset: 0x{0})", (object)writeStream.Position.ToString("x8").ToUpper()); writeStream.Seek(Shared.AddPadding((int)writeStream.Position), SeekOrigin.Begin); byte[] byteArray3 = tmd.ToByteArray(); writeStream.Write(byteArray3, 0, byteArray3.Length); ContentIndices[] sortedContentList = tmd.GetSortedContentList(); for (int index = 0; index < sortedContentList.Length; ++index) { writeStream.Seek(Shared.AddPadding((int)writeStream.Position), SeekOrigin.Begin); FireProgress((index + 1) * 100 / contents.Count); FireDebug(" Writing Content #{1} of {2}... (Offset: 0x{0})", writeStream.Position.ToString("x8").ToUpper(), index + 1, contents.Count); FireDebug(" -> Content ID: 0x{0}", (object)tmd.Contents[sortedContentList[index].Index].ContentID.ToString("x8")); FireDebug(" -> Index: 0x{0}", (object)tmd.Contents[sortedContentList[index].Index].Index.ToString("x4")); FireDebug(" -> Type: 0x{0} ({1})", ((ushort)tmd.Contents[sortedContentList[index].Index].Type).ToString("x4"), tmd.Contents[sortedContentList[index].Index].Type.ToString()); FireDebug(" -> Size: {0} bytes", (object)tmd.Contents[sortedContentList[index].Index].Size); FireDebug(" -> Hash: {0}", (object)Shared.ByteArrayToString(tmd.Contents[sortedContentList[index].Index].Hash)); byte[] buffer = EncryptContent(contents[sortedContentList[index].Index], sortedContentList[index].Index); writeStream.Write(buffer, 0, buffer.Length); } if (wadHeader.FooterSize != 0U) { FireDebug(" Writing Footer... (Offset: 0x{0})", (object)writeStream.Position.ToString("x8").ToUpper()); writeStream.Seek(Shared.AddPadding((int)writeStream.Position), SeekOrigin.Begin); writeStream.Write(footer, 0, footer.Length); } while (writeStream.Position % 64L != 0L) { writeStream.WriteByte(0); } FireDebug("Writing Wad Finished... (Written Bytes: {0})", (object)writeStream.Position); } private void UnpackAll(string unpackDir, bool nameContentId) { FireDebug("Unpacking Wad to: {0}", (object)unpackDir); if (!Directory.Exists(unpackDir)) { Directory.CreateDirectory(unpackDir); } string str1 = this.tik.TitleID.ToString("x16"); FireDebug(" Saving Certificate Chain: {0}.cert", (object)str1); CertificateChain cert = this.cert; string str2 = unpackDir; char directorySeparatorChar = Path.DirectorySeparatorChar; string str3 = directorySeparatorChar.ToString(); string str4 = str1; string savePath1 = str2 + str3 + str4 + ".cert"; cert.Save(savePath1); FireDebug(" Saving Ticket: {0}.tik", (object)str1); Ticket tik = this.tik; string str5 = unpackDir; directorySeparatorChar = Path.DirectorySeparatorChar; string str6 = directorySeparatorChar.ToString(); string str7 = str1; string savePath2 = str5 + str6 + str7 + ".tik"; tik.Save(savePath2); FireDebug(" Saving TMD: {0}.tmd", (object)str1); TMD tmd = this.tmd; string str8 = unpackDir; directorySeparatorChar = Path.DirectorySeparatorChar; string str9 = directorySeparatorChar.ToString(); string str10 = str1; string savePath3 = str8 + str9 + str10 + ".tmd"; tmd.Save(savePath3); for (int index = 0; index < this.tmd.NumOfContents; ++index) { FireProgress((index + 1) * 100 / this.tmd.NumOfContents); FireDebug(" Saving Content #{0} of {1}: {2}.app", index + 1, this.tmd.NumOfContents, nameContentId ? this.tmd.Contents[index].ContentID.ToString("x8") : this.tmd.Contents[index].Index.ToString("x8")); FireDebug(" -> Content ID: 0x{0}", (object)this.tmd.Contents[index].ContentID.ToString("x8")); FireDebug(" -> Index: 0x{0}", (object)this.tmd.Contents[index].Index.ToString("x4")); object[] objArray = new object[2]; ushort num = (ushort)this.tmd.Contents[index].Type; objArray[0] = num.ToString("x4"); objArray[1] = this.tmd.Contents[index].Type.ToString(); FireDebug(" -> Type: 0x{0} ({1})", objArray); FireDebug(" -> Size: {0} bytes", (object)this.tmd.Contents[index].Size); FireDebug(" -> Hash: {0}", (object)Shared.ByteArrayToString(this.tmd.Contents[index].Hash)); string str11 = unpackDir; directorySeparatorChar = Path.DirectorySeparatorChar; string str12 = directorySeparatorChar.ToString(); string str13; if (!nameContentId) { num = this.tmd.Contents[index].Index; str13 = num.ToString("x8"); } else { str13 = this.tmd.Contents[index].ContentID.ToString("x8"); } using FileStream fileStream = new FileStream(str11 + str12 + str13 + ".app", FileMode.Create); fileStream.Write(contents[index], 0, contents[index].Length); } FireDebug(" Saving Footer: {0}.footer", (object)str1); string str14 = unpackDir; directorySeparatorChar = Path.DirectorySeparatorChar; string str15 = directorySeparatorChar.ToString(); string str16 = str1; using (FileStream fileStream = new FileStream(str14 + str15 + str16 + ".footer", FileMode.Create)) { fileStream.Write(footer, 0, footer.Length); } FireDebug("Unpacking Wad Finished..."); } private void ParseWad(Stream wadFile) { FireDebug("Parsing Wad..."); wadFile.Seek(0L, SeekOrigin.Begin); byte[] buffer = new byte[4]; wadHeader = new WAD_Header(); contents = new List(); FireDebug(" Parsing Header... (Offset: 0x{0})", (object)wadFile.Position.ToString("x8").ToUpper()); wadFile.Read(buffer, 0, 4); if ((int)Shared.Swap(BitConverter.ToUInt32(buffer, 0)) != (int)wadHeader.HeaderSize) { throw new Exception("Invalid Headersize!"); } wadFile.Read(buffer, 0, 4); wadHeader.WadType = Shared.Swap(BitConverter.ToUInt32(buffer, 0)); wadFile.Seek(12L, SeekOrigin.Current); wadFile.Read(buffer, 0, 4); wadHeader.TmdSize = Shared.Swap(BitConverter.ToUInt32(buffer, 0)); wadFile.Read(buffer, 0, 4); wadHeader.ContentSize = Shared.Swap(BitConverter.ToUInt32(buffer, 0)); wadFile.Read(buffer, 0, 4); wadHeader.FooterSize = Shared.Swap(BitConverter.ToUInt32(buffer, 0)); FireDebug(" Parsing Certificate Chain... (Offset: 0x{0})", (object)wadFile.Position.ToString("x8").ToUpper()); wadFile.Seek(Shared.AddPadding((int)wadFile.Position), SeekOrigin.Begin); byte[] numArray1 = new byte[(int)wadHeader.CertSize]; wadFile.Read(numArray1, 0, numArray1.Length); cert.LoadFile(numArray1); FireDebug(" Parsing Ticket... (Offset: 0x{0})", (object)wadFile.Position.ToString("x8").ToUpper()); wadFile.Seek(Shared.AddPadding((int)wadFile.Position), SeekOrigin.Begin); byte[] numArray2 = new byte[(int)wadHeader.TicketSize]; wadFile.Read(numArray2, 0, numArray2.Length); tik.LoadFile(numArray2); FireDebug(" Parsing TMD... (Offset: 0x{0})", (object)wadFile.Position.ToString("x8").ToUpper()); wadFile.Seek(Shared.AddPadding((int)wadFile.Position), SeekOrigin.Begin); byte[] numArray3 = new byte[(int)wadHeader.TmdSize]; wadFile.Read(numArray3, 0, numArray3.Length); tmd.LoadFile(numArray3); if ((long)tmd.TitleID != (long)tik.TitleID) { FireWarning("The Title ID in the Ticket doesn't match the one in the TMD!"); } long position; for (int contentIndex = 0; contentIndex < tmd.NumOfContents; ++contentIndex) { FireProgress((contentIndex + 1) * 100 / tmd.NumOfContents); object[] objArray1 = new object[3] { contentIndex + 1, tmd.NumOfContents, null }; position = wadFile.Position; objArray1[2] = position.ToString("x8").ToUpper(); FireDebug(" Reading Content #{0} of {1}... (Offset: 0x{2})", objArray1); FireDebug(" -> Content ID: 0x{0}", (object)tmd.Contents[contentIndex].ContentID.ToString("x8")); object[] objArray2 = new object[1]; ushort num = tmd.Contents[contentIndex].Index; objArray2[0] = num.ToString("x4"); FireDebug(" -> Index: 0x{0}", objArray2); object[] objArray3 = new object[2]; num = (ushort)tmd.Contents[contentIndex].Type; objArray3[0] = num.ToString("x4"); objArray3[1] = tmd.Contents[contentIndex].Type.ToString(); FireDebug(" -> Type: 0x{0} ({1})", objArray3); FireDebug(" -> Size: {0} bytes", (object)tmd.Contents[contentIndex].Size); FireDebug(" -> Hash: {0}", (object)Shared.ByteArrayToString(tmd.Contents[contentIndex].Hash)); wadFile.Seek(Shared.AddPadding((int)wadFile.Position), SeekOrigin.Begin); byte[] numArray4 = new byte[Shared.AddPadding((int)tmd.Contents[contentIndex].Size, 16)]; wadFile.Read(numArray4, 0, numArray4.Length); byte[] array = DecryptContent(numArray4, contentIndex); Array.Resize(ref array, (int)tmd.Contents[contentIndex].Size); if (!Shared.CompareByteArrays(tmd.Contents[contentIndex].Hash, sha.ComputeHash(array, 0, (int)tmd.Contents[contentIndex].Size))) { FireDebug("/!\\ /!\\ /!\\ Hashes do not match /!\\ /!\\ /!\\"); // ISSUE: variable of a boxed type int local = contentIndex + 1; string str1 = tmd.Contents[contentIndex].ContentID.ToString("x8"); num = tmd.Contents[contentIndex].Index; string str2 = num.ToString("x4"); FireWarning(string.Format("Content #{0} (Content ID: 0x{1}; Index: 0x{2}): Hashes do not match! The content might be corrupted!", local, str1, str2)); } contents.Add(array); if (tmd.Contents[contentIndex].Index == 0) { try { bannerApp.LoadFile(array); hasBanner = true; } catch { hasBanner = false; } } } if (wadHeader.FooterSize != 0U) { object[] objArray = new object[1]; position = wadFile.Position; objArray[0] = position.ToString("x8").ToUpper(); FireDebug(" Reading Footer... (Offset: 0x{0})", objArray); footer = new byte[(int)wadHeader.FooterSize]; wadFile.Seek(Shared.AddPadding((int)wadFile.Position), SeekOrigin.Begin); wadFile.Read(footer, 0, footer.Length); ParseFooterTimestamp(); } FireDebug("Parsing Wad Finished..."); } private byte[] DecryptContent(byte[] content, int contentIndex) { int length = content.Length; Array.Resize(ref content, Shared.AddPadding(content.Length, 16)); byte[] titleKey = tik.TitleKey; byte[] numArray = new byte[16]; byte[] bytes = BitConverter.GetBytes(tmd.Contents[contentIndex].Index); numArray[0] = bytes[1]; numArray[1] = bytes[0]; #pragma warning disable SYSLIB0022 // Type or member is obsolete RijndaelManaged rijndaelManaged = new RijndaelManaged { Mode = CipherMode.CBC, Padding = PaddingMode.None, KeySize = 128, BlockSize = 128, Key = titleKey, IV = numArray }; #pragma warning restore SYSLIB0022 // Type or member is obsolete ICryptoTransform decryptor = rijndaelManaged.CreateDecryptor(); MemoryStream memoryStream = new MemoryStream(content); CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read); byte[] buffer = new byte[length]; cryptoStream.Read(buffer, 0, buffer.Length); cryptoStream.Dispose(); memoryStream.Dispose(); return buffer; } private byte[] EncryptContent(byte[] content, int contentIndex) { Array.Resize(ref content, Shared.AddPadding(content.Length, 16)); byte[] titleKey = tik.TitleKey; byte[] numArray = new byte[16]; byte[] bytes = BitConverter.GetBytes(tmd.Contents[contentIndex].Index); numArray[0] = bytes[1]; numArray[1] = bytes[0]; #pragma warning disable SYSLIB0022 // Type or member is obsolete RijndaelManaged rijndaelManaged = new RijndaelManaged { Mode = CipherMode.CBC, Padding = PaddingMode.None, KeySize = 128, BlockSize = 128, Key = titleKey, IV = numArray }; #pragma warning restore SYSLIB0022 // Type or member is obsolete ICryptoTransform encryptor = rijndaelManaged.CreateEncryptor(); MemoryStream memoryStream = new MemoryStream(content); CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Read); byte[] buffer = new byte[content.Length]; cryptoStream.Read(buffer, 0, buffer.Length); cryptoStream.Dispose(); memoryStream.Dispose(); return buffer; } private void CreateFooterTimestamp() { byte[] bytes = new ASCIIEncoding().GetBytes("TmStmp" + ((int)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds).ToString()); Array.Resize(ref bytes, 64); wadHeader.FooterSize = (uint)bytes.Length; footer = bytes; } private void ParseFooterTimestamp() { creationTimeUTC = new DateTime(1970, 1, 1); if ((footer[0] != 67 || footer[1] != 77 || (footer[2] != 105 || footer[3] != 105) || (footer[4] != 85 || footer[5] != 84)) && (footer[0] != 84 || footer[1] != 109 || (footer[2] != 83 || footer[3] != 116) || (footer[4] != 109 || footer[5] != 112))) { return; } string s = new ASCIIEncoding().GetString(footer, 6, 10); int num = 0; ref int local = ref num; if (!int.TryParse(s, out local)) { return; } creationTimeUTC = creationTimeUTC.AddSeconds(num); } private void FireDebug(string debugMessage, params object[] args) { EventHandler debug = Debug; if (debug == null) { return; } debug(new object(), new MessageEventArgs(string.Format(debugMessage, args))); } private void FireWarning(string warningMessage) { EventHandler warning = Warning; if (warning == null) { return; } warning(new object(), new MessageEventArgs(warningMessage)); } private void FireProgress(int progressPercentage) { EventHandler progress = Progress; if (progress == null) { return; } progress(new object(), new ProgressChangedEventArgs(progressPercentage, string.Empty)); } private void Cert_Debug(object sender, MessageEventArgs e) { FireDebug(" Certificate Chain: {0}", (object)e.Message); } private void Tik_Debug(object sender, MessageEventArgs e) { FireDebug(" Ticket: {0}", (object)e.Message); } private void Tmd_Debug(object sender, MessageEventArgs e) { FireDebug(" TMD: {0}", (object)e.Message); } private void BannerApp_Debug(object sender, MessageEventArgs e) { FireDebug(" BannerApp: {0}", (object)e.Message); } private void BannerApp_Warning(object sender, MessageEventArgs e) { FireWarning(e.Message); } } public class WAD_Header { private readonly uint headerSize = 0x20; private uint wadType = 0x49730000; private readonly uint certSize = 0xA00; private readonly uint reserved = 0x00; private readonly uint tikSize = 0x2A4; private uint tmdSize; private uint contentSize; private uint footerSize = 0x00; public uint HeaderSize => headerSize; public uint WadType { get => wadType; set => wadType = value; } public uint CertSize => certSize; public uint Reserved => reserved; public uint TicketSize => tikSize; public uint TmdSize { get => tmdSize; set => tmdSize = value; } public uint ContentSize { get => contentSize; set => contentSize = value; } public uint FooterSize { get => footerSize; set => footerSize = value; } public void Write(Stream writeStream) { writeStream.Seek(0, SeekOrigin.Begin); writeStream.Write(BitConverter.GetBytes(Shared.Swap(headerSize)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(wadType)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(certSize)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(reserved)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(tikSize)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(tmdSize)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(contentSize)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(footerSize)), 0, 4); } } }