/* This file is part of ShowMiiWads
 * Copyright (C) 2009 Leathl
 * 
 * ShowMiiWads 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.
 *
 * ShowMiiWads 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 <http://www.gnu.org/licenses/>.
 */

//Wii.py by Xuzz, SquidMan, megazig, Matt_P, Omega and The Lemon Man was the base for TPL conversion
//Zetsubou by SquidMan was a reference for TPL conversion
//gbalzss by Andre Perrot was the base for LZ77 (de-)compression
//Thanks to the authors!

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;

namespace Wii
{
    public class Tools
    {
        public static event EventHandler<ProgressChangedEventArgs> ProgressChanged;

        public static void ChangeProgress(int ProgressPercent)
        {
            EventHandler<ProgressChangedEventArgs> progressChanged = ProgressChanged;
            if (progressChanged != null)
            {
                progressChanged(new object(), new ProgressChangedEventArgs(ProgressPercent));
            }
        }

        /// <summary>
        /// Writes the small Byte Array into the big one at the given offset
        /// </summary>
        /// <param name="big"></param>
        /// <param name="small"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        public static byte[] InsertByteArray(byte[] big, byte[] small, int offset)
        {
            for (int i = 0; i < small.Length; i++)
                big[offset + i] = small[i];
            return big;
        }

        /// <summary>
        /// Returns the current UTC Unix Timestamp as a Byte Array
        /// </summary>
        /// <returns></returns>
        public static byte[] GetTimestamp()
        {
            DateTime dtNow = DateTime.UtcNow;
            TimeSpan tsTimestamp = (dtNow - new DateTime(1970, 1, 1, 0, 0, 0));

            int timestamp = (int)tsTimestamp.TotalSeconds;
            ASCIIEncoding enc = new ASCIIEncoding();
            byte[] timestampBytes = enc.GetBytes("CMiiUT" + timestamp.ToString());
            return timestampBytes;
        }

        /// <summary>
        /// Creates a new Byte Array out of the given one
        /// from the given offset with the specified length
        /// </summary>
        /// <param name="array"></param>
        /// <param name="offset"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static byte[] GetPartOfByteArray(byte[] array, int offset, int length)
        {
            byte[] ret = new byte[length];
            for (int i = 0; i < length; i++)
                ret[i] = array[offset + i];
            return ret;
        }

        /// <summary>
        /// Converts UInt32 Array into Byte Array
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public static byte[] UInt32ArrayToByteArray(UInt32[] array)
        {
            List<byte> results = new List<byte>();
            foreach (UInt32 value in array)
            {
                byte[] converted = BitConverter.GetBytes(value);
                results.AddRange(converted);
            }
            return results.ToArray();
        }

        /// <summary>
        /// Converts UInt16 Array into Byte Array
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public static byte[] UInt16ArrayToByteArray(UInt16[] array)
        {
            List<byte> results = new List<byte>();
            foreach (UInt16 value in array)
            {
                byte[] converted = BitConverter.GetBytes(value);
                results.AddRange(converted);
            }
            return results.ToArray();
        }

        /// <summary>
        /// Converts UInt16 Array into Byte Array
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public static byte[] UIntArrayToByteArray(uint[] array)
        {
            List<byte> results = new List<byte>();
            foreach (uint value in array)
            {
                byte[] converted = BitConverter.GetBytes(value);
                results.AddRange(converted);
            }
            return results.ToArray();
        }

        /// <summary>
        /// Converts Byte Array into UInt16 Array
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public static UInt32[] ByteArrayToUInt32Array(byte[] array)
        {
            UInt32[] converted = new UInt32[array.Length / 2];
            int j = 0;
            for (int i = 0; i < array.Length; i += 4)
            {
                converted[j] = BitConverter.ToUInt32(array, i);
                j++;
            }
            return converted;
        }

        /// <summary>
        /// Converts Byte Array into UInt16 Array
        /// </summary>
        /// <param name="array"></param>
        /// <returns></returns>
        public static UInt16[] ByteArrayToUInt16Array(byte[] array)
        {
            UInt16[] converted = new UInt16[array.Length / 2];
            int j = 0;
            for (int i = 0; i < array.Length; i += 2)
            {
                converted[j] = BitConverter.ToUInt16(array, i);
                j++;
            }
            return converted;
        }

        /// <summary>
        /// Returns the file length as a Byte Array
        /// </summary>
        /// <param name="filelength"></param>
        /// <returns></returns>
        public static byte[] FileLengthToByteArray(int filelength)
        {
            byte[] length = BitConverter.GetBytes(filelength);
            Array.Reverse(length);
            return length;
        }

        /// <summary>
        /// Adds a padding to the next 64 bytes, if necessary
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        public static int AddPadding(int value)
        {
            return AddPadding(value, 64);
        }

        /// <summary>
        /// Adds a padding to the given value, if necessary
        /// </summary>
        /// <param name="value"></param>
        /// <param name="padding"></param>
        /// <returns></returns>
        public static int AddPadding(int value, int padding)
        {
            if (value % padding != 0)
            {
                value = value + (padding - (value % padding));
            }

            return value;
        }

        /// <summary>
        /// Converts a Hex-String to Int
        /// </summary>
        /// <param name="hexstring"></param>
        /// <returns></returns>
        public static int HexStringToInt(string hexstring)
        {
            try { return int.Parse(hexstring, System.Globalization.NumberStyles.HexNumber); }
            catch { throw new Exception("An Error occured, maybe the Wad file is corrupt!"); }
        }

        /// <summary>
        /// Converts a Hex-String to Long
        /// </summary>
        /// <param name="hexstring"></param>
        /// <returns></returns>
        public static long HexStringToLong(string hexstring)
        {
            try { return long.Parse(hexstring, System.Globalization.NumberStyles.HexNumber); }
            catch { throw new Exception("An Error occured, maybe the Wad file is corrupt!"); }
        }

        /// <summary>
        /// Writes a Byte Array to a file
        /// </summary>
        /// <param name="file"></param>
        public static void SaveFileFromByteArray(byte[] file, string destination)
        {
            using (FileStream fs = new FileStream(destination, FileMode.Create))
                fs.Write(file, 0, file.Length);
        }

        /// <summary>
        /// Loads a file into a Byte Array
        /// </summary>
        /// <param name="sourcefile"></param>
        /// <returns></returns>
        public static byte[] LoadFileToByteArray(string sourcefile)
        {
            if (File.Exists(sourcefile))
            {
                using (FileStream fs = new FileStream(sourcefile, FileMode.Open))
                {
                    byte[] filearray = new byte[fs.Length];
                    fs.Read(filearray, 0, filearray.Length);
                    return filearray;
                }
            }
            else throw new FileNotFoundException("File couldn't be found:\r\n" + sourcefile);
        }

        /// <summary>
        /// Loads a file into a Byte Array
        /// </summary>
        /// <param name="sourcefile"></param>
        /// <returns></returns>
        public static byte[] LoadFileToByteArray(string sourcefile, int offset, int length)
        {
            if (File.Exists(sourcefile))
            {
                using (FileStream fs = new FileStream(sourcefile, FileMode.Open))
                {
                    if (fs.Length < length) length = (int)fs.Length;
                    byte[] filearray = new byte[length];
                    fs.Seek(offset, SeekOrigin.Begin);
                    fs.Read(filearray, 0, length);
                    return filearray;
                }
            }
            else throw new FileNotFoundException("File couldn't be found:\r\n" + sourcefile);
        }

        /// <summary>
        /// Checks the SHA1 of the Common-Key
        /// </summary>
        /// <param name="pathtocommonkey"></param>
        /// <returns></returns>
        public static bool CheckCommonKey(string pathtocommonkey)
        {
            byte[] sum = new byte[] { 0xEB, 0xEA, 0xE6, 0xD2, 0x76, 0x2D, 0x4D, 0x3E, 0xA1, 0x60, 0xA6, 0xD8, 0x32, 0x7F, 0xAC, 0x9A, 0x25, 0xF8, 0x06, 0x2B };
            
            FileInfo fi = new FileInfo(pathtocommonkey);
            if (fi.Length != 16) return false;
            else
            {
                byte[] ckey = LoadFileToByteArray(pathtocommonkey);

                SHA1Managed sha1 = new SHA1Managed();
                byte[] newsum = sha1.ComputeHash(ckey);

                if (CompareByteArrays(sum, newsum) == true) return true;
                else return false;
            }
        }

        /// <summary>
        /// Creates the Common Key
        /// </summary>
        /// <param name="fat">Must be "45e"</param>
        /// <param name="destination">Destination Path</param>
        public static void CreateCommonKey(string fat, string destinationpath)
        {
            //What an effort, lol
            byte[] encryptedwater = new byte[] { 0x4d, 0x89, 0x21, 0x34, 0x62, 0x81, 0xe4, 0x02, 0x37, 0x36, 0xc4, 0xb4, 0xde, 0x40, 0x32, 0xab };
            byte[] key = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, byte.Parse(fat.Remove(2), System.Globalization.NumberStyles.HexNumber), byte.Parse(fat.Remove(0, 2) + "0", System.Globalization.NumberStyles.HexNumber) };
            byte[] decryptedwater = new byte[10];

            RijndaelManaged decryptkey = new RijndaelManaged();
            decryptkey.Mode = CipherMode.CBC;
            decryptkey.Padding = PaddingMode.None;
            decryptkey.KeySize = 128;
            decryptkey.BlockSize = 128;
            decryptkey.Key = key;
            Array.Reverse(key);
            decryptkey.IV = key;

            ICryptoTransform cryptor = decryptkey.CreateDecryptor();

            using (MemoryStream memory = new MemoryStream(encryptedwater))
            {
                using (CryptoStream crypto = new CryptoStream(memory, cryptor, CryptoStreamMode.Read))
                    crypto.Read(decryptedwater, 0, 10);
            }

            string water = BitConverter.ToString(decryptedwater).Replace("-", "").ToLower() + " ";

            water = water.Insert(0, fat[2].ToString());
            water = water.Insert(2, fat[2].ToString());
            water = water.Insert(7, fat[2].ToString());
            water = water.Insert(11, fat[2].ToString());

            water = water.Insert(7, fat[1].ToString());
            water = water.Insert(10, fat[1].ToString());
            water = water.Insert(18, fat[1].ToString());
            water = water.Insert(19, fat[1].ToString());

            water = water.Insert(3, fat[0].ToString());
            water = water.Insert(15, fat[0].ToString());
            water = water.Insert(16, fat[0].ToString());
            water = water.Insert(22, fat[0].ToString());

            byte[] cheese = new byte[16];
            int count = -1;

            for (int i = 0; i < 32; i += 2)
                cheese[++count] = byte.Parse(water.Remove(0, i).Remove(2), System.Globalization.NumberStyles.HexNumber);

            if (destinationpath[destinationpath.Length - 1] != '\\') destinationpath = destinationpath + "\\";
            using (FileStream keystream = new FileStream(destinationpath + "\\common-key.bin", FileMode.Create))
            {
                keystream.Write(cheese, 0, cheese.Length);
            }
        }

        /// <summary>
        /// Counts the appearance of a specific character in a string
        /// </summary>
        /// <param name="theString"></param>
        /// <param name="theChar"></param>
        /// <returns></returns>
        public static int CountCharsInString(string theString, char theChar)
        {
            int count = 0;
            foreach (char thisChar in theString)
            {
                if (thisChar == theChar)
                    count++;
            }
            return count;
        }

        /// <summary>
        /// Compares two Byte Arrays and returns true, if they match
        /// </summary>
        /// <param name="first"></param>
        /// <param name="second"></param>
        /// <returns></returns>
        public static bool CompareByteArrays(byte[] first, byte[] second)
        {
            if (first.Length != second.Length) return false;
            else
            {
                for (int i = 0; i < first.Length; i++)
                    if (first[i] != second[i]) return false;

                return true;
            }
        }

        /// <summary>
        /// Converts a Hex String to a Byte Array
        /// </summary>
        /// <param name="hexstring"></param>
        /// <returns></returns>
        public static byte[] HexStringToByteArray(string hexstring)
        {
            byte[] ba = new byte[hexstring.Length / 2];

            for (int i = 0; i < hexstring.Length / 2; i++)
            {
                ba[i] = byte.Parse(hexstring.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
            }

            return ba;
        }
        
        /// <summary>
        /// Checks, if the given string does exist in the string Array
        /// </summary>
        /// <param name="theString"></param>
        /// <param name="theStringArray"></param>
        /// <returns></returns>
        public static bool StringExistsInStringArray(string theString, string[] theStringArray)
        {
            return Array.Exists(theStringArray, thisString => thisString == theString);
        }

        /// <summary>
        /// Copies an entire Directoy
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        public static void CopyDirectory(string source, string destination)
        {
            string[] subdirs = Directory.GetDirectories(source);
            string[] files = Directory.GetFiles(source);

            foreach (string thisFile in files)
            {
                if (!Directory.Exists(destination)) Directory.CreateDirectory(destination);
                if (File.Exists(destination + "\\" + Path.GetFileName(thisFile))) File.Delete(destination + "\\" + Path.GetFileName(thisFile));
                File.Copy(thisFile, destination + "\\" + Path.GetFileName(thisFile));
            }

            foreach (string thisDir in subdirs)
            {
                CopyDirectory(thisDir, destination + "\\" + thisDir.Remove(0, thisDir.LastIndexOf('\\') + 1));
            }
        }
    }

    public class WadInfo
    {
        public const int Headersize = 64;
        public static string[] RegionCode = new string[4] { "Japan", "USA", "Europe", "Region Free" };

        /// <summary>
        /// Returns the Header of a Wadfile
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static byte[] GetHeader(byte[] wadfile)
        {
            byte[] Header = new byte[0x20];

            for (int i = 0; i < Header.Length; i++)
            {
                Header[i] = wadfile[i];
            }

            return Header;
        }

        /// <summary>
        /// Returns the size of the Certificate
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetCertSize(byte[] wadfile)
        {
            int size = int.Parse(wadfile[0x08].ToString("x2") + wadfile[0x09].ToString("x2") + wadfile[0x0a].ToString("x2") + wadfile[0x0b].ToString("x2"), System.Globalization.NumberStyles.HexNumber);
            return size;
        }

        /// <summary>
        /// Returns the size of the Ticket
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetTikSize(byte[] wadfile)
        {
            int size = int.Parse(wadfile[0x10].ToString("x2") + wadfile[0x11].ToString("x2") + wadfile[0x12].ToString("x2") + wadfile[0x13].ToString("x2"), System.Globalization.NumberStyles.HexNumber);
            return size;
        }

        /// <summary>
        /// Returns the size of the TMD
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetTmdSize(byte[] wadfile)
        {
            int size = int.Parse(wadfile[0x14].ToString("x2") + wadfile[0x15].ToString("x2") + wadfile[0x16].ToString("x2") + wadfile[0x17].ToString("x2"), System.Globalization.NumberStyles.HexNumber);
            return size;
        }

        /// <summary>
        /// Returns the size of all Contents
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetContentSize(byte[] wadfile)
        {
            int size = int.Parse(wadfile[0x18].ToString("x2") + wadfile[0x19].ToString("x2") + wadfile[0x1a].ToString("x2") + wadfile[0x1b].ToString("x2"), System.Globalization.NumberStyles.HexNumber);
            return size;
        }

        /// <summary>
        /// Returns the size of the Footer
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetFooterSize(byte[] wadfile)
        {
            int size = int.Parse(wadfile[0x1c].ToString("x2") + wadfile[0x1d].ToString("x2") + wadfile[0x1e].ToString("x2") + wadfile[0x1f].ToString("x2"), System.Globalization.NumberStyles.HexNumber);
            return size;
        }

        /// <summary>
        /// Returns the position of the tmd in the wad file
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetTmdPos(byte[] wadfile)
        {
            return Headersize + Tools.AddPadding(GetCertSize(wadfile)) + Tools.AddPadding(GetTikSize(wadfile));
        }

        /// <summary>
        /// Returns the position of the ticket in the wad file, ticket or tmd
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetTikPos(byte[] wadfile)
        {
            return Headersize + Tools.AddPadding(GetCertSize(wadfile));
        }

        /// <summary>
        /// Returns the title ID of the wad file.
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="type">0 = Tik, 1 = Tmd</param>
        /// <returns></returns>
        public static string GetTitleID(string wadtiktmd, int type)
        {
            byte[] temp = Tools.LoadFileToByteArray(wadtiktmd);
            return GetTitleID(temp, type);
        }

        /// <summary>
        /// Returns the title ID of the wad file.
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="type">0 = Tik, 1 = Tmd</param>
        /// <returns></returns>
        public static string GetTitleID(byte[] wadtiktmd, int type)
        {
            string channeltype = GetChannelType(wadtiktmd, type);
            int tikpos = 0;
            int tmdpos = 0;

            if (IsThisWad(wadtiktmd) == true)
            {
                //It's a wad
                tikpos = GetTikPos(wadtiktmd);
                tmdpos = GetTmdPos(wadtiktmd);
            }

            if (type == 1)
            {
                if (!channeltype.Contains("System:"))
                {
                    string tmdid = Convert.ToChar(wadtiktmd[tmdpos + 0x190]).ToString() + Convert.ToChar(wadtiktmd[tmdpos + 0x191]).ToString() + Convert.ToChar(wadtiktmd[tmdpos + 0x192]).ToString() + Convert.ToChar(wadtiktmd[tmdpos + 0x193]).ToString();
                    return tmdid;
                }
                else if (channeltype.Contains("IOS"))
                {
                    int tmdid = Tools.HexStringToInt(wadtiktmd[tmdpos + 0x190].ToString("x2") + wadtiktmd[tmdpos + 0x191].ToString("x2") + wadtiktmd[tmdpos + 0x192].ToString("x2") + wadtiktmd[tmdpos + 0x193].ToString("x2"));               
                    return "IOS" + tmdid;
                }
                else if (channeltype.Contains("System")) return "SYSTEM";
                else return "";
            }
            else
            {
                if (!channeltype.Contains("System:"))
                {
                    string tikid = Convert.ToChar(wadtiktmd[tikpos + 0x1e0]).ToString() + Convert.ToChar(wadtiktmd[tikpos + 0x1e1]).ToString() + Convert.ToChar(wadtiktmd[tikpos + 0x1e2]).ToString() + Convert.ToChar(wadtiktmd[tikpos + 0x1e3]).ToString();
                    return tikid;
                }
                else if (channeltype.Contains("IOS"))
                {
                    int tikid = Tools.HexStringToInt(wadtiktmd[tikpos + 0x1e0].ToString("x2") + wadtiktmd[tikpos + 0x1e1].ToString("x2") + wadtiktmd[tikpos + 0x1e2].ToString("x2") + wadtiktmd[tikpos + 0x1e3].ToString("x2"));
                    return "IOS" + tikid;
                }
                else if (channeltype.Contains("System")) return "SYSTEM";
                else return "";
            }
        }

        /// <summary>
        /// Returns the full title ID of the wad file as a hex string.
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="type">0 = Tik, 1 = Tmd</param>
        /// <returns></returns>
        public static string GetFullTitleID(byte[] wadtiktmd, int type)
        {
            int tikpos = 0;
            int tmdpos = 0;

            if (IsThisWad(wadtiktmd) == true)
            {
                //It's a wad
                tikpos = GetTikPos(wadtiktmd);
                tmdpos = GetTmdPos(wadtiktmd);
            }

            if (type == 1)
            {
                string tmdid = wadtiktmd[tmdpos + 0x18c].ToString("x2") +
                    wadtiktmd[tmdpos + 0x18d].ToString("x2") +
                    wadtiktmd[tmdpos + 0x18e].ToString("x2") +
                    wadtiktmd[tmdpos + 0x18f].ToString("x2") +
                    wadtiktmd[tmdpos + 0x190].ToString("x2") +
                    wadtiktmd[tmdpos + 0x191].ToString("x2") +
                    wadtiktmd[tmdpos + 0x192].ToString("x2") +
                    wadtiktmd[tmdpos + 0x193].ToString("x2");
                return tmdid;
            }
            else
            {
                string tikid = wadtiktmd[tikpos + 0x1dc].ToString() +
                    wadtiktmd[tikpos + 0x1dd].ToString() +
                    wadtiktmd[tikpos + 0x1de].ToString() +
                    wadtiktmd[tikpos + 0x1df].ToString() +
                    wadtiktmd[tikpos + 0x1e0].ToString() +
                    wadtiktmd[tikpos + 0x1e1].ToString() +
                    wadtiktmd[tikpos + 0x1e2].ToString() +
                    wadtiktmd[tikpos + 0x1e3].ToString();
                return tikid;
            }
        }

        /// <summary>
        /// Returns the title for each language of a wad file.
        /// Order: Jap, Eng, Ger, Fra, Spa, Ita, Dut
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static string[] GetChannelTitles(string wadfile)
        {
            byte[] wadarray = Tools.LoadFileToByteArray(wadfile);
            return GetChannelTitles(wadarray);
        }

        /// <summary>
        /// Returns the title for each language of a wad file.
        /// Order: Jap, Eng, Ger, Fra, Spa, Ita, Dut
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static string[] GetChannelTitles(byte[] wadfile)
        {
            if (File.Exists(System.Windows.Forms.Application.StartupPath + "\\common-key.bin") || File.Exists(System.Windows.Forms.Application.StartupPath + "\\key.bin"))
            {
                string channeltype = GetChannelType(wadfile, 0);

                if (!channeltype.Contains("System:"))
                {
                    if (!channeltype.Contains("Hidden"))
                    {
                        string[] titles = new string[7];

                        //Detection from footer is turned off, cause the footer
                        //can be easily edited and thus the titles in it could be simply wrong

                        //int footer = GetFooterSize(wadfile);
                        //if (footer > 0)
                        //{
                        //    int footerpos = wadfile.Length - footer;
                        //    int count = 0;
                        //    int imetpos = 0;

                        //    if ((wadfile.Length - (wadfile.Length - footer)) < 250) return new string[7];

                        //    for (int z = 0; z < 250; z++)
                        //    {
                        //        if (Convert.ToChar(wadfile[footerpos + z]) == 'I')
                        //            if (Convert.ToChar(wadfile[footerpos + z + 1]) == 'M')
                        //                if (Convert.ToChar(wadfile[footerpos + z + 2]) == 'E')
                        //                    if (Convert.ToChar(wadfile[footerpos + z + 3]) == 'T')
                        //                    {
                        //                        imetpos = footerpos + z;
                        //                        break;
                        //                    }
                        //    }

                        //    int jappos = imetpos + 29;

                        //    for (int i = jappos; i < jappos + 588; i += 84)
                        //    {
                        //        for (int j = 0; j < 40; j += 2)
                        //        {
                        //            if (wadfile[i + j] != 0x00)
                        //            {
                        //                char temp = Convert.ToChar(wadfile[i + j]);
                        //                titles[count] += temp;
                        //            }
                        //        }

                        //        count++;
                        //    }

                        //    return titles;
                        //}

                        string[,] conts = GetContentInfo(wadfile);
                        byte[] titlekey = GetTitleKey(wadfile);
                        int nullapp = 0;

                        for (int i = 0; i < conts.GetLength(0); i++)
                        {
                            if (conts[i, 1] == "00000000")
                                nullapp = i;
                        }

                        byte[] contenthandle = WadEdit.DecryptContent(wadfile, nullapp, titlekey);
                        int imetpos = 0;

                        if (contenthandle.Length < 400) return new string[7];

                        for (int z = 0; z < 400; z++)
                        {
                            if (Convert.ToChar(contenthandle[z]) == 'I')
                                if (Convert.ToChar(contenthandle[z + 1]) == 'M')
                                    if (Convert.ToChar(contenthandle[z + 2]) == 'E')
                                        if (Convert.ToChar(contenthandle[z + 3]) == 'T')
                                        {
                                            imetpos = z;
                                            break;
                                        }
                        }

                        int jappos = imetpos + 29;
                        int count = 0;

                        for (int i = jappos; i < jappos + 588; i += 84)
                        {
                            for (int j = 0; j < 40; j += 2)
                            {
                                if (contenthandle[i + j] != 0x00)
                                {
                                    char temp = BitConverter.ToChar(new byte[] { contenthandle[i + j], contenthandle[i + j - 1] }, 0);
                                    titles[count] += temp;
                                }
                            }

                            count++;
                        }

                        return titles;
                    }
                    else return new string[7];
                }
                else return new string[7];
            }
            else return new string[7];
        }

        /// <summary>
        /// Returns the title for each language of a 00.app file
        /// Order: Jap, Eng, Ger, Fra, Spa, Ita, Dut
        /// </summary>
        /// <param name="app"></param>
        /// <returns></returns>
        public static string[] GetChannelTitlesFromApp(string app)
        {
            byte[] tmp = Tools.LoadFileToByteArray(app);
            return GetChannelTitlesFromApp(tmp);
        }

        /// <summary>
        /// Returns the title for each language of a 00.app file
        /// Order: Jap, Eng, Ger, Fra, Spa, Ita, Dut
        /// </summary>
        /// <param name="app"></param>
        /// <returns></returns>
        public static string[] GetChannelTitlesFromApp(byte[] app)
        {
            string[] titles = new string[7];

            int imetpos = 0;
            int length = 400;

            if (app.Length < 400) length = app.Length - 4;

            for (int z = 0; z < length; z++)
            {
                if (Convert.ToChar(app[z]) == 'I')
                    if (Convert.ToChar(app[z + 1]) == 'M')
                        if (Convert.ToChar(app[z + 2]) == 'E')
                            if (Convert.ToChar(app[z + 3]) == 'T')
                            {
                                imetpos = z;
                                break;
                            }
            }

            if (imetpos != 0)
            {
                int jappos = imetpos + 29;
                int count = 0;

                for (int i = jappos; i < jappos + 588; i += 84)
                {
                    for (int j = 0; j < 40; j += 2)
                    {
                        if (app[i + j] != 0x00)
                        {
                            char temp = BitConverter.ToChar(new byte[] { app[i + j], app[i + j - 1] }, 0);
                            titles[count] += temp;
                        }
                    }

                    count++;
                }
            }

            return titles;
        }

        /// <summary>
        /// Returns the Type of the Channel as a string
        /// Wad or Tik needed for WiiWare / VC detection!
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static string GetChannelType(byte[] wadtiktmd, int type)
        {
            int tikpos = 0;
            int tmdpos = 0;

            if (IsThisWad(wadtiktmd) == true)
            {
                //It's a wad
                tikpos = GetTikPos(wadtiktmd);
                tmdpos = GetTmdPos(wadtiktmd);
            }

            string thistype = "";

            if (type == 0)
            { thistype = wadtiktmd[tikpos + 0x1dc].ToString("x2") + wadtiktmd[tikpos + 0x1dd].ToString("x2") + wadtiktmd[tikpos + 0x1de].ToString("x2") + wadtiktmd[tikpos + 0x1df].ToString("x2"); }
            else { thistype = wadtiktmd[tmdpos + 0x18c].ToString("x2") + wadtiktmd[tmdpos + 0x18d].ToString("x2") + wadtiktmd[tmdpos + 0x18e].ToString("x2") + wadtiktmd[tmdpos + 0x18f].ToString("x2"); }
            string channeltype = "Unknown";

            if (thistype == "00010001")
            {
                channeltype = CheckWiiWareVC(wadtiktmd, type);
            }
            else if (thistype == "00010002") channeltype = "System Channel";
            else if (thistype == "00010004" || thistype == "00010000") channeltype = "Game Channel";
            else if (thistype == "00010005") channeltype = "Downloaded Content";
            else if (thistype == "00010008") channeltype = "Hidden Channel";
            else if (thistype == "00000001")
            {
                channeltype = "System: IOS";

                string thisid = "";
                if (type == 0) { thisid = wadtiktmd[tikpos + 0x1e0].ToString("x2") + wadtiktmd[tikpos + 0x1e1].ToString("x2") + wadtiktmd[tikpos + 0x1e2].ToString("x2") + wadtiktmd[tikpos + 0x1e3].ToString("x2"); }
                else { thisid = wadtiktmd[tmdpos + 0x190].ToString("x2") + wadtiktmd[tmdpos + 0x191].ToString("x2") + wadtiktmd[tmdpos + 0x192].ToString("x2") + wadtiktmd[tmdpos + 0x193].ToString("x2"); }

                if (thisid == "00000001") channeltype = "System: Boot2";
                else if (thisid == "00000002") channeltype = "System: Menu";
                else if (thisid == "00000100") channeltype = "System: BC";
                else if (thisid == "00000101") channeltype = "System: MIOS";
            }

            return channeltype;
        }

        /// <summary>
        /// Returns the amount of included Contents (app-files)
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetContentNum(byte[] wadtmd)
        {
            int tmdpos = 0;

            if (IsThisWad(wadtmd) == true)
            {
                //It's a wad file, so get the tmd position
                tmdpos = GetTmdPos(wadtmd);
            }

            int contents = Tools.HexStringToInt(wadtmd[tmdpos + 0x1de].ToString("x2") + wadtmd[tmdpos + 0x1df].ToString("x2"));

            return contents;
        }

        /// <summary>
        /// Returns the boot index specified in the tmd
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetBootIndex(byte[] wadtmd)
        {
            int tmdpos = 0;

            if (IsThisWad(wadtmd))
                tmdpos = GetTmdPos(wadtmd);

            int bootIndex = Tools.HexStringToInt(wadtmd[tmdpos + 0x1e0].ToString("x2") + wadtmd[tmdpos + 0x1e1].ToString("x2"));

            return bootIndex;
        }

        /// <summary>
        /// Returns the approx. destination size on the Wii
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static string GetNandSize(byte[] wadtmd, bool ConvertToMB)
        {
            int tmdpos = 0;
            int minsize = 0;
            int maxsize = 0;
            int numcont = GetContentNum(wadtmd);

            if (IsThisWad(wadtmd) == true)
            {
                //It's a wad
                tmdpos = GetTmdPos(wadtmd);
            }

            for (int i = 0; i < numcont; i++)
            {
                int cont = 36 * i;
                int contentsize = Tools.HexStringToInt(wadtmd[tmdpos + 0x1e4 + 8 + cont].ToString("x2") +
                    wadtmd[tmdpos + 0x1e5 + 8 + cont].ToString("x2") +
                    wadtmd[tmdpos + 0x1e6 + 8 + cont].ToString("x2") +
                    wadtmd[tmdpos + 0x1e7 + 8 + cont].ToString("x2") +
                    wadtmd[tmdpos + 0x1e8 + 8 + cont].ToString("x2") +
                    wadtmd[tmdpos + 0x1e9 + 8 + cont].ToString("x2") +
                    wadtmd[tmdpos + 0x1ea + 8 + cont].ToString("x2") +
                    wadtmd[tmdpos + 0x1eb + 8 + cont].ToString("x2"));

                string type = wadtmd[tmdpos + 0x1e4 + 6 + cont].ToString("x2") + wadtmd[tmdpos + 0x1e5 + 6 + cont].ToString("x2");

                if (type == "0001")
                {
                    minsize += contentsize;
                    maxsize += contentsize;
                }
                else if (type == "8001")
                    maxsize += contentsize;
            }

            string size = "";

            if (maxsize == minsize) size = maxsize.ToString();
            else size = minsize.ToString() + " - " + maxsize.ToString();

            if (ConvertToMB == true)
            {
                if (size.Contains("-"))
                {
                    string min = size.Remove(size.IndexOf(' '));
                    string max = size.Remove(0, size.IndexOf('-') + 2);

                    min = Convert.ToString(Math.Round(Convert.ToDouble(min) * 0.0009765625 * 0.0009765625, 2));
                    max = Convert.ToString(Math.Round(Convert.ToDouble(max) * 0.0009765625 * 0.0009765625, 2));
                    if (min.Length > 4) { min = min.Remove(4); }
                    if (max.Length > 4) { max = max.Remove(4); }
                    size = min + " - " + max + " MB";
                }
                else
                {
                    size = Convert.ToString(Math.Round(Convert.ToDouble(size) * 0.0009765625 * 0.0009765625, 2));
                    if (size.Length > 4) { size = size.Remove(4); }
                    size = size + " MB";
                }
            }

            return size.Replace(",", ".");
        }

        /// <summary>
        /// Returns the approx. destination block on the Wii
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static string GetNandBlocks(string wadtmd)
        {
            using (FileStream fs = new FileStream(wadtmd, FileMode.Open))
            {
                byte[] temp = new byte[fs.Length];
                fs.Read(temp, 0, temp.Length);
                return GetNandBlocks(temp);
            }
        }

        /// <summary>
        /// Returns the approx. destination block on the Wii
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static string GetNandBlocks(byte[] wadtmd)
        {
            string size = GetNandSize(wadtmd, false);

            if (size.Contains("-"))
            {
                string size1 = size.Remove(size.IndexOf(' '));
                string size2 = size.Remove(0, size.LastIndexOf(' ') + 1);

                double blocks1 = (double)((Convert.ToDouble(size1) / 1024) / 128);
                double blocks2 = (double)((Convert.ToDouble(size2) / 1024) / 128);

                return Math.Ceiling(blocks1) + " - " + Math.Ceiling(blocks2);
            }
            else
            {
                double blocks = (double)((Convert.ToDouble(size) / 1024) / 128);

                return Math.Ceiling(blocks).ToString();
            }
        }

        /// <summary>
        /// Returns the title version of the wad file
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetTitleVersion(string wadtmd)
        {
            byte[] temp = Tools.LoadFileToByteArray(wadtmd, 0, 10000);
            return GetTitleVersion(temp);
        }

        /// <summary>
        /// Returns the title version of the wad file
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static int GetTitleVersion(byte[] wadtmd)
        {
            int tmdpos = 0;

            if (IsThisWad(wadtmd) == true) { tmdpos = GetTmdPos(wadtmd); }
            return Tools.HexStringToInt(wadtmd[tmdpos + 0x1dc].ToString("x2") + wadtmd[tmdpos + 0x1dd].ToString("x2"));
        }

        /// <summary>
        /// Returns the IOS that is needed by the wad file
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static string GetIosFlag(byte[] wadtmd)
        {
            string type = GetChannelType(wadtmd, 1);

            if (!type.Contains("IOS") && !type.Contains("BC"))
            {
                int tmdpos = 0;
                if (IsThisWad(wadtmd) == true) { tmdpos = GetTmdPos(wadtmd); }
                return "IOS" + Tools.HexStringToInt(wadtmd[tmdpos + 0x188].ToString("x2") + wadtmd[tmdpos + 0x189].ToString("x2") + wadtmd[tmdpos + 0x18a].ToString("x2") + wadtmd[tmdpos + 0x18b].ToString("x2"));
            }
            else return "";
        }

        /// <summary>
        /// Returns the region of the wad file
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static string GetRegionFlag(byte[] wadtmd)
        {
            int tmdpos = 0;
            string channeltype = GetChannelType(wadtmd, 1);

            if (IsThisWad(wadtmd) == true) { tmdpos = GetTmdPos(wadtmd); }

            if (!channeltype.Contains("System:"))
            {
                int region = Tools.HexStringToInt(wadtmd[tmdpos + 0x19d].ToString("x2"));
                return RegionCode[region];
            }
            else return "";
        }

        /// <summary>
        /// Returns the Path where the wad will be installed on the Wii
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static string GetNandPath(string wadfile)
        {
            byte[] wad = Tools.LoadFileToByteArray(wadfile);
            return GetNandPath(wad, 0);
        }

        /// <summary>
        /// Returns the Path where the wad will be installed on the Wii
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="type">0 = Tik, 1 = Tmd</param>
        /// <returns></returns>
        public static string GetNandPath(byte[] wadtiktmd, int type)
        {
            int tikpos = 0;
            int tmdpos = 0;

            if (IsThisWad(wadtiktmd) == true)
            {
                tikpos = GetTikPos(wadtiktmd);
                tmdpos = GetTmdPos(wadtiktmd);
            }

            string thispath = "";

            if (type == 0)
            {
                thispath = wadtiktmd[tikpos + 0x1dc].ToString("x2") +
                    wadtiktmd[tikpos + 0x1dd].ToString("x2") +
                    wadtiktmd[tikpos + 0x1de].ToString("x2") +
                    wadtiktmd[tikpos + 0x1df].ToString("x2") +
                    wadtiktmd[tikpos + 0x1e0].ToString("x2") +
                    wadtiktmd[tikpos + 0x1e1].ToString("x2") +
                    wadtiktmd[tikpos + 0x1e2].ToString("x2") +
                    wadtiktmd[tikpos + 0x1e3].ToString("x2");
            }
            else
            {
                thispath = wadtiktmd[tmdpos + 0x18c].ToString("x2") +
                    wadtiktmd[tmdpos + 0x18d].ToString("x2") +
                    wadtiktmd[tmdpos + 0x18e].ToString("x2") +
                    wadtiktmd[tmdpos + 0x18f].ToString("x2") +
                    wadtiktmd[tmdpos + 0x190].ToString("x2") +
                    wadtiktmd[tmdpos + 0x191].ToString("x2") +
                    wadtiktmd[tmdpos + 0x192].ToString("x2") +
                    wadtiktmd[tmdpos + 0x193].ToString("x2");
            }

            thispath = thispath.Insert(8, "\\");
            return thispath;
        }

        /// <summary>
        /// Returns true, if the wad file is a WiiWare / VC title.
        /// </summary>
        /// <param name="wadtiktmd"></param>
        /// <param name="type">0 = Tik, 1 = Tmd</param>
        /// <returns></returns>
        public static string CheckWiiWareVC(byte[] wadtiktmd, int type)
        {
            int tiktmdpos = 0;
            int offset = 0x221;
            int idoffset = 0x1e0;

            if (type == 1) { offset = 0x197; idoffset = 0x190; }
            if (IsThisWad(wadtiktmd) == true)
            {
                if (type == 1) tiktmdpos = GetTmdPos(wadtiktmd);
                else tiktmdpos = GetTikPos(wadtiktmd);
            }

            if (wadtiktmd[tiktmdpos + offset] == 0x01)
            {
                char idchar = Convert.ToChar(wadtiktmd[tiktmdpos + idoffset]);
                char idchar2 = Convert.ToChar(wadtiktmd[tiktmdpos + idoffset + 1]);

                if (idchar == 'H') return "System Channel";
                else if (idchar == 'W') return "WiiWare";
                else
                {
                    if (idchar == 'C') return "C64";
                    else if (idchar == 'E' && idchar2 == 'A') return "NeoGeo";
                    else if (idchar == 'E') return "VC - Arcade";
                    else if (idchar == 'F') return "NES";
                    else if (idchar == 'J') return "SNES";
                    else if (idchar == 'L') return "Sega Master System";
                    else if (idchar == 'M') return "Sega Genesis";
                    else if (idchar == 'N') return "Nintendo 64";
                    else if (idchar == 'P') return "Turbografx";
                    else if (idchar == 'Q') return "Turbografx CD";
                    else return "Channel Title";
                }
            }
            else return "Channel Title";
        }

        /// <summary>
        /// Returns all information stored in the tmd for all contents in the wad file.
        /// [x, 0] = Content ID, [x, 1] = Index, [x, 2] = Type, [x, 3] = Size, [x, 4] = Sha1
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static string[,] GetContentInfo(byte[] wadtmd)
        {
            int tmdpos = 0;

            if (IsThisWad(wadtmd) == true) { tmdpos = GetTmdPos(wadtmd); }
            int contentcount = GetContentNum(wadtmd);
            string[,] contentinfo = new string[contentcount, 5];

            for (int i = 0; i < contentcount; i++)
            {
                contentinfo[i, 0] = wadtmd[tmdpos + 0x1e4 + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1e5 + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1e6 + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1e7 + (36 * i)].ToString("x2");
                contentinfo[i, 1] = "0000" +
                    wadtmd[tmdpos + 0x1e8 + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1e9 + (36 * i)].ToString("x2");
                contentinfo[i, 2] = wadtmd[tmdpos + 0x1ea + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1eb + (36 * i)].ToString("x2");
                contentinfo[i, 3] = Tools.HexStringToInt(
                    wadtmd[tmdpos + 0x1ec + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1ed + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1ee + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1ef + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1f0 + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1f1 + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1f2 + (36 * i)].ToString("x2") +
                    wadtmd[tmdpos + 0x1f3 + (36 * i)].ToString("x2")).ToString();

                for (int j = 0; j < 20; j++)
                {
                    contentinfo[i, 4] += wadtmd[tmdpos + 0x1f4 + (36 * i) + j].ToString("x2");
                }
            }

            return contentinfo;
        }

        /// <summary>
        /// Returns the Tik of the wad file as a Byte-Array
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static byte[] ReturnTik(byte[] wadfile)
        {
            int tikpos = GetTikPos(wadfile);
            int tiksize = GetTikSize(wadfile);

            byte[] tik = new byte[tiksize];

            for (int i = 0; i < tiksize; i++)
            {
                tik[i] = wadfile[tikpos + i];
            }

            return tik;
        }

        /// <summary>
        /// Returns the Tmd of the wad file as a Byte-Array
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static byte[] ReturnTmd(byte[] wadfile)
        {
            int tmdpos = GetTmdPos(wadfile);
            int tmdsize = GetTmdSize(wadfile);

            byte[] tmd = new byte[tmdsize];

            for (int i = 0; i < tmdsize; i++)
            {
                tmd[i] = wadfile[tmdpos + i];
            }

            return tmd;
        }

        /// <summary>
        /// Checks, if the given file is a wad
        /// </summary>
        /// <param name="wadtiktmd"></param>
        /// <returns></returns>
        public static bool IsThisWad(byte[] wadtiktmd)
        {
            if (wadtiktmd[0] == 0x00 &&
                wadtiktmd[1] == 0x00 &&
                wadtiktmd[2] == 0x00 &&
                wadtiktmd[3] == 0x20 &&
                wadtiktmd[4] == 0x49 &&
                wadtiktmd[5] == 0x73)
            { return true; }

            return false;
        }

        /// <summary>
        /// Returns the decrypted TitleKey
        /// </summary>
        /// <param name="wadtik"></param>
        /// <returns></returns>
        public static byte[] GetTitleKey(byte[] wadtik)
        {
            byte[] commonkey = new byte[16];

            if (File.Exists(System.Windows.Forms.Application.StartupPath + "\\common-key.bin"))
            { commonkey = Tools.LoadFileToByteArray(System.Windows.Forms.Application.StartupPath + "\\common-key.bin"); }
            else if (File.Exists(System.Windows.Forms.Application.StartupPath + "\\key.bin"))
            { commonkey = Tools.LoadFileToByteArray(System.Windows.Forms.Application.StartupPath + "\\key.bin"); }
            else { throw new FileNotFoundException("The (common-)key.bin must be in the application directory!"); }

            byte[] encryptedkey = new byte[16];
            byte[] iv = new byte[16];
            int tikpos = 0;

            if (IsThisWad(wadtik) == true)
            {
                //It's a wad file, so get the tik position
                tikpos = GetTikPos(wadtik);
            }

            for (int i = 0; i < 16; i++)
            {
                encryptedkey[i] = wadtik[tikpos + 0x1bf + i];
            }

            for (int j = 0; j < 8; j++)
            {
                iv[j] = wadtik[tikpos + 0x1dc + j];
                iv[j + 8] = 0x00;
            }

            RijndaelManaged decrypt = new RijndaelManaged();
            decrypt.Mode = CipherMode.CBC;
            decrypt.Padding = PaddingMode.None;
            decrypt.KeySize = 128;
            decrypt.BlockSize = 128;
            decrypt.Key = commonkey;
            decrypt.IV = iv;

            ICryptoTransform cryptor = decrypt.CreateDecryptor();

            MemoryStream memory = new MemoryStream(encryptedkey);
            CryptoStream crypto = new CryptoStream(memory, cryptor, CryptoStreamMode.Read);

            byte[] decryptedkey = new byte[16];
            crypto.Read(decryptedkey, 0, decryptedkey.Length);

            crypto.Close();
            memory.Close();

            return decryptedkey;
        }

        /// <summary>
        /// Decodes the Timestamp in the Trailer, if available.
        /// Returns null if no Timestamp was found.
        /// </summary>
        /// <param name="trailer"></param>
        /// <returns></returns>
        public static DateTime GetCreationTime(string trailer)
        {
            byte[] bTrailer = Tools.LoadFileToByteArray(trailer);
            return GetCreationTime(bTrailer);
        }

        /// <summary>
        /// Decodes the Timestamp in the Trailer, if available.
        /// Returns null if no Timestamp was found.
        /// </summary>
        /// <param name="trailer"></param>
        /// <returns></returns>
        public static DateTime GetCreationTime(byte[] trailer)
        {
            DateTime result = new DateTime(1970, 1, 1);

            if (trailer[0] == 'C' &&
                trailer[1] == 'M' &&
                trailer[2] == 'i' &&
                trailer[3] == 'i' &&
                trailer[4] == 'U' &&
                trailer[5] == 'T')
            {
                ASCIIEncoding enc = new ASCIIEncoding();
                string stringSeconds = enc.GetString(trailer, 6, 10);
                int seconds = 0;

                if (int.TryParse(stringSeconds, out seconds))
                {
                    result = result.AddSeconds((double)seconds);
                    return result;
                }
                else return result;
            }

            return result;
        }
    }

    public class WadEdit
    {
        /// <summary>
        /// Changes the region of the wad file
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="region">0 = JAP, 1 = USA, 2 = EUR, 3 = FREE</param>
        /// <returns></returns>
        public static byte[] ChangeRegion(byte[] wadfile, int region)
        {

            int tmdpos = WadInfo.GetTmdPos(wadfile);

            if (region == 0) wadfile[tmdpos + 0x19d] = 0x00;
            else if (region == 1) wadfile[tmdpos + 0x19d] = 0x01;
            else if (region == 2) wadfile[tmdpos + 0x19d] = 0x02;
            else wadfile[tmdpos + 0x19d] = 0x03;

            wadfile = TruchaSign(wadfile, 1);

            return wadfile;
        }

        /// <summary>
        /// Changes the region of the wad file
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="region"></param>
        public static void ChangeRegion(string wadfile, int region)
        {
            byte[] wadarray = Tools.LoadFileToByteArray(wadfile);
            wadarray = ChangeRegion(wadarray, region);

            using (FileStream fs = new FileStream(wadfile, FileMode.Open, FileAccess.Write))
            {
                fs.Seek(0, SeekOrigin.Begin);
                fs.Write(wadarray, 0, wadarray.Length);
            }
        }

        /// <summary>
        /// Changes the Channel Title of the wad file
        /// All languages have the same title
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        public static byte[] ChangeChannelTitle(byte[] wadfile, string title)
        {
            return ChangeChannelTitle(wadfile, title, title, title, title, title, title, title);
        }

        /// <summary>
        /// Changes the Channel Title of the wad file
        /// Each language has a specific title
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="jap"></param>
        /// <param name="eng"></param>
        /// <param name="ger"></param>
        /// <param name="fra"></param>
        /// <param name="spa"></param>
        /// <param name="ita"></param>
        /// <param name="dut"></param>
        public static void ChangeChannelTitle(string wadfile, string jap, string eng, string ger, string fra, string spa, string ita, string dut)
        {
            byte[] wadarray = Tools.LoadFileToByteArray(wadfile);
            wadarray = ChangeChannelTitle(wadarray, jap, eng, ger, fra, spa, ita, dut);

            using (FileStream fs = new FileStream(wadfile, FileMode.Open, FileAccess.Write))
            {
                fs.Seek(0, SeekOrigin.Begin);
                fs.Write(wadarray, 0, wadarray.Length);
            }
        }

        /// <summary>
        /// Changes the Channel Title of the wad file
        /// Each language has a specific title
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="jap">Japanese Title</param>
        /// <param name="eng">English Title</param>
        /// <param name="ger">German Title</param>
        /// <param name="fra">French Title</param>
        /// <param name="spa">Spanish Title</param>
        /// <param name="ita">Italian Title</param>
        /// <param name="dut">Dutch Title</param>
        /// <returns></returns>
        public static byte[] ChangeChannelTitle(byte[] wadfile, string jap, string eng, string ger, string fra, string spa, string ita, string dut)
        {
            Tools.ChangeProgress(0);

            char[] japchars = jap.ToCharArray();
            char[] engchars = eng.ToCharArray();
            char[] gerchars = ger.ToCharArray();
            char[] frachars = fra.ToCharArray();
            char[] spachars = spa.ToCharArray();
            char[] itachars = ita.ToCharArray();
            char[] dutchars = dut.ToCharArray();

            byte[] titlekey = WadInfo.GetTitleKey(wadfile);
            string[,] conts = WadInfo.GetContentInfo(wadfile);
            int tmdpos = WadInfo.GetTmdPos(wadfile);
            int tmdsize = WadInfo.GetTmdSize(wadfile);
            int nullapp = 0;
            int contentpos = 64 + Tools.AddPadding(WadInfo.GetCertSize(wadfile)) + Tools.AddPadding(WadInfo.GetTikSize(wadfile)) + Tools.AddPadding(WadInfo.GetTmdSize(wadfile));
            SHA1Managed sha1 = new SHA1Managed();

            Tools.ChangeProgress(10);

            for (int i = 0; i < conts.GetLength(0); i++)
            {
                if (conts[i, 1] == "00000000")
                {
                    nullapp = i;
                    break;
                }
                else
                    contentpos += Tools.AddPadding(Convert.ToInt32(conts[i, 3]));
            }

            byte[] contenthandle = DecryptContent(wadfile, nullapp, titlekey);

            Tools.ChangeProgress(25);

            int imetpos = 0;

            for (int z = 0; z < 400; z++)
            {
                if (Convert.ToChar(contenthandle[z]) == 'I')
                    if (Convert.ToChar(contenthandle[z + 1]) == 'M')
                        if (Convert.ToChar(contenthandle[z + 2]) == 'E')
                            if (Convert.ToChar(contenthandle[z + 3]) == 'T')
                            {
                                imetpos = z;
                                break;
                            }
            }

            Tools.ChangeProgress(40);

            int count = 0;

            for (int x = imetpos; x < imetpos + 40; x += 2)
            {
                if (japchars.Length > count)
                {
                    contenthandle[x + 29] = BitConverter.GetBytes(japchars[count])[0];
                    contenthandle[x + 30] = BitConverter.GetBytes(japchars[count])[1];
                }
                else { contenthandle[x + 29] = 0x00; }
                if (engchars.Length > count)
                {
                    contenthandle[x + 29 + 84] = BitConverter.GetBytes(engchars[count])[0];
                    contenthandle[x + 29 + 84 - 1] = BitConverter.GetBytes(engchars[count])[1];
                }
                else { contenthandle[x + 29 + 84] = 0x00; }
                if (gerchars.Length > count)
                {
                    contenthandle[x + 29 + 84 * 2] = BitConverter.GetBytes(gerchars[count])[0];
                    contenthandle[x + 29 + 84 * 2 - 1] = BitConverter.GetBytes(gerchars[count])[1];
                }
                else { contenthandle[x + 29 + 84 * 2] = 0x00; }
                if (frachars.Length > count)
                {
                    contenthandle[x + 29 + 84 * 3] = BitConverter.GetBytes(frachars[count])[0];
                    contenthandle[x + 29 + 84 * 3 - 1] = BitConverter.GetBytes(frachars[count])[1];
                }
                else { contenthandle[x + 29 + 84 * 3] = 0x00; }
                if (spachars.Length > count)
                {
                    contenthandle[x + 29 + 84 * 4] = BitConverter.GetBytes(spachars[count])[0];
                    contenthandle[x + 29 + 84 * 4 - 1] = BitConverter.GetBytes(spachars[count])[1];
                }
                else { contenthandle[x + 29 + 84 * 4] = 0x00; }
                if (itachars.Length > count)
                {
                    contenthandle[x + 29 + 84 * 5] = BitConverter.GetBytes(itachars[count])[0];
                    contenthandle[x + 29 + 84 * 5 - 1] = BitConverter.GetBytes(itachars[count])[1];
                }
                else { contenthandle[x + 29 + 84 * 5] = 0x00; }
                if (dutchars.Length > count)
                {
                    contenthandle[x + 29 + 84 * 6] = BitConverter.GetBytes(dutchars[count])[0];
                    contenthandle[x + 29 + 84 * 6 - 1] = BitConverter.GetBytes(dutchars[count])[1];
                }
                else { contenthandle[x + 29 + 84 * 6] = 0x00; }

                count++;
            }

            Tools.ChangeProgress(50);

            byte[] newmd5 = new byte[16];
            contenthandle = FixMD5InImet(contenthandle, out newmd5);
            byte[] newsha = sha1.ComputeHash(contenthandle);

            contenthandle = EncryptContent(contenthandle, WadInfo.ReturnTmd(wadfile), nullapp, titlekey, false);

            Tools.ChangeProgress(70);

            for (int y = 0; y < contenthandle.Length; y++)
            {
                wadfile[contentpos + y] = contenthandle[y];
            }

            //SHA1 in TMD
            byte[] tmd = Tools.GetPartOfByteArray(wadfile, tmdpos, tmdsize);
            for (int i = 0; i < 20; i++)
                tmd[0x1f4 + (36 * nullapp) + i] = newsha[i];
            TruchaSign(tmd, 1);
            wadfile = Tools.InsertByteArray(wadfile, tmd, tmdpos);

            int footer = WadInfo.GetFooterSize(wadfile);

            Tools.ChangeProgress(80);

            if (footer > 0)
            {
                int footerpos = wadfile.Length - footer;
                int imetposfoot = 0;

                for (int z = 0; z < 200; z++)
                {
                    if (Convert.ToChar(wadfile[footerpos + z]) == 'I')
                        if (Convert.ToChar(wadfile[footerpos + z + 1]) == 'M')
                            if (Convert.ToChar(wadfile[footerpos + z + 2]) == 'E')
                                if (Convert.ToChar(wadfile[footerpos + z + 3]) == 'T')
                                {
                                    imetposfoot = footerpos + z;
                                    break;
                                }
                }

                Tools.ChangeProgress(90);

                int count2 = 0;

                for (int x = imetposfoot; x < imetposfoot + 40; x += 2)
                {
                    if (japchars.Length > count2) { wadfile[x + 29] = Convert.ToByte(japchars[count2]); }
                    else { wadfile[x + 29] = 0x00; }
                    if (engchars.Length > count2) { wadfile[x + 29 + 84] = Convert.ToByte(engchars[count2]); }
                    else { wadfile[x + 29 + 84] = 0x00; }
                    if (gerchars.Length > count2) { wadfile[x + 29 + 84 * 2] = Convert.ToByte(gerchars[count2]); }
                    else { wadfile[x + 29 + 84 * 2] = 0x00; }
                    if (frachars.Length > count2) { wadfile[x + 29 + 84 * 3] = Convert.ToByte(frachars[count2]); }
                    else { wadfile[x + 29 + 84 * 3] = 0x00; }
                    if (spachars.Length > count2) { wadfile[x + 29 + 84 * 4] = Convert.ToByte(spachars[count2]); }
                    else { wadfile[x + 29 + 84 * 4] = 0x00; }
                    if (itachars.Length > count2) { wadfile[x + 29 + 84 * 5] = Convert.ToByte(itachars[count2]); }
                    else { wadfile[x + 29 + 84 * 5] = 0x00; }
                    if (dutchars.Length > count2) { wadfile[x + 29 + 84 * 6] = Convert.ToByte(dutchars[count2]); }
                    else { wadfile[x + 29 + 84 * 6] = 0x00; }

                    count2++;
                }

                for (int i = 0; i < 16; i++)
                    wadfile[imetposfoot + 1456 + i] = newmd5[i];
            }

            Tools.ChangeProgress(100);
            return wadfile;
        }

        /// <summary>
        /// Changes the Title ID in the Tik or Tmd file
        /// </summary>
        /// <param name="tiktmd"></param>
        /// <param name="type">0 = Tik, 1 = Tmd</param>
        /// <returns></returns>
        public static void ChangeTitleID(string tiktmdfile, int type, string titleid)
        {
            byte[] temp = Tools.LoadFileToByteArray(tiktmdfile);
            temp = ChangeTitleID(temp, type, titleid);
            Tools.SaveFileFromByteArray(temp, tiktmdfile);
        }

        /// <summary>
        /// Changes the Title ID in the Tik or Tmd file
        /// </summary>
        /// <param name="tiktmd"></param>
        /// <param name="type">0 = Tik, 1 = Tmd</param>
        /// <returns></returns>
        public static byte[] ChangeTitleID(byte[] tiktmd, int type, string titleid)
        {
            int offset = 0x1e0;
            if (type == 1) offset = 0x190;
            char[] id = titleid.ToCharArray();

            tiktmd[offset] = (byte)id[0];
            tiktmd[offset + 1] = (byte)id[1];
            tiktmd[offset + 2] = (byte)id[2];
            tiktmd[offset + 3] = (byte)id[3];

            tiktmd = TruchaSign(tiktmd, type);

            return tiktmd;
        }

        /// <summary>
        /// Changes the title ID of the wad file
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="titleid"></param>
        /// <returns></returns>
        public static byte[] ChangeTitleID(byte[] wadfile, string titleid)
        {
            Tools.ChangeProgress(0);

            int tikpos = WadInfo.GetTikPos(wadfile);
            int tmdpos = WadInfo.GetTmdPos(wadfile);
            char[] id = titleid.ToCharArray();

            byte[] oldtitlekey = WadInfo.GetTitleKey(wadfile);

            Tools.ChangeProgress(20);

            //Change the ID in the ticket
            wadfile[tikpos + 0x1e0] = (byte)id[0];
            wadfile[tikpos + 0x1e1] = (byte)id[1];
            wadfile[tikpos + 0x1e2] = (byte)id[2];
            wadfile[tikpos + 0x1e3] = (byte)id[3];

            //Change the ID in the tmd
            wadfile[tmdpos + 0x190] = (byte)id[0];
            wadfile[tmdpos + 0x191] = (byte)id[1];
            wadfile[tmdpos + 0x192] = (byte)id[2];
            wadfile[tmdpos + 0x193] = (byte)id[3];

            Tools.ChangeProgress(40);

            //Trucha-Sign both
            wadfile = TruchaSign(wadfile, 0);

            Tools.ChangeProgress(50);

            wadfile = TruchaSign(wadfile, 1);

            Tools.ChangeProgress(60);

            byte[] newtitlekey = WadInfo.GetTitleKey(wadfile);
            byte[] tmd = WadInfo.ReturnTmd(wadfile);

            int contentcount = WadInfo.GetContentNum(wadfile);

            wadfile = ReEncryptAllContents(wadfile, oldtitlekey, newtitlekey);

            Tools.ChangeProgress(100);
            return wadfile;
        }

        /// <summary>
        /// Changes the title ID of the wad file
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="titleid"></param>
        public static void ChangeTitleID(string wadfile, string titleid)
        {
            byte[] wadarray = Tools.LoadFileToByteArray(wadfile);
            wadarray = ChangeTitleID(wadarray, titleid);

            using (FileStream fs = new FileStream(wadfile, FileMode.Open, FileAccess.Write))
            {
                fs.Seek(0, SeekOrigin.Begin);
                fs.Write(wadarray, 0, wadarray.Length);
            }
        }

        /// <summary>
        /// Clears the Signature of the Tik / Tmd to 0x00
        /// </summary>
        /// <param name="wadtiktmd">Wad, Tik or Tmd</param>
        /// <param name="type">0 = Tik, 1 = Tmd</param>
        /// <returns></returns>
        public static byte[] ClearSignature(byte[] wadtiktmd, int type)
        {
            int tmdtikpos = 0;
            int tmdtiksize = wadtiktmd.Length; ;

            if (WadInfo.IsThisWad(wadtiktmd) == true)
            {
                //It's a wad file, so get the tik or tmd position and length
                switch (type)
                {
                    case 1:
                        tmdtikpos = WadInfo.GetTmdPos(wadtiktmd);
                        tmdtiksize = WadInfo.GetTmdSize(wadtiktmd);
                        break;
                    default:
                        tmdtikpos = WadInfo.GetTikPos(wadtiktmd);
                        tmdtiksize = WadInfo.GetTikSize(wadtiktmd);
                        break;
                }
            }

            for (int i = 4; i < 260; i++)
            {
                wadtiktmd[tmdtikpos + i] = 0x00;
            }

            return wadtiktmd;
        }

        /// <summary>
        /// Trucha-Signs the Tik or Tmd
        /// </summary>
        /// <param name="file">Wad or Tik or Tmd</param>
        /// <param name="type">0 = Tik, 1 = Tmd</param>
        /// <returns></returns>
        public static void TruchaSign(string file, int type)
        {
            byte[] temp = Tools.LoadFileToByteArray(file);
            temp = TruchaSign(temp, type);
            Tools.SaveFileFromByteArray(temp, file);
        }

        /// <summary>
        /// Trucha-Signs the Tik or Tmd
        /// </summary>
        /// <param name="wadortmd">Wad or Tik or Tmd</param>
        /// <param name="type">0 = Tik, 1 = Tmd</param>
        /// <returns></returns>
        public static byte[] TruchaSign(byte[] wadtiktmd, int type)
        {
            SHA1Managed sha = new SHA1Managed();
            int[] position = new int[2] { 0x1f1, 0x1d4 }; //0x104 0x1c1
            int[] tosign = new int[2] { 0x140, 0x140 }; //0x104 0x140
            int tiktmdpos = 0;
            int tiktmdsize = wadtiktmd.Length;

            if (sha.ComputeHash(wadtiktmd, tiktmdpos + tosign[type], tiktmdsize - tosign[type])[0] != 0x00)
            {
                ClearSignature(wadtiktmd, type);

                if (WadInfo.IsThisWad(wadtiktmd) == true)
                {
                    //It's a wad file
                    if (type == 0) //Get Tik position and size
                    {
                        tiktmdpos = WadInfo.GetTikPos(wadtiktmd);
                        tiktmdsize = WadInfo.GetTikSize(wadtiktmd);
                    }
                    else //Get Tmd position and size
                    {
                        tiktmdpos = WadInfo.GetTmdPos(wadtiktmd);
                        tiktmdsize = WadInfo.GetTmdSize(wadtiktmd);
                    }
                }

                byte[] sha1 = new byte[20];

                for (UInt16 i = 0; i < 65535; i++)
                {
                    byte[] hex = BitConverter.GetBytes(i);
                    wadtiktmd[tiktmdpos + position[type]] = hex[0];
                    wadtiktmd[tiktmdpos + position[type] + 1] = hex[1];

                    sha1 = sha.ComputeHash(wadtiktmd, tiktmdpos + tosign[type], tiktmdsize - tosign[type]);
                    if (sha1[0] == 0x00) break;
                }

                return wadtiktmd;
            }
            else return wadtiktmd;
        }

        /// <summary>
        /// Decrypts the given content
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        public static byte[] DecryptContent(byte[] wadfile, int contentcount, byte[] titlekey)
        {
            int tmdpos = WadInfo.GetTmdPos(wadfile);
            byte[] iv = new byte[16];
            string[,] continfo = WadInfo.GetContentInfo(wadfile);
            int contentsize = Convert.ToInt32(continfo[contentcount, 3]);
            int paddedsize = Tools.AddPadding(contentsize, 16);

            int contentpos = 64 + Tools.AddPadding(WadInfo.GetCertSize(wadfile)) + Tools.AddPadding(WadInfo.GetTikSize(wadfile)) + Tools.AddPadding(WadInfo.GetTmdSize(wadfile));

            for (int x = 0; x < contentcount; x++)
            {
                contentpos += Tools.AddPadding(Convert.ToInt32(continfo[x, 3]));
            }

            iv[0] = wadfile[tmdpos + 0x1e8 + (0x24 * contentcount)];
            iv[1] = wadfile[tmdpos + 0x1e9 + (0x24 * contentcount)];

            RijndaelManaged decrypt = new RijndaelManaged();
            decrypt.Mode = CipherMode.CBC;
            decrypt.Padding = PaddingMode.None;
            decrypt.KeySize = 128;
            decrypt.BlockSize = 128;
            decrypt.Key = titlekey;
            decrypt.IV = iv;

            ICryptoTransform cryptor = decrypt.CreateDecryptor();

            MemoryStream memory = new MemoryStream(wadfile, contentpos, paddedsize);
            CryptoStream crypto = new CryptoStream(memory, cryptor, CryptoStreamMode.Read);

            bool fullread = false;
            byte[] buffer = new byte[16384];
            byte[] cont = new byte[1];

            using (MemoryStream ms = new MemoryStream())
            {
                while (fullread == false)
                {
                    int len = 0;
                    if ((len = crypto.Read(buffer, 0, buffer.Length)) <= 0)
                    {
                        fullread = true;
                        cont = ms.ToArray();
                    }
                    ms.Write(buffer, 0, len);
                }
            }

            memory.Close();
            crypto.Close();

            Array.Resize(ref cont, contentsize);

            return cont;
        }

        /// <summary>
        /// Decrypts the given content
        /// </summary>
        /// <param name="content"></param>
        /// <param name="tmd"></param>
        /// <param name="contentcount"></param>
        /// <param name="titlekey"></param>
        /// <returns></returns>
        public static byte[] DecryptContent(byte[] content, byte[] tmd, int contentcount, byte[] titlekey)
        {
            byte[] iv = new byte[16];
            string[,] continfo = WadInfo.GetContentInfo(tmd);
            int contentsize = content.Length;
            int paddedsize = Tools.AddPadding(contentsize, 16);
            Array.Resize(ref content, paddedsize);

            iv[0] = tmd[0x1e8 + (0x24 * contentcount)];
            iv[1] = tmd[0x1e9 + (0x24 * contentcount)];

            RijndaelManaged decrypt = new RijndaelManaged();
            decrypt.Mode = CipherMode.CBC;
            decrypt.Padding = PaddingMode.None;
            decrypt.KeySize = 128;
            decrypt.BlockSize = 128;
            decrypt.Key = titlekey;
            decrypt.IV = iv;

            ICryptoTransform cryptor = decrypt.CreateDecryptor();

            MemoryStream memory = new MemoryStream(content, 0, paddedsize);
            CryptoStream crypto = new CryptoStream(memory, cryptor, CryptoStreamMode.Read);

            bool fullread = false;
            byte[] buffer = new byte[memory.Length];
            byte[] cont = new byte[1];

            using (MemoryStream ms = new MemoryStream())
            {
                while (fullread == false)
                {
                    int len = 0;
                    if ((len = crypto.Read(buffer, 0, buffer.Length)) <= 0)
                    {
                        fullread = true;
                        cont = ms.ToArray();
                    }
                    ms.Write(buffer, 0, len);
                }
            }

            memory.Close();
            crypto.Close();

            return cont;
        }

        /// <summary>
        /// Encrypts the given content and adds a padding to the next 64 bytes
        /// </summary>
        /// <param name="content"></param>
        /// <param name="tmd"></param>
        /// <param name="contentcount"></param>
        /// <param name="titlekey"></param>
        /// <returns></returns>
        public static byte[] EncryptContent(byte[] content, byte[] tmd, int contentcount, byte[] titlekey, bool addpadding)
        {
            byte[] iv = new byte[16];
            string[,] continfo = WadInfo.GetContentInfo(tmd);
            int contentsize = content.Length;
            int paddedsize = Tools.AddPadding(contentsize, 16);
            Array.Resize(ref content, paddedsize);

            iv[0] = tmd[0x1e8 + (0x24 * contentcount)];
            iv[1] = tmd[0x1e9 + (0x24 * contentcount)];

            RijndaelManaged encrypt = new RijndaelManaged();
            encrypt.Mode = CipherMode.CBC;
            encrypt.Padding = PaddingMode.None;
            encrypt.KeySize = 128;
            encrypt.BlockSize = 128;
            encrypt.Key = titlekey;
            encrypt.IV = iv;

            ICryptoTransform cryptor = encrypt.CreateEncryptor();

            MemoryStream memory = new MemoryStream(content, 0, paddedsize);
            CryptoStream crypto = new CryptoStream(memory, cryptor, CryptoStreamMode.Read);

            bool fullread = false;
            byte[] buffer = new byte[memory.Length];
            byte[] cont = new byte[1];

            using (MemoryStream ms = new MemoryStream())
            {
                while (fullread == false)
                {
                    int len = 0;
                    if ((len = crypto.Read(buffer, 0, buffer.Length)) <= 0)
                    {
                        fullread = true;
                        cont = ms.ToArray();
                    }
                    ms.Write(buffer, 0, len);
                }
            }

            memory.Close();
            crypto.Close();

            if (addpadding == true) { Array.Resize(ref cont, Tools.AddPadding(cont.Length)); }
            return cont;
        }

        /// <summary>
        /// Re-Encrypts the given content
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static byte[] ReEncryptAllContents(byte[] wadfile, byte[] oldtitlekey, byte[] newtitlekey)
        {
            int contentnum = WadInfo.GetContentNum(wadfile);
            int certsize = WadInfo.GetCertSize(wadfile);
            int tiksize = WadInfo.GetTikSize(wadfile);
            int tmdsize = WadInfo.GetTmdSize(wadfile);
            int contentpos = 64 + Tools.AddPadding(certsize) + Tools.AddPadding(tiksize) + Tools.AddPadding(tmdsize);

            for (int i = 0; i < contentnum; i++)
            {
                byte[] tmd = WadInfo.ReturnTmd(wadfile);
                byte[] decryptedcontent = DecryptContent(wadfile, i, oldtitlekey);
                byte[] encryptedcontent = EncryptContent(decryptedcontent, tmd, i, newtitlekey, false);

                for (int j = 0; j < encryptedcontent.Length; j++)
                {
                    wadfile[contentpos + j] = encryptedcontent[j];
                }
                contentpos += Tools.AddPadding(encryptedcontent.Length);
            }

            return wadfile;
        }

        /// <summary>
        /// Fixes the MD5 Sum in the IMET Header
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static byte[] FixMD5InImet(byte[] file, out byte[] newmd5)
        {
            if (Convert.ToChar(file[128]) == 'I' &&
                Convert.ToChar(file[129]) == 'M' &&
                Convert.ToChar(file[130]) == 'E' &&
                Convert.ToChar(file[131]) == 'T')
            {
                byte[] buffer = new byte[1536];

                using (MemoryStream ms = new MemoryStream())
                {
                    ms.Write(file, 0x40, 1536);
                    buffer = ms.ToArray();
                }

                for (int i = 0; i < 16; i++)
                    buffer[1520 + i] = 0x00;

                MD5 md5 = new MD5CryptoServiceProvider();
                byte[] hash = md5.ComputeHash(buffer);

                for (int i = 0; i < 16; i++)
                    file[1584 + i] = hash[i];

                newmd5 = hash;
                return file;
            }
            else
            {
                byte[] oldmd5 = new byte[16];

                using (MemoryStream ms = new MemoryStream())
                {
                    ms.Write(file, 1584, 16);
                    oldmd5 = ms.ToArray();
                }

                newmd5 = oldmd5;
                return file;
            }
        }

        /// <summary>
        /// Fixes the MD5 Sum in the IMET Header.
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static byte[] FixMD5InImet(byte[] file)
        {
            byte[] tmp = new byte[16];
            return FixMD5InImet(file, out tmp);
        }

        /// <summary>
        /// Updates the Content Info in the Tmd.
        /// Tmd and Contents must be in the same Directory
        /// </summary>
        /// <param name="tmdfile"></param>
        public static void UpdateTmdContents(string tmdfile)
        {
            FileStream tmd = new FileStream(tmdfile, FileMode.Open, FileAccess.ReadWrite);

            tmd.Seek(0x1de, SeekOrigin.Begin);
            int contentcount = Tools.HexStringToInt(tmd.ReadByte().ToString("x2") + tmd.ReadByte().ToString("x2"));

            for (int i = 0; i < contentcount; i++)
            {
                int oldsize = 0;
                int contentpos = 0x1e4 + (36 * i);

                tmd.Seek(contentpos + 4, SeekOrigin.Begin);
                string index = "0000" + tmd.ReadByte().ToString("x2") + tmd.ReadByte().ToString("x2");

                tmd.Seek(contentpos + 8, SeekOrigin.Begin);
                try
                {
                    oldsize = Tools.HexStringToInt(tmd.ReadByte().ToString("x2") +
                        tmd.ReadByte().ToString("x2") +
                        tmd.ReadByte().ToString("x2") +
                        tmd.ReadByte().ToString("x2") +
                        tmd.ReadByte().ToString("x2") +
                        tmd.ReadByte().ToString("x2") +
                        tmd.ReadByte().ToString("x2") +
                        tmd.ReadByte().ToString("x2"));
                }
                catch { }

                byte[] oldsha1 = new byte[20];
                tmd.Read(oldsha1, 0, oldsha1.Length);

                if (File.Exists(tmdfile.Remove(tmdfile.LastIndexOf('\\') + 1) + index + ".app"))
                {
                    byte[] content = Wii.Tools.LoadFileToByteArray(tmdfile.Remove(tmdfile.LastIndexOf('\\') + 1) + index + ".app");
                    int newsize = content.Length;
                    
                    if (newsize != oldsize)
                    {
                        byte[] changedsize = Tools.FileLengthToByteArray(newsize);

                        tmd.Seek(contentpos + 8, SeekOrigin.Begin);
                        for (int x = 8; x > changedsize.Length; x--) tmd.WriteByte(0x00);
                        tmd.Write(changedsize, 0, changedsize.Length);
                    }

                    SHA1Managed sha1 = new SHA1Managed();
                    byte[] newsha1 = sha1.ComputeHash(content);
                    sha1.Clear();

                    if (Tools.CompareByteArrays(newsha1, oldsha1) == false)
                    {
                        tmd.Seek(contentpos + 16, SeekOrigin.Begin);
                        tmd.Write(newsha1, 0, newsha1.Length);
                    }
                }
                else
                {
                    throw new Exception("At least one content file wasn't found!");
                }
            }

            tmd.Close();
        }

        /// <summary>
        /// Changes the Boot Index in the Tmd to the given value
        /// </summary>
        /// <param name="wadtmd"></param>
        /// <returns></returns>
        public static byte[] ChangeTmdBootIndex(byte[] wadtmd, int newindex)
        {
            int tmdpos = 0;

            if (WadInfo.IsThisWad(wadtmd) == true)
                tmdpos = WadInfo.GetTmdPos(wadtmd);

            byte[] index = BitConverter.GetBytes((UInt16)newindex);
            wadtmd[tmdpos + 0x1e0] = index[1];
            wadtmd[tmdpos + 0x1e1] = index[0];

            return wadtmd;
        }

        /// <summary>
        /// Changes the Content Count in the Tmd
        /// </summary>
        /// <param name="wadtmd"></param>
        /// <param name="newcount"></param>
        /// <returns></returns>
        public static byte[] ChangeTmdContentCount(byte[] wadtmd, int newcount)
        {
            int tmdpos = 0;

            if (WadInfo.IsThisWad(wadtmd) == true)
                tmdpos = WadInfo.GetTmdPos(wadtmd);

            byte[] count = BitConverter.GetBytes((UInt16)newcount);
            wadtmd[tmdpos + 0x1de] = count[1];
            wadtmd[tmdpos + 0x1df] = count[0];

            return wadtmd;
        }

        /// <summary>
        /// Changes the Slot where the IOS Wad will be installed to
        /// </summary>
        /// <param name="wad"></param>
        /// <param name="newslot"></param>
        /// <returns></returns>
        public static byte[] ChangeIosSlot(byte[] wadfile, int newslot)
        {
            Tools.ChangeProgress(0);

            int tikpos = WadInfo.GetTikPos(wadfile);
            int tmdpos = WadInfo.GetTmdPos(wadfile);
            byte[] slot = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(newslot));

            byte[] oldtitlekey = WadInfo.GetTitleKey(wadfile);

            Tools.ChangeProgress(20);

            //Change the ID in the ticket
            wadfile[tikpos + 0x1e0] = slot[0];
            wadfile[tikpos + 0x1e1] = slot[1];
            wadfile[tikpos + 0x1e2] = slot[2];
            wadfile[tikpos + 0x1e3] = slot[3];

            //Change the ID in the tmd
            wadfile[tmdpos + 0x190] = slot[0];
            wadfile[tmdpos + 0x191] = slot[1];
            wadfile[tmdpos + 0x192] = slot[2];
            wadfile[tmdpos + 0x193] = slot[3];

            Tools.ChangeProgress(40);

            //Trucha-Sign both
            wadfile = TruchaSign(wadfile, 0);

            Tools.ChangeProgress(50);

            wadfile = TruchaSign(wadfile, 1);

            Tools.ChangeProgress(60);

            byte[] newtitlekey = WadInfo.GetTitleKey(wadfile);
            byte[] tmd = WadInfo.ReturnTmd(wadfile);

            int contentcount = WadInfo.GetContentNum(wadfile);

            wadfile = ReEncryptAllContents(wadfile, oldtitlekey, newtitlekey);

            Tools.ChangeProgress(100);
            return wadfile;
        }

        /// <summary>
        /// Changes the Title Version of a Wad or Tmd
        /// </summary>
        /// <param name="wadtmd"></param>
        /// <param name="newversion"></param>
        /// <returns></returns>
        public static byte[] ChangeTitleVersion(byte[] wadtmd, int newversion)
        {
            if (newversion > 65535) throw new Exception("Version can be max. 65535");

            int offset = 0x1dc;
            int tmdpos = 0;

            if (WadInfo.IsThisWad(wadtmd))
                tmdpos = WadInfo.GetTmdPos(wadtmd);

            byte[] version = BitConverter.GetBytes((UInt16)newversion);
            Array.Reverse(version);

            wadtmd[tmdpos + offset] = version[0];
            wadtmd[tmdpos + offset + 1] = version[1];

            wadtmd = TruchaSign(wadtmd, 1);

            return wadtmd;
        }

        /// <summary>
        /// Changes the Title Key in the Tik
        /// </summary>
        /// <param name="tik"></param>
        /// <returns></returns>
        public static byte[] ChangeTitleKey(byte[] tik)
        {
            byte[] newKey = new byte[] { 0x47, 0x6f, 0x74, 0x74, 0x61, 0x47, 0x65, 0x74, 0x53, 0x6f, 0x6d, 0x65, 0x42, 0x65, 0x65, 0x72 };
            Tools.InsertByteArray(tik, newKey, 447);
            return tik;
        }
    }

    public class WadUnpack
    {
        /// <summary>
        /// Unpacks the 00000000.app of a wad
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static void UnpackNullApp(string wadfile, string destination)
        {
            if (!destination.EndsWith(".app")) destination += "\\00000000.app";

            byte[] wad = Tools.LoadFileToByteArray(wadfile);
            byte[] nullapp = UnpackNullApp(wad);
            Tools.SaveFileFromByteArray(nullapp, destination);
        }

        /// <summary>
        /// Unpacks the 00000000.app of a wad
        /// </summary>
        /// <param name="wadfile"></param>
        /// <returns></returns>
        public static byte[] UnpackNullApp(byte[] wadfile)
        {
            int certsize = WadInfo.GetCertSize(wadfile);
            int tiksize = WadInfo.GetTikSize(wadfile);
            int tmdpos = WadInfo.GetTmdPos(wadfile);
            int tmdsize = WadInfo.GetTmdSize(wadfile);
            int contentpos = 64 + Tools.AddPadding(certsize) + Tools.AddPadding(tiksize) + Tools.AddPadding(tmdsize);

            byte[] titlekey = WadInfo.GetTitleKey(wadfile);
            string[,] contents = WadInfo.GetContentInfo(wadfile);

            for (int i = 0; i < contents.GetLength(0); i++)
            {
                if (contents[i, 1] == "00000000")
                {
                    return WadEdit.DecryptContent(wadfile, i, titlekey);
                }
            }

            throw new Exception("00000000.app couldn't be found in the Wad");
        }

        /// <summary>
        /// Unpacks the the wad file
        /// </summary>
        public static void UnpackWad(string pathtowad, string destinationpath)
        {
            byte[] wadfile = Tools.LoadFileToByteArray(pathtowad);
            UnpackWad(wadfile, destinationpath);
        }

        /// <summary>
        /// Unpacks the the wad file
        /// </summary>
        public static void UnpackWad(string pathtowad, string destinationpath, out bool hashesmatch)
        {
            byte[] wadfile = Tools.LoadFileToByteArray(pathtowad);
            UnpackWad(wadfile, destinationpath, out hashesmatch);
        }

        /// <summary>
        /// Unpacks the wad file to *wadpath*\wadunpack\
        /// </summary>
        /// <param name="pathtowad"></param>
        public static void UnpackWad(string pathtowad)
        {
            string destinationpath = pathtowad.Remove(pathtowad.LastIndexOf('\\'));
            byte[] wadfile = Tools.LoadFileToByteArray(pathtowad);
            UnpackWad(wadfile, destinationpath);
        }

        /// <summary>
        /// Unpacks the wad file
        /// </summary>
        public static void UnpackWad(byte[] wadfile, string destinationpath)
        {
            bool temp;
            UnpackWad(wadfile, destinationpath, out temp);
        }

        /// <summary>
        /// Unpacks the wad file
        /// </summary>
        public static void UnpackWad(byte[] wadfile, string destinationpath, out bool hashesmatch)
        {
            if (destinationpath[destinationpath.Length - 1] != '\\')
            { destinationpath = destinationpath + "\\"; }

            hashesmatch = true;

            if (!Directory.Exists(destinationpath))
            { Directory.CreateDirectory(destinationpath); }
            if (Directory.GetFiles(destinationpath, "*.app").Length > 0)
            {
                throw new Exception("At least one of the files to unpack already exists!");
            }

            int certpos = 0x40;
            int certsize = WadInfo.GetCertSize(wadfile);
            int tikpos = WadInfo.GetTikPos(wadfile);
            int tiksize = WadInfo.GetTikSize(wadfile);
            int tmdpos = WadInfo.GetTmdPos(wadfile);
            int tmdsize = WadInfo.GetTmdSize(wadfile);
            int contentlength = WadInfo.GetContentSize(wadfile);
            int footersize = WadInfo.GetFooterSize(wadfile);
            int footerpos = 64 + Tools.AddPadding(certsize) + Tools.AddPadding(tiksize) + Tools.AddPadding(tmdsize) + Tools.AddPadding(contentlength);
            string wadpath = WadInfo.GetNandPath(wadfile, 0).Remove(8, 1);
            string[,] contents = WadInfo.GetContentInfo(wadfile);
            byte[] titlekey = WadInfo.GetTitleKey(wadfile);
            int contentpos = 64 + Tools.AddPadding(certsize) + Tools.AddPadding(tiksize) + Tools.AddPadding(tmdsize);

            //unpack cert
            using (FileStream cert = new FileStream(destinationpath + wadpath + ".cert", FileMode.Create))
            {
                cert.Seek(0, SeekOrigin.Begin);
                cert.Write(wadfile, certpos, certsize);
            }

            //unpack ticket
            using (FileStream tik = new FileStream(destinationpath + wadpath + ".tik", FileMode.Create))
            {
                tik.Seek(0, SeekOrigin.Begin);
                tik.Write(wadfile, tikpos, tiksize);
            }

            //unpack tmd
            using (FileStream tmd = new FileStream(destinationpath + wadpath + ".tmd", FileMode.Create))
            {
                tmd.Seek(0, SeekOrigin.Begin);
                tmd.Write(wadfile, tmdpos, tmdsize);
            }

            //unpack trailer
            try
            {
                if (footersize > 0)
                {
                    using (FileStream trailer = new FileStream(destinationpath + wadpath + ".trailer", FileMode.Create))
                    {
                        trailer.Seek(0, SeekOrigin.Begin);
                        trailer.Write(wadfile, footerpos, footersize);
                    }
                }
            }
            catch { } //who cares if the trailer doesn't extract properly?

            Tools.ChangeProgress(0);

            //unpack contents
            for (int i = 0; i < contents.GetLength(0); i++)
            {
                Tools.ChangeProgress((i + 1) * 100 / contents.GetLength(0));
                byte[] thiscontent = WadEdit.DecryptContent(wadfile, i, titlekey);
                FileStream content = new FileStream(destinationpath + contents[i, 1] + ".app", FileMode.Create);

                content.Write(thiscontent, 0, thiscontent.Length);
                content.Close();

                contentpos += Tools.AddPadding(thiscontent.Length);

                //sha1 comparison
                SHA1Managed sha1 = new SHA1Managed();
                byte[] thishash = sha1.ComputeHash(thiscontent);
                byte[] tmdhash = Tools.HexStringToByteArray(contents[i, 4]);

                if (Tools.CompareByteArrays(thishash, tmdhash) == false) hashesmatch = false;
                //    throw new Exception("At least one content's hash doesn't match the hash in the Tmd!");
            }
        }

        /// <summary>
        /// Unpacks the wad file to the given directory.
        /// Shared contents will be unpacked to /shared1
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="nandpath"></param>
        public static void UnpackToNand(string wadfile, string nandpath)
        {
            byte[] wadarray = Tools.LoadFileToByteArray(wadfile);
            UnpackToNand(wadarray, nandpath);
        }

        /// <summary>
        /// Unpacks the wad file to the given directory.
        /// Shared contents will be unpacked to /shared1
        /// </summary>
        /// <param name="wadfile"></param>
        /// <param name="nandpath"></param>
        public static void UnpackToNand(byte[] wadfile, string nandpath)
        {
            string path = WadInfo.GetNandPath(wadfile, 0);
            string path1 = path.Remove(path.IndexOf('\\'));
            string path2 = path.Remove(0, path.IndexOf('\\') + 1);

            if (nandpath[nandpath.Length - 1] != '\\') { nandpath = nandpath + "\\"; }

            if (!Directory.Exists(nandpath + "ticket")) { Directory.CreateDirectory(nandpath + "ticket"); }
            if (!Directory.Exists(nandpath + "title")) { Directory.CreateDirectory(nandpath + "title"); }
            if (!Directory.Exists(nandpath + "ticket\\" + path1)) { Directory.CreateDirectory(nandpath + "ticket\\" + path1); }
            if (!Directory.Exists(nandpath + "title\\" + path1)) { Directory.CreateDirectory(nandpath + "title\\" + path1); }
            if (!Directory.Exists(nandpath + "title\\" + path1 + "\\" + path2)) { Directory.CreateDirectory(nandpath + "title\\" + path1 + "\\" + path2); }
            if (!Directory.Exists(nandpath + "title\\" + path1 + "\\" + path2 + "\\content")) { Directory.CreateDirectory(nandpath + "title\\" + path1 + "\\" + path2 + "\\content"); }
            if (!Directory.Exists(nandpath + "title\\" + path1 + "\\" + path2 + "\\data")) { Directory.CreateDirectory(nandpath + "title\\" + path1 + "\\" + path2 + "\\data"); }
            if (!Directory.Exists(nandpath + "shared1")) Directory.CreateDirectory(nandpath + "shared1");

            int certsize = WadInfo.GetCertSize(wadfile);
            int tikpos = WadInfo.GetTikPos(wadfile);
            int tiksize = WadInfo.GetTikSize(wadfile);
            int tmdpos = WadInfo.GetTmdPos(wadfile);
            int tmdsize = WadInfo.GetTmdSize(wadfile);
            int contentlength = WadInfo.GetContentSize(wadfile);
            string[,] contents = WadInfo.GetContentInfo(wadfile);
            byte[] titlekey = WadInfo.GetTitleKey(wadfile);
            int contentpos = 64 + Tools.AddPadding(certsize) + Tools.AddPadding(tiksize) + Tools.AddPadding(tmdsize);

            //unpack ticket
            using (FileStream tik = new FileStream(nandpath + "ticket\\" + path1 + "\\" + path2 + ".tik", FileMode.Create))
            {
                tik.Seek(0, SeekOrigin.Begin);
                tik.Write(wadfile, tikpos, tiksize);
            }

            //unpack tmd
            using (FileStream tmd = new FileStream(nandpath + "title\\" + path1 + "\\" + path2 + "\\content\\title.tmd", FileMode.Create))
            {
                tmd.Seek(0, SeekOrigin.Begin);
                tmd.Write(wadfile, tmdpos, tmdsize);
            }

            Tools.ChangeProgress(0);

            //unpack contents
            for (int i = 0; i < contents.GetLength(0); i++)
            {
                Tools.ChangeProgress((i + 1) * 100 / contents.GetLength(0));
                byte[] thiscontent = WadEdit.DecryptContent(wadfile, i, titlekey);

                if (contents[i, 2] == "8001")
                {
                    if (File.Exists(nandpath + "shared1\\content.map"))
                    {
                        byte[] contmap = Tools.LoadFileToByteArray(nandpath + "shared1\\content.map");

                        if (ContentMap.CheckSharedContent(contmap, contents[i, 4]) == false)
                        {
                            string newname = ContentMap.GetNewSharedContentName(contmap);

                            FileStream content = new FileStream(nandpath + "shared1\\" + newname + ".app", FileMode.Create);
                            content.Write(thiscontent, 0, thiscontent.Length);
                            content.Close();
                            ContentMap.AddSharedContent(nandpath + "shared1\\content.map", newname, contents[i, 4]);
                        }
                    }
                    else
                    {
                        FileStream content = new FileStream(nandpath + "shared1\\00000000.app", FileMode.Create);
                        content.Write(thiscontent, 0, thiscontent.Length);
                        content.Close();
                        ContentMap.AddSharedContent(nandpath + "shared1\\content.map", "00000000", contents[i, 4]);
                    }
                }
                else
                {
                    FileStream content = new FileStream(nandpath + "title\\" + path1 + "\\" + path2 + "\\content\\" + contents[i, 0] + ".app", FileMode.Create);

                    content.Write(thiscontent, 0, thiscontent.Length);
                    content.Close();
                }

                contentpos += Tools.AddPadding(thiscontent.Length);
            }

            //add titleid to uid.sys, if it doesn't exist
            string titleid = WadInfo.GetFullTitleID(wadfile, 1);

            if (File.Exists(nandpath + "\\sys\\uid.sys"))
            {
                FileStream fs = new FileStream(nandpath + "\\sys\\uid.sys", FileMode.Open);
                byte[] uidsys = new byte[fs.Length];
                fs.Read(uidsys, 0, uidsys.Length);
                fs.Close();

                if (UID.CheckUID(uidsys, titleid) == false)
                {
                    uidsys = UID.AddUID(uidsys, titleid);
                    Tools.SaveFileFromByteArray(uidsys, nandpath + "\\sys\\uid.sys");
                }
            }
            else
            {
                if (!Directory.Exists(nandpath + "\\sys")) Directory.CreateDirectory(nandpath + "\\sys");
                byte[] uidsys = UID.AddUID(new byte[0], titleid);
                Tools.SaveFileFromByteArray(uidsys, nandpath + "\\sys\\uid.sys");
            }
        }
    }

    public class WadPack
    {
        public static byte[] wadheader = new byte[8] { 0x00, 0x00, 0x00, 0x20, 0x49, 0x73, 0x00, 0x00 };

        /// <summary>
        /// Packs the contents in the given directory and creates the destination wad file 
        /// </summary>
        /// <param name="directory"></param>
        public static void PackWad(string contentdirectory, string destinationfile)
        {
            if (contentdirectory[contentdirectory.Length - 1] != '\\') { contentdirectory = contentdirectory + "\\"; }

            if (!Directory.Exists(contentdirectory)) throw new DirectoryNotFoundException("The directory doesn't exists:\r\n" + contentdirectory);
            if (Directory.GetFiles(contentdirectory, "*.app").Length < 1) throw new Exception("No *.app file was found");
            if (Directory.GetFiles(contentdirectory, "*.cert").Length < 1) throw new Exception("No *.cert file was found");
            if (Directory.GetFiles(contentdirectory, "*.tik").Length < 1) throw new Exception("No *.tik file was found");
            if (Directory.GetFiles(contentdirectory, "*.tmd").Length < 1) throw new Exception("No *.tmd file was found");
            if (File.Exists(destinationfile)) throw new Exception("The destination file already exists!");

            string[] certfile = Directory.GetFiles(contentdirectory, "*.cert");
            string[] tikfile = Directory.GetFiles(contentdirectory, "*.tik");
            string[] tmdfile = Directory.GetFiles(contentdirectory, "*.tmd");

            byte[] cert = Tools.LoadFileToByteArray(certfile[0]);
            byte[] tik = Tools.LoadFileToByteArray(tikfile[0]);
            byte[] tmd = Tools.LoadFileToByteArray(tmdfile[0]);

            tik = WadEdit.ChangeTitleKey(tik);

            string[,] contents = WadInfo.GetContentInfo(tmd);

            FileStream wadstream = new FileStream(destinationfile, FileMode.Create);

            //Trucha-Sign Tik and Tmd, if they aren't already
            WadEdit.TruchaSign(tik, 0);
            WadEdit.TruchaSign(tmd, 1);

            //Write Cert
            wadstream.Seek(64, SeekOrigin.Begin);
            wadstream.Write(cert, 0, cert.Length);

            //Write Tik
            wadstream.Seek(64 + Tools.AddPadding(cert.Length), SeekOrigin.Begin);
            wadstream.Write(tik, 0, tik.Length);

            //Write Tmd
            wadstream.Seek(64 + Tools.AddPadding(cert.Length) + Tools.AddPadding(tik.Length), SeekOrigin.Begin);
            wadstream.Write(tmd, 0, tmd.Length);

            //Write Content
            int allcont = 0;
            int contpos = 64 + Tools.AddPadding(cert.Length) + Tools.AddPadding(tik.Length) + Tools.AddPadding(tmd.Length);
            int contcount = WadInfo.GetContentNum(tmd);

            Tools.ChangeProgress(0);
            byte[] titlekey = WadInfo.GetTitleKey(tik);

            for (int i = 0; i < contents.GetLength(0); i++)
            {
                Tools.ChangeProgress((i + 1) * 100 / contents.GetLength(0));
                byte[] thiscont = Tools.LoadFileToByteArray(contentdirectory + contents[i, 1] + ".app");

                //if (i == contents.GetLength(0) - 1) { thiscont = WadEdit.EncryptContent(thiscont, tmd, i, titlekey, false); }
                //else { thiscont = WadEdit.EncryptContent(thiscont, tmd, i, titlekey, true); }
                thiscont = WadEdit.EncryptContent(thiscont, tmd, i, titlekey, true);

                wadstream.Seek(contpos, SeekOrigin.Begin);
                wadstream.Write(thiscont, 0, thiscont.Length);
                contpos += thiscont.Length;
                allcont += thiscont.Length;
            }

            //Write Footer Timestamp
            byte[] footer = Tools.GetTimestamp();
            Array.Resize(ref footer, Tools.AddPadding(footer.Length, 16));

            int footerLength = footer.Length;
            wadstream.Seek(Tools.AddPadding(contpos), SeekOrigin.Begin);
            wadstream.Write(footer, 0, footer.Length);

            //Write Header
            byte[] certsize = Tools.FileLengthToByteArray(cert.Length);
            byte[] tiksize = Tools.FileLengthToByteArray(tik.Length);
            byte[] tmdsize = Tools.FileLengthToByteArray(tmd.Length);
            byte[] allcontsize = Tools.FileLengthToByteArray(allcont);
            byte[] footersize = Tools.FileLengthToByteArray(footerLength);

            wadstream.Seek(0x00, SeekOrigin.Begin);
            wadstream.Write(wadheader, 0, wadheader.Length);
            wadstream.Seek(0x08, SeekOrigin.Begin);
            wadstream.Write(certsize, 0, certsize.Length);
            wadstream.Seek(0x10, SeekOrigin.Begin);
            wadstream.Write(tiksize, 0, tiksize.Length);
            wadstream.Seek(0x14, SeekOrigin.Begin);
            wadstream.Write(tmdsize, 0, tmdsize.Length);
            wadstream.Seek(0x18, SeekOrigin.Begin);
            wadstream.Write(allcontsize, 0, allcontsize.Length);
            wadstream.Seek(0x1c, SeekOrigin.Begin);
            wadstream.Write(footersize, 0, footersize.Length);

            wadstream.Close();
        }

        /// <summary>
        /// Packs a Wad from a title installed on Nand
        /// Returns: 0 = OK, 1 = Files missing, 2 = Shared contents missing, 3 = Cert missing
        /// </summary>
        /// <param name="nandpath"></param>
        /// <param name="path">XXXXXXXX\XXXXXXXX</param>
        /// <param name="destinationfile"></param>
        /// <returns></returns>
        public static void PackWadFromNand(string nandpath, string path, string destinationfile)
        {
            if (nandpath[nandpath.Length - 1] != '\\') { nandpath = nandpath + "\\"; }
            string path1 = path.Remove(8);
            string path2 = path.Remove(0, 9);
            string ticketdir = nandpath + "ticket\\" + path1 + "\\";
            string contentdir = nandpath + "title\\" + path1 + "\\" + path2 + "\\content\\";
            string sharedir = nandpath + "shared1\\";
            string certdir = nandpath + "sys\\";

            if (!Directory.Exists(ticketdir) ||
                !Directory.Exists(contentdir)) throw new DirectoryNotFoundException("Directory doesn't exist:\r\n" + contentdir);
            if (!Directory.Exists(sharedir)) throw new DirectoryNotFoundException("Directory doesn't exist:\r\n" + sharedir);
            if (!File.Exists(certdir + "cert.sys")) throw new FileNotFoundException("File doesn't exist:\r\n" + certdir + "cert.sys");

            byte[] cert = Tools.LoadFileToByteArray(certdir + "cert.sys");
            byte[] tik = Tools.LoadFileToByteArray(ticketdir + path2 + ".tik");
            byte[] tmd = Tools.LoadFileToByteArray(contentdir + "title.tmd");

            tik = WadEdit.ChangeTitleKey(tik);

            string[,] contents = WadInfo.GetContentInfo(tmd);

            FileStream wadstream = new FileStream(destinationfile, FileMode.Create);

            //Trucha-Sign Tik and Tmd, if they aren't already
            WadEdit.TruchaSign(tik, 0);
            WadEdit.TruchaSign(tmd, 1);

            //Write Cert
            wadstream.Seek(64, SeekOrigin.Begin);
            wadstream.Write(cert, 0, cert.Length);

            //Write Tik
            wadstream.Seek(64 + Tools.AddPadding(cert.Length), SeekOrigin.Begin);
            wadstream.Write(tik, 0, tik.Length);

            //Write Tmd
            wadstream.Seek(64 + Tools.AddPadding(cert.Length) + Tools.AddPadding(tik.Length), SeekOrigin.Begin);
            wadstream.Write(tmd, 0, tmd.Length);

            //Write Content
            int allcont = 0;
            int contpos = 64 + Tools.AddPadding(cert.Length) + Tools.AddPadding(tik.Length) + Tools.AddPadding(tmd.Length);
            int contcount = WadInfo.GetContentNum(tmd);

            Tools.ChangeProgress(0);
            byte[] titlekey = WadInfo.GetTitleKey(tik);
            byte[] contentmap = Tools.LoadFileToByteArray(sharedir + "content.map");

            for (int i = 0; i < contents.GetLength(0); i++)
            {
                Tools.ChangeProgress((i + 1) * 100 / contents.GetLength(0));
                byte[] thiscont = new byte[1];

                if (contents[i, 2] == "8001")
                {
                    string contname = ContentMap.GetSharedContentName(contentmap, contents[i, 4]);
                    
                    if (contname == "fail")
                    {
                        wadstream.Close();
                        File.Delete(destinationfile);
                        throw new FileNotFoundException("At least one shared content is missing!");
                    }
                    
                    thiscont = Tools.LoadFileToByteArray(sharedir + contname + ".app");
                }
                else thiscont = Tools.LoadFileToByteArray(contentdir + contents[i, 0] + ".app");

                //if (i == contents.GetLength(0) - 1) { thiscont = WadEdit.EncryptContent(thiscont, tmd, i, titlekey, false); }
                //else { thiscont = WadEdit.EncryptContent(thiscont, tmd, i, titlekey, true); }
                thiscont = WadEdit.EncryptContent(thiscont, tmd, i, titlekey, true);

                wadstream.Seek(contpos, SeekOrigin.Begin);
                wadstream.Write(thiscont, 0, thiscont.Length);
                contpos += thiscont.Length;
                allcont += thiscont.Length;
            }

            //Write Footer Timestamp
            byte[] footer = Tools.GetTimestamp();
            Array.Resize(ref footer, Tools.AddPadding(footer.Length, 16));

            int footerLength = footer.Length;
            wadstream.Seek(Tools.AddPadding(contpos), SeekOrigin.Begin);
            wadstream.Write(footer, 0, footer.Length);

            //Write Header
            byte[] certsize = Tools.FileLengthToByteArray(cert.Length);
            byte[] tiksize = Tools.FileLengthToByteArray(tik.Length);
            byte[] tmdsize = Tools.FileLengthToByteArray(tmd.Length);
            byte[] allcontsize = Tools.FileLengthToByteArray(allcont);
            byte[] footersize = Tools.FileLengthToByteArray(footerLength);

            wadstream.Seek(0x00, SeekOrigin.Begin);
            wadstream.Write(wadheader, 0, wadheader.Length);
            wadstream.Seek(0x08, SeekOrigin.Begin);
            wadstream.Write(certsize, 0, certsize.Length);
            wadstream.Seek(0x10, SeekOrigin.Begin);
            wadstream.Write(tiksize, 0, tiksize.Length);
            wadstream.Seek(0x14, SeekOrigin.Begin);
            wadstream.Write(tmdsize, 0, tmdsize.Length);
            wadstream.Seek(0x18, SeekOrigin.Begin);
            wadstream.Write(allcontsize, 0, allcontsize.Length);
            wadstream.Seek(0x1c, SeekOrigin.Begin);
            wadstream.Write(footersize, 0, footersize.Length);

            wadstream.Close();
        }
    }

    public class UID
    {
        /// <summary>
        /// Checks if the given Title ID exists in the uid.sys
        /// </summary>
        /// <param name="uidsys"></param>
        /// <param name="fulltitleid"></param>
        /// <returns></returns>
        public static bool CheckUID(byte[] uidsys, string fulltitleid)
        {
            for (int i = 0; i < uidsys.Length; i += 12)
            {
                string temp = "";

                for (int y = i; y < i + 8; y++)
                    temp += uidsys[y].ToString("x2");

                if (temp == fulltitleid) return true;
            }

            return false;
        }

        /// <summary>
        /// Gets a new UID
        /// </summary>
        /// <param name="uidsys"></param>
        /// <returns></returns>
        public static string GetNewUID(byte[] uidsys)
        {
            string lastuid = uidsys[uidsys.Length - 4].ToString("x2") +
                uidsys[uidsys.Length - 3].ToString("x2") +
                uidsys[uidsys.Length - 2].ToString("x2") +
                uidsys[uidsys.Length - 1].ToString("x2");

            string newuid = (int.Parse(lastuid, System.Globalization.NumberStyles.HexNumber) + 1).ToString("x8");
            return newuid;
        }

        /// <summary>
        /// Adds a Title ID to uid.sys
        /// </summary>
        /// <param name="uidsys"></param>
        /// <param name="fulltitleid"></param>
        /// <returns></returns>
        public static byte[] AddUID(byte[] uidsys, string fulltitleid)
        {
            if (uidsys.Length > 11)
            {
                MemoryStream uid = new MemoryStream();
                byte[] titleid = Tools.HexStringToByteArray(fulltitleid);
                byte[] newuid = Tools.HexStringToByteArray(GetNewUID(uidsys));

                uid.Write(uidsys, 0, uidsys.Length);
                uid.Write(titleid, 0, titleid.Length);
                uid.Write(newuid, 0, newuid.Length);

                return uid.ToArray();
            }
            else
            {
                MemoryStream uid = new MemoryStream();
                byte[] titleid = Tools.HexStringToByteArray(fulltitleid);
                byte[] newuid = new byte[] { 0x00, 0x00, 0x10, 0x00 };

                uid.Write(titleid, 0, titleid.Length);
                uid.Write(newuid, 0, newuid.Length);

                return uid.ToArray();
            }
        }
    }

    public class ContentMap
    {
        /// <summary>
        /// Gets the name of the shared content in /shared1/.
        /// Returns "fail", if the content doesn't exist
        /// </summary>
        /// <param name="contentmap"></param>
        /// <param name="sha1ofcontent"></param>
        /// <returns></returns>
        public static string GetSharedContentName(byte[] contentmap, string sha1ofcontent)
        {
            int contindex = 0;
            string result = "";

            for (int i = 0; i < contentmap.Length - 19; i++)
            {
                string tmp = "";
                for (int y = 0; y < 20; y++)
                {
                    tmp += contentmap[i + y].ToString("x2");
                }

                if (tmp == sha1ofcontent)
                {
                    contindex = i;
                    break;
                }
            }

            if (contindex == 0) return "fail";

            result += Convert.ToChar(contentmap[contindex - 8]);
            result += Convert.ToChar(contentmap[contindex - 7]);
            result += Convert.ToChar(contentmap[contindex - 6]);
            result += Convert.ToChar(contentmap[contindex - 5]);
            result += Convert.ToChar(contentmap[contindex - 4]);
            result += Convert.ToChar(contentmap[contindex - 3]);
            result += Convert.ToChar(contentmap[contindex - 2]);
            result += Convert.ToChar(contentmap[contindex - 1]);

            return result;
        }

        /// <summary>
        /// Checks, if the shared content exists
        /// </summary>
        /// <param name="contentmap"></param>
        /// <param name="sha1ofcontent"></param>
        /// <returns></returns>
        public static bool CheckSharedContent(byte[] contentmap, string sha1ofcontent)
        {
            for (int i = 0; i < contentmap.Length - 19; i++)
            {
                string tmp = "";
                for (int y = 0; y < 20; y++)
                {
                    tmp += contentmap[i + y].ToString("x2");
                }

                if (tmp == sha1ofcontent) return true;
            }

            return false;
        }

        public static string GetNewSharedContentName(byte[] contentmap)
        {
            string name = "";

            name += Convert.ToChar(contentmap[contentmap.Length - 28]);
            name += Convert.ToChar(contentmap[contentmap.Length - 27]);
            name += Convert.ToChar(contentmap[contentmap.Length - 26]);
            name += Convert.ToChar(contentmap[contentmap.Length - 25]);
            name += Convert.ToChar(contentmap[contentmap.Length - 24]);
            name += Convert.ToChar(contentmap[contentmap.Length - 23]);
            name += Convert.ToChar(contentmap[contentmap.Length - 22]);
            name += Convert.ToChar(contentmap[contentmap.Length - 21]);

            string newname = (int.Parse(name, System.Globalization.NumberStyles.HexNumber) + 1).ToString("x8");

            return newname;
        }

        public static void AddSharedContent(string contentmap, string contentname, string sha1ofcontent)
        {
            byte[] name = new byte[8];
            byte[] sha1 = new byte[20];

            for (int i = 0; i < 8; i++)
            {
                name[i] = (byte)contentname[i];
            }

            for (int i = 0; i < sha1ofcontent.Length; i += 2)
            {
                sha1[i / 2] = Convert.ToByte(sha1ofcontent.Substring(i, 2), 16);
            }

            using (FileStream map = new FileStream(contentmap, FileMode.OpenOrCreate, FileAccess.ReadWrite))
            {
                map.Seek(0, SeekOrigin.End);
                map.Write(name, 0, name.Length);
                map.Write(sha1, 0, sha1.Length);
            }
        }
    }

    public class U8
    {
        /// <summary>
        /// Checks if the given file is a U8 Archive
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static bool CheckU8(byte[] file)
        {
            int length = 2500;
            if (file.Length < length) length = file.Length - 4;

            for (int i = 0; i < length; i++)
            {
                if (file[i] == 0x55 && file[i + 1] == 0xAA && file[i + 2] == 0x38 && file[i + 3] == 0x2D)
                    return true;
            }

            return false;
        }

        /// <summary>
        /// Checks if the given file is a U8 Archive
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static bool CheckU8(string file)
        {
            byte[] buff = Tools.LoadFileToByteArray(file, 0, 2500);
            return CheckU8(buff);
        }

        /// <summary>
        /// Gets all contents of a folder (including (sub-)files and (sub-)folders)
        /// </summary>
        /// <param name="dir"></param>
        /// <param name="root"></param>
        /// <returns></returns>
        public static string[] GetDirContent(string dir, bool root)
        {
            string[] files = Directory.GetFiles(dir);
            string[] dirs = Directory.GetDirectories(dir);
            string all = "";

            if (root == false)
                all += dir + "\n";

            for (int i = 0; i < files.Length; i++)
                all += files[i] + "\n";

            foreach (string thisDir in dirs)
            {
                string[] temp = GetDirContent(thisDir, false);

                foreach (string thisTemp in temp)
                    all += thisTemp + "\n";
            }

            return all.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
        }

        /// <summary>
        /// Detecs if the U8 file has an IMD5 or IMET Header.
        /// Return: 0 = No Header, 1 = IMD5, 2 = IMET
        /// </summary>
        /// <param name="file"></param>
        public static int DetectHeader(string file)
        {
            byte[] temp = Tools.LoadFileToByteArray(file, 0, 400);
            return DetectHeader(temp);
        }

        /// <summary>
        /// Detecs if the U8 file has an IMD5 or IMET Header.
        /// Return: 0 = No Header, 1 = IMD5, 2 = IMET
        /// </summary>
        /// <param name="file"></param>
        public static int DetectHeader(byte[] file)
        {
            for (int i = 0; i < 16; i++) //Just to be safe
            {
                if (Convert.ToChar(file[i]) == 'I')
                    if (Convert.ToChar(file[i + 1]) == 'M')
                        if (Convert.ToChar(file[i + 2]) == 'D')
                            if (Convert.ToChar(file[i + 3]) == '5')
                                return 1;
            }

            int length = 400;
            if (file.Length < 400) length = file.Length - 4;

            for (int z = 0; z < length; z++)
            {
                if (Convert.ToChar(file[z]) == 'I')
                    if (Convert.ToChar(file[z + 1]) == 'M')
                        if (Convert.ToChar(file[z + 2]) == 'E')
                            if (Convert.ToChar(file[z + 3]) == 'T')
                                return 2;
            }

            return 0;
        }

        /// <summary>
        /// Adds an IMD5 Header to the given U8 Archive
        /// </summary>
        /// <param name="u8archive"></param>
        /// <returns></returns>
        public static byte[] AddHeaderIMD5(byte[] u8archive)
        {
            MemoryStream ms = new MemoryStream();
            MD5 md5 = MD5.Create();
            
            byte[] imd5 = new byte[4];
            imd5[0] = (byte)'I';
            imd5[1] = (byte)'M';
            imd5[2] = (byte)'D';
            imd5[3] = (byte)'5';

            byte[] size = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(u8archive.Length));
            byte[] hash = md5.ComputeHash(u8archive, 0, u8archive.Length);

            ms.Seek(0, SeekOrigin.Begin);
            ms.Write(imd5, 0, imd5.Length);
            ms.Write(size, 0, size.Length);

            ms.Seek(0x10, SeekOrigin.Begin);
            ms.Write(hash, 0, hash.Length);

            ms.Write(u8archive, 0, u8archive.Length);

            md5.Clear();
            return ms.ToArray();
        }

        /// <summary>
        /// Adds an IMET Header to the given 00.app
        /// </summary>
        /// <param name="u8archive"></param>
        /// <param name="channeltitles">Order: Jap, Eng, Ger, Fra, Spa, Ita, Dut</param>
        /// <param name="sizes">Order: Banner.bin, Icon.bin, Sound.bin</param>
        /// <returns></returns>
        public static byte[] AddHeaderIMET(byte[] nullapp, string[] channeltitles, int[] sizes)
        {
            if (channeltitles.Length < 7) return nullapp;
            for (int i = 0; i < channeltitles.Length; i++)
                if (channeltitles[i].Length > 20) return nullapp;

            MemoryStream ms = new MemoryStream();
            MD5 md5 = MD5.Create();

            byte[] imet = new byte[4];
            imet[0] = (byte)'I';
            imet[1] = (byte)'M';
            imet[2] = (byte)'E';
            imet[3] = (byte)'T';

            byte[] unknown = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(0x0000060000000003));

            byte[] iconsize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(sizes[1]));
            byte[] bannersize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(sizes[0]));
            byte[] soundsize = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(sizes[2]));

            byte[] japtitle = new byte[84];
            byte[] engtitle = new byte[84];
            byte[] gertitle = new byte[84];
            byte[] fratitle = new byte[84];
            byte[] spatitle = new byte[84];
            byte[] itatitle = new byte[84];
            byte[] duttitle = new byte[84];

            for (int i = 0; i < 20; i++)
            {
                if (channeltitles[0].Length > i)
                {
                    japtitle[i * 2] = BitConverter.GetBytes(channeltitles[0][i])[1];
                    japtitle[i * 2 + 1] = BitConverter.GetBytes(channeltitles[0][i])[0];
                }
                if (channeltitles[1].Length > i)
                {
                    engtitle[i * 2] = BitConverter.GetBytes(channeltitles[1][i])[1];
                    engtitle[i * 2 + 1] = BitConverter.GetBytes(channeltitles[1][i])[0];
                }
                if (channeltitles[2].Length > i)
                {
                    gertitle[i * 2] = BitConverter.GetBytes(channeltitles[2][i])[1];
                    gertitle[i * 2 + 1] = BitConverter.GetBytes(channeltitles[2][i])[0];
                }
                if (channeltitles[3].Length > i)
                {
                    fratitle[i * 2] = BitConverter.GetBytes(channeltitles[3][i])[1];
                    fratitle[i * 2 + 1] = BitConverter.GetBytes(channeltitles[3][i])[0];
                }
                if (channeltitles[4].Length > i)
                {
                    spatitle[i * 2] = BitConverter.GetBytes(channeltitles[4][i])[1];
                    spatitle[i * 2 + 1] = BitConverter.GetBytes(channeltitles[4][i])[0];
                }
                if (channeltitles[5].Length > i)
                {
                    itatitle[i * 2] = BitConverter.GetBytes(channeltitles[5][i])[1];
                    itatitle[i * 2 + 1] = BitConverter.GetBytes(channeltitles[5][i])[0];
                }
                if (channeltitles[6].Length > i)
                {
                    duttitle[i * 2] = BitConverter.GetBytes(channeltitles[6][i])[1];
                    duttitle[i * 2 + 1] = BitConverter.GetBytes(channeltitles[6][i])[0];
                }
            }

            byte[] crypto = new byte[16];

            ms.Seek(128, SeekOrigin.Begin);
            ms.Write(imet, 0, imet.Length);
            ms.Write(unknown, 0, unknown.Length);
            ms.Write(iconsize, 0, iconsize.Length);
            ms.Write(bannersize, 0, bannersize.Length);
            ms.Write(soundsize, 0, soundsize.Length);

            ms.Seek(4, SeekOrigin.Current);
            ms.Write(japtitle, 0, japtitle.Length);
            ms.Write(engtitle, 0, engtitle.Length);
            ms.Write(gertitle, 0, gertitle.Length);
            ms.Write(fratitle, 0, fratitle.Length);
            ms.Write(spatitle, 0, spatitle.Length);
            ms.Write(itatitle, 0, itatitle.Length);
            ms.Write(duttitle, 0, duttitle.Length);

            ms.Seek(0x348, SeekOrigin.Current);
            ms.Write(crypto, 0, crypto.Length);

            byte[] tohash = ms.ToArray();
            crypto = md5.ComputeHash(tohash, 0x40, 0x600);

            ms.Seek(-16, SeekOrigin.Current);
            ms.Write(crypto, 0, crypto.Length);
            ms.Write(nullapp, 0, nullapp.Length);

            md5.Clear();
            return ms.ToArray();
        }

        /// <summary>
        /// Packs a U8 Archive
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="outfile"></param>
        public static void PackU8(string folder, string outfile)
        {
            byte[] u8 = PackU8(folder);

            using (FileStream fs = new FileStream(outfile, FileMode.Create))
                fs.Write(u8, 0, u8.Length);
        }

        /// <summary>
        /// Packs a U8 Archive
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="outfile"></param>
        public static void PackU8(string folder, string outfile, bool addimd5header)
        {
            byte[] u8 = PackU8(folder);

            if (addimd5header == true)
                u8 = AddHeaderIMD5(u8);

            using (FileStream fs = new FileStream(outfile, FileMode.Create))
                fs.Write(u8, 0, u8.Length);
        }

        /// <summary>
        /// Packs a U8 Archive
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="outfile"></param>
        public static byte[] PackU8(string folder)
        {
            int a = 0, b = 0, c = 0;
            return PackU8(folder, out a, out b, out c);
        }

        /// <summary>
        /// Packs a U8 Archive
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="outfile"></param>
        public static byte[] PackU8(string folder, bool addimd5header)
        {
            byte[] u8 = PackU8(folder);

            if (addimd5header == true)
                u8 = AddHeaderIMD5(u8);
            
            return u8;
        }

        /// <summary>
        /// Packs a U8 Archive
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="outfile"></param>
        public static byte[] PackU8(string folder, out int bannersize, out int iconsize, out int soundsize)
        {
            int datapad = 32, stringtablepad = 32; //Biggie seems to use these paddings, so let's do it, too ;)
            string rootpath = folder;
            if (rootpath[rootpath.Length - 1] != '\\') rootpath = rootpath + "\\";

            bannersize = 0; iconsize = 0; soundsize = 0;

            string[] files = GetDirContent(folder, true);
            int nodecount = files.Length + 1; //All files and dirs + rootnode
            int recursion = 0;
            int currentnodes = 0;
            string name = string.Empty;
            string stringtable = "\0";
            byte[] tempnode = new byte[12];

            MemoryStream nodes = new MemoryStream();
            MemoryStream data = new MemoryStream();
            BinaryWriter writedata = new BinaryWriter(data);

            tempnode[0] = 0x01;
            tempnode[1] = 0x00;
            tempnode[2] = 0x00;
            tempnode[3] = 0x00;
            tempnode[4] = 0x00;
            tempnode[5] = 0x00;
            tempnode[6] = 0x00;
            tempnode[7] = 0x00;

            byte[] temp = BitConverter.GetBytes((UInt32)files.Length + 1); Array.Reverse(temp);
            tempnode[8] = temp[0];
            tempnode[9] = temp[1];
            tempnode[10] = temp[2];
            tempnode[11] = temp[3];

            nodes.Write(tempnode, 0, tempnode.Length);

            for (int i = 0; i < files.Length; i++)
            {
                files[i] = files[i].Remove(0, rootpath.Length - 1);

                recursion = Tools.CountCharsInString(files[i], '\\') - 1;
                name = files[i].Remove(0, files[i].LastIndexOf('\\') + 1);

                byte[] temp1 = BitConverter.GetBytes((UInt16)stringtable.Length); Array.Reverse(temp1);
                tempnode[2] = temp1[0];
                tempnode[3] = temp1[1];

                stringtable += name + "\0";

                if (Directory.Exists(rootpath + files[i])) //It's a dir
                {
                    tempnode[0] = 0x01;
                    tempnode[1] = 0x00;

                    byte[] temp2 = BitConverter.GetBytes((UInt32)recursion); Array.Reverse(temp2);
                    tempnode[4] = temp2[0];
                    tempnode[5] = temp2[1];
                    tempnode[6] = temp2[2];
                    tempnode[7] = temp2[3];

                    int size = currentnodes + 1;

                    for (int j = i; j < files.Length; j++)
                        if (files[j].Contains(files[i])) size++;

                    byte[] temp3 = BitConverter.GetBytes((UInt32)size); Array.Reverse(temp3);
                    tempnode[8] = temp3[0];
                    tempnode[9] = temp3[1];
                    tempnode[10] = temp3[2];
                    tempnode[11] = temp3[3];
                }
                else //It's a file
                {
                    byte[] tempfile = new byte[0x40];
                    int lzoffset = -1;

                    if (files[i].EndsWith("banner.bin"))
                    {
                        tempfile = Wii.Tools.LoadFileToByteArray(rootpath + files[i], 0, tempfile.Length);

                        for (int x = 0; x < tempfile.Length; x++)
                        {
                           if (tempfile[x] == 'L')
                               if (tempfile[x + 1] == 'Z')
                                   if (tempfile[x + 2] == '7')
                                       if (tempfile[x + 3] == '7')
                                       {
                                           lzoffset = x;
                                           break;
                                       }
                        }

                        if (lzoffset != -1)
                        {
                            bannersize = BitConverter.ToInt32(new byte[] { tempfile[lzoffset + 5], tempfile[lzoffset + 6], tempfile[lzoffset + 7], tempfile[lzoffset + 8] }, 0);
                        }
                        else
                        {
                            FileInfo fibanner = new FileInfo(rootpath + files[i]);
                            bannersize = (int)fibanner.Length - 32;
                        }
                    }
                    else if (files[i].EndsWith("icon.bin"))
                    {tempfile = Wii.Tools.LoadFileToByteArray(rootpath + files[i], 0, tempfile.Length);

                        for (int x = 0; x < tempfile.Length; x++)
                        {
                           if (tempfile[x] == 'L')
                               if (tempfile[x + 1] == 'Z')
                                   if (tempfile[x + 2] == '7')
                                       if (tempfile[x + 3] == '7')
                                       {
                                           lzoffset = x;
                                       }
                        }

                        if (lzoffset != -1)
                        {
                            iconsize = BitConverter.ToInt32(new byte[] { tempfile[lzoffset + 5], tempfile[lzoffset + 6], tempfile[lzoffset + 7], tempfile[lzoffset + 8] }, 0);
                        }
                        else
                        {
                            FileInfo fiicon = new FileInfo(rootpath + files[i]);
                            iconsize = (int)fiicon.Length - 32;
                        }
                    }
                    else if (files[i].EndsWith("sound.bin"))
                    {tempfile = Wii.Tools.LoadFileToByteArray(rootpath + files[i], 0, tempfile.Length);

                        for (int x = 0; x < tempfile.Length; x++)
                        {
                            if (tempfile[x] == 'L')
                                if (tempfile[x + 1] == 'Z')                                
                                    if (tempfile[x + 2] == '7')
                                        if (tempfile[x + 3] == '7')
                                        {
                                            lzoffset = x;
                                            break;
                                        }
                        }

                        if (lzoffset != -1)
                        {
                            soundsize = BitConverter.ToInt32(new byte[] { tempfile[lzoffset + 5], tempfile[lzoffset + 6], tempfile[lzoffset + 7], tempfile[lzoffset + 8] }, 0);
                        }
                        else
                        {
                            FileInfo fisound = new FileInfo(rootpath + files[i]);
                            soundsize = (int)fisound.Length - 32;
                        }
                    }

                    tempnode[0] = 0x00;
                    tempnode[1] = 0x00;

                    byte[] temp2 = BitConverter.GetBytes((UInt32)data.Position); Array.Reverse(temp2);
                    tempnode[4] = temp2[0];
                    tempnode[5] = temp2[1];
                    tempnode[6] = temp2[2];
                    tempnode[7] = temp2[3];

                    FileInfo fi = new FileInfo(rootpath + files[i]);
                    byte[] temp3 = BitConverter.GetBytes((UInt32)fi.Length); Array.Reverse(temp3);
                    tempnode[8] = temp3[0];
                    tempnode[9] = temp3[1];
                    tempnode[10] = temp3[2];
                    tempnode[11] = temp3[3];

                    using (FileStream fs = new FileStream(rootpath + files[i], FileMode.Open))
                    using (BinaryReader br = new BinaryReader(fs))
                        writedata.Write(br.ReadBytes((int)br.BaseStream.Length));

                    writedata.Seek(Tools.AddPadding((int)data.Position, datapad), SeekOrigin.Begin);
                }

                nodes.Write(tempnode, 0, tempnode.Length);
                currentnodes++;
            }

            byte[] type = new byte[2];
            byte[] curpos = new byte[4];

            for (int x = 0; x < nodecount * 12; x += 12)
            {
                nodes.Seek(x, SeekOrigin.Begin);
                nodes.Read(type, 0, 2);

                if (type[0] == 0x00 && type[1] == 0x00)
                {
                    nodes.Seek(x + 4, SeekOrigin.Begin);
                    nodes.Read(curpos, 0, 4);
                    Array.Reverse(curpos);

                    UInt32 newpos = BitConverter.ToUInt32(curpos, 0) + (UInt32)(Tools.AddPadding(0x20 + ((12 * nodecount) + stringtable.Length), stringtablepad));

                    nodes.Seek(x + 4, SeekOrigin.Begin);
                    byte[] temp2 = BitConverter.GetBytes(newpos); Array.Reverse(temp2);
                    nodes.Write(temp2, 0, 4);
                }
            }

            writedata.Close();
            MemoryStream output = new MemoryStream();
            BinaryWriter writeout = new BinaryWriter(output);

            writeout.Write((UInt32)0x2d38aa55);
            writeout.Write(IPAddress.HostToNetworkOrder((ushort)0x20));
            writeout.Write(IPAddress.HostToNetworkOrder((ushort)((12 * nodecount) + stringtable.Length)));
            writeout.Write(IPAddress.HostToNetworkOrder((ushort)(Tools.AddPadding(0x20 + ((12 * nodecount) + stringtable.Length), stringtablepad))));

            writeout.Seek(0x10, SeekOrigin.Current);

            writeout.Write(nodes.ToArray());
            writeout.Write(ASCIIEncoding.ASCII.GetBytes(stringtable));

            writeout.Seek(Tools.AddPadding(0x20 + ((12 * nodecount) + stringtable.Length), stringtablepad), SeekOrigin.Begin);

            writeout.Write(data.ToArray());

            output.Seek(0, SeekOrigin.End);
            for (int i = (int)output.Position; i < Tools.AddPadding((int)output.Position, datapad); i++)
                output.WriteByte(0);

            writeout.Close();
            output.Close();

            return output.ToArray();
        }

        /// <summary>
        /// Unpacks the given U8 archive
        /// If the archive is Lz77 compressed, it will be decompressed first!
        /// </summary>
        /// <param name="u8archive"></param>
        /// <param name="unpackpath"></param>
        public static void UnpackU8(string u8archive, string unpackpath)
        {
            byte[] u8 = Wii.Tools.LoadFileToByteArray(u8archive);
            UnpackU8(u8, unpackpath);
        }

        /// <summary>
        /// Unpacks the given U8 archive
        /// If the archive is Lz77 compressed, it will be decompressed first!
        /// </summary>
        /// <param name="u8archive"></param>
        /// <param name="unpackpath"></param>
        public static void UnpackU8(byte[] u8archive, string unpackpath)
        {
            int lz77offset = Lz77.GetLz77Offset(u8archive);
            if (lz77offset != -1) { u8archive = Lz77.Decompress(u8archive, lz77offset); }

            if (unpackpath[unpackpath.Length - 1] != '\\') { unpackpath = unpackpath + "\\"; }
            if (!Directory.Exists(unpackpath)) Directory.CreateDirectory(unpackpath);

            int u8offset = -1;
            int length = 2500;
            if (u8archive.Length < length) length = u8archive.Length - 4;

            for (int i = 0; i < length; i++)
            {
                if (u8archive[i] == 0x55 && u8archive[i + 1] == 0xAA && u8archive[i + 2] == 0x38 && u8archive[i + 3] == 0x2D)
                {
                    u8offset = i;
                    break;
                }
            }
            
            if (u8offset == -1) throw new Exception("File is not a valid U8 Archive!");

            int nodecount = Tools.HexStringToInt(u8archive[u8offset + 0x28].ToString("x2") + u8archive[u8offset + 0x29].ToString("x2") + u8archive[u8offset + 0x2a].ToString("x2") + u8archive[u8offset + 0x2b].ToString("x2"));
            int nodeoffset = 0x20;

            string[,] nodes = new string[nodecount, 5];

            for (int j = 0; j < nodecount; j++)
            {
                nodes[j, 0] = u8archive[u8offset + nodeoffset].ToString("x2") + u8archive[u8offset + nodeoffset + 1].ToString("x2");
                nodes[j, 1] = u8archive[u8offset + nodeoffset + 2].ToString("x2") + u8archive[u8offset + nodeoffset + 3].ToString("x2");
                nodes[j, 2] = u8archive[u8offset + nodeoffset + 4].ToString("x2") + u8archive[u8offset + nodeoffset + 5].ToString("x2") + u8archive[u8offset + nodeoffset + 6].ToString("x2") + u8archive[u8offset + nodeoffset + 7].ToString("x2");
                nodes[j, 3] = u8archive[u8offset + nodeoffset + 8].ToString("x2") + u8archive[u8offset + nodeoffset + 9].ToString("x2") + u8archive[u8offset + nodeoffset + 10].ToString("x2") + u8archive[u8offset + nodeoffset + 11].ToString("x2");

                nodeoffset += 12;
            }

            int stringtablepos = u8offset + nodeoffset;

            for (int x = 0; x < nodecount; x++)
            {
                bool end = false;
                int nameoffset = Tools.HexStringToInt(nodes[x, 1]);
                string thisname = "";

                do
                {
                    if (u8archive[stringtablepos + nameoffset] != 0x00)
                    {
                        char tempchar = Convert.ToChar(u8archive[stringtablepos + nameoffset]);
                        thisname += tempchar.ToString();
                        nameoffset++;
                    }
                    else end = true;
                } while (end == false);

                nodes[x, 4] = thisname;
            }

            string[] dirs = new string[nodecount];
            dirs[0] = unpackpath;
            int[] dircount = new int[nodecount];
            int dirindex = 0;

            try
            {
                for (int y = 1; y < nodecount; y++)
                {
                    switch (nodes[y, 0])
                    {
                        case "0100":
                            if (dirs[dirindex][dirs[dirindex].Length - 1] != '\\') { dirs[dirindex] = dirs[dirindex] + "\\"; }
                            Directory.CreateDirectory(dirs[dirindex] + nodes[y, 4]);
                            dirs[dirindex + 1] = dirs[dirindex] + nodes[y, 4];
                            dirindex++;
                            dircount[dirindex] = Tools.HexStringToInt(nodes[y, 3]);
                            break;
                        default:
                            int filepos = u8offset + Tools.HexStringToInt(nodes[y, 2]);
                            int filesize = Tools.HexStringToInt(nodes[y, 3]);

                            using (FileStream fs = new FileStream(dirs[dirindex] + "\\" + nodes[y, 4], FileMode.Create))
                            {
                                fs.Write(u8archive, filepos, filesize);
                            }
                            break;
                    }

                    while (dirindex > 0 && dircount[dirindex] == (y + 1))
                    {
                        dirindex--;
                    }
                }
            }
            catch { }
        }

        /// <summary>
        /// Gets the Banner.bin out of the 00000000.app
        /// </summary>
        /// <param name="nullapp"></param>
        /// <returns></returns>
        public static byte[] GetBannerBin(byte[] nullapp)
        {
            int lz77offset = Lz77.GetLz77Offset(nullapp);
            if (lz77offset != -1) { nullapp = Lz77.Decompress(nullapp, lz77offset); }

            int u8offset = -1;
            for (int i = 0; i < 2500; i++)
            {
                if (nullapp[i] == 0x55 && nullapp[i + 1] == 0xAA && nullapp[i + 2] == 0x38 && nullapp[i + 3] == 0x2D)
                {
                    u8offset = i;
                    break;
                }
            }

            if (u8offset == -1) throw new Exception("File is not a valid U8 Archive!");

            int nodecount = Tools.HexStringToInt(nullapp[u8offset + 0x28].ToString("x2") + nullapp[u8offset + 0x29].ToString("x2") + nullapp[u8offset + 0x2a].ToString("x2") + nullapp[u8offset + 0x2b].ToString("x2"));
            int nodeoffset = 0x20;

            string[,] nodes = new string[nodecount, 5];

            for (int j = 0; j < nodecount; j++)
            {
                nodes[j, 0] = nullapp[u8offset + nodeoffset].ToString("x2") + nullapp[u8offset + nodeoffset + 1].ToString("x2");
                nodes[j, 1] = nullapp[u8offset + nodeoffset + 2].ToString("x2") + nullapp[u8offset + nodeoffset + 3].ToString("x2");
                nodes[j, 2] = nullapp[u8offset + nodeoffset + 4].ToString("x2") + nullapp[u8offset + nodeoffset + 5].ToString("x2") + nullapp[u8offset + nodeoffset + 6].ToString("x2") + nullapp[u8offset + nodeoffset + 7].ToString("x2");
                nodes[j, 3] = nullapp[u8offset + nodeoffset + 8].ToString("x2") + nullapp[u8offset + nodeoffset + 9].ToString("x2") + nullapp[u8offset + nodeoffset + 10].ToString("x2") + nullapp[u8offset + nodeoffset + 11].ToString("x2");

                nodeoffset += 12;
            }

            int stringtablepos = u8offset + nodeoffset;

            for (int x = 0; x < nodecount; x++)
            {
                bool end = false;
                int nameoffset = Tools.HexStringToInt(nodes[x, 1]);
                string thisname = "";

                while (end == false)
                {
                    if (nullapp[stringtablepos + nameoffset] != 0x00)
                    {
                        char tempchar = Convert.ToChar(nullapp[stringtablepos + nameoffset]);
                        thisname += tempchar.ToString();
                        nameoffset++;
                    }
                    else end = true;
                }

                nodes[x, 4] = thisname;
            }

            for (int y = 1; y < nodecount; y++)
            {
                if (nodes[y, 4] == "banner.bin")
                {
                    int filepos = u8offset + Tools.HexStringToInt(nodes[y, 2]);
                    int filesize = Tools.HexStringToInt(nodes[y, 3]);

                    MemoryStream ms = new MemoryStream(nullapp);
                    byte[] banner = new byte[filesize];
                    ms.Seek(filepos, SeekOrigin.Begin);
                    ms.Read(banner, 0, filesize);
                    ms.Close();

                    return banner;
                }
            }

            throw new Exception("This file doesn't contain banner.bin!");
        }

        /// <summary>
        /// Gets the Icon.bin out of the 00000000.app
        /// </summary>
        /// <param name="nullapp"></param>
        /// <returns></returns>
        public static byte[] GetIconBin(byte[] nullapp)
        {
            int lz77offset = Lz77.GetLz77Offset(nullapp);
            if (lz77offset != -1) { nullapp = Lz77.Decompress(nullapp, lz77offset); }

            int u8offset = -1;
            for (int i = 0; i < 2500; i++)
            {
                if (nullapp[i] == 0x55 && nullapp[i + 1] == 0xAA && nullapp[i + 2] == 0x38 && nullapp[i + 3] == 0x2D)
                {
                    u8offset = i;
                    break;
                }
            }

            if (u8offset == -1) throw new Exception("File is not a valid U8 Archive!");

            int nodecount = Tools.HexStringToInt(nullapp[u8offset + 0x28].ToString("x2") + nullapp[u8offset + 0x29].ToString("x2") + nullapp[u8offset + 0x2a].ToString("x2") + nullapp[u8offset + 0x2b].ToString("x2"));
            int nodeoffset = 0x20;

            string[,] nodes = new string[nodecount, 5];

            for (int j = 0; j < nodecount; j++)
            {
                nodes[j, 0] = nullapp[u8offset + nodeoffset].ToString("x2") + nullapp[u8offset + nodeoffset + 1].ToString("x2");
                nodes[j, 1] = nullapp[u8offset + nodeoffset + 2].ToString("x2") + nullapp[u8offset + nodeoffset + 3].ToString("x2");
                nodes[j, 2] = nullapp[u8offset + nodeoffset + 4].ToString("x2") + nullapp[u8offset + nodeoffset + 5].ToString("x2") + nullapp[u8offset + nodeoffset + 6].ToString("x2") + nullapp[u8offset + nodeoffset + 7].ToString("x2");
                nodes[j, 3] = nullapp[u8offset + nodeoffset + 8].ToString("x2") + nullapp[u8offset + nodeoffset + 9].ToString("x2") + nullapp[u8offset + nodeoffset + 10].ToString("x2") + nullapp[u8offset + nodeoffset + 11].ToString("x2");

                nodeoffset += 12;
            }

            int stringtablepos = u8offset + nodeoffset;

            for (int x = 0; x < nodecount; x++)
            {
                bool end = false;
                int nameoffset = Tools.HexStringToInt(nodes[x, 1]);
                string thisname = "";

                while (end == false)
                {
                    if (nullapp[stringtablepos + nameoffset] != 0x00)
                    {
                        char tempchar = Convert.ToChar(nullapp[stringtablepos + nameoffset]);
                        thisname += tempchar.ToString();
                        nameoffset++;
                    }
                    else end = true;
                }

                nodes[x, 4] = thisname;
            }

            for (int y = 1; y < nodecount; y++)
            {
                if (nodes[y, 4] == "icon.bin")
                {
                    int filepos = u8offset + Tools.HexStringToInt(nodes[y, 2]);
                    int filesize = Tools.HexStringToInt(nodes[y, 3]);

                    MemoryStream ms = new MemoryStream(nullapp);
                    byte[] icon = new byte[filesize];
                    ms.Seek(filepos, SeekOrigin.Begin);
                    ms.Read(icon, 0, filesize);
                    ms.Close();

                    return icon;
                }
            }

            throw new Exception("This file doesn't contain icon.bin!");
        }

        /// <summary>
        /// Extracts all Tpl's to the given path
        /// </summary>
        /// <param name="u8archive"></param>
        /// <param name="path"></param>
        public static void UnpackTpls(byte[] u8archive, string unpackpath)
        {
            int lz77offset = Lz77.GetLz77Offset(u8archive);
            if (lz77offset != -1) { u8archive = Lz77.Decompress(u8archive, lz77offset); }

            if (unpackpath[unpackpath.Length - 1] != '\\') { unpackpath = unpackpath + "\\"; }
            if (!Directory.Exists(unpackpath)) Directory.CreateDirectory(unpackpath);

            int u8offset = -1;
            int length = 2500;
            if (u8archive.Length < 2500) length = u8archive.Length - 4;

            for (int i = 0; i < 2500; i++)
            {
                if (u8archive[i] == 0x55 && u8archive[i + 1] == 0xAA && u8archive[i + 2] == 0x38 && u8archive[i + 3] == 0x2D)
                {
                    u8offset = i;
                    break;
                }
            }

            if (u8offset == -1) throw new Exception("File is not a valid U8 Archive!");

            int nodecount = Tools.HexStringToInt(u8archive[u8offset + 0x28].ToString("x2") + u8archive[u8offset + 0x29].ToString("x2") + u8archive[u8offset + 0x2a].ToString("x2") + u8archive[u8offset + 0x2b].ToString("x2"));
            int nodeoffset = 0x20;

            string[,] nodes = new string[nodecount, 5];

            for (int j = 0; j < nodecount; j++)
            {
                nodes[j, 0] = u8archive[u8offset + nodeoffset].ToString("x2") + u8archive[u8offset + nodeoffset + 1].ToString("x2");
                nodes[j, 1] = u8archive[u8offset + nodeoffset + 2].ToString("x2") + u8archive[u8offset + nodeoffset + 3].ToString("x2");
                nodes[j, 2] = u8archive[u8offset + nodeoffset + 4].ToString("x2") + u8archive[u8offset + nodeoffset + 5].ToString("x2") + u8archive[u8offset + nodeoffset + 6].ToString("x2") + u8archive[u8offset + nodeoffset + 7].ToString("x2");
                nodes[j, 3] = u8archive[u8offset + nodeoffset + 8].ToString("x2") + u8archive[u8offset + nodeoffset + 9].ToString("x2") + u8archive[u8offset + nodeoffset + 10].ToString("x2") + u8archive[u8offset + nodeoffset + 11].ToString("x2");

                nodeoffset += 12;
            }

            int stringtablepos = u8offset + nodeoffset;

            for (int x = 0; x < nodecount; x++)
            {
                bool end = false;
                int nameoffset = Tools.HexStringToInt(nodes[x, 1]);
                string thisname = "";

                while (end == false)
                {
                    if (u8archive[stringtablepos + nameoffset] != 0x00)
                    {
                        char tempchar = Convert.ToChar(u8archive[stringtablepos + nameoffset]);
                        thisname += tempchar.ToString();
                        nameoffset++;
                    }
                    else end = true;
                }

                nodes[x, 4] = thisname;
            }

            for (int y = 1; y < nodecount; y++)
            {
                if (nodes[y, 4].Contains("."))
                {
                    if (nodes[y, 4].Remove(0, nodes[y, 4].LastIndexOf('.')) == ".tpl")
                    {
                        int filepos = u8offset + Tools.HexStringToInt(nodes[y, 2]);
                        int filesize = Tools.HexStringToInt(nodes[y, 3]);

                        using (FileStream fs = new FileStream(unpackpath + nodes[y, 4], FileMode.Create))
                        {
                            fs.Write(u8archive, filepos, filesize);
                        }
                    }
                }
            }
        }
    }

    public class Lz77
    {
        private const int N = 4096;
        private const int F = 18;
        private const int threshold = 2;
        private static int[] lson = new int[N + 1];
        private static int[] rson = new int[N + 257];
        private static int[] dad = new int[N + 1];
        private static ushort[] text_buf = new ushort[N + 17];
        private static int match_position = 0, match_length = 0;
        private static int textsize = 0;
        private static int codesize = 0;

        /// <summary>
        /// Returns the Offset to the Lz77 Header
        /// -1 will be returned, if the file is not Lz77 compressed
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static int GetLz77Offset(byte[] data)
        {
            int length = 5000;
            if (data.Length < 5000) length = data.Length - 4;

            for (int i = 0; i < length; i++)
            {
                if (data[i] == 0x55 && data[i + 1] == 0xAA && data[i + 2] == 0x38 && data[i + 3] == 0x2D)
                {
                    break;
                }

                UInt32 tmp = BitConverter.ToUInt32(data, i);
                if (tmp == 0x37375a4c) return i;
            }

            return -1;
        }

        /// <summary>
        /// Decompresses the given file
        /// </summary>
        /// <param name="infile"></param>
        /// <param name="outfile"></param>
        public static void Decompress(string infile, string outfile)
        {
            byte[] input = Tools.LoadFileToByteArray(infile);
            int offset = GetLz77Offset(input);
            if (offset == -1) throw new Exception("File is not Lz77 compressed!");
            Tools.SaveFileFromByteArray(Decompress(input, offset), outfile);
        }

        /// <summary>
        /// Decompresses the given data
        /// </summary>
        /// <param name="compressed"></param>
        /// <param name="offset"></param>
        /// <returns></returns>
        public static byte[] Decompress(byte[] compressed, int offset)
        {
            int i, j, k, r, c, z;
            uint flags;
            UInt32 decomp_size;
            UInt32 cur_size = 0;

            MemoryStream infile = new MemoryStream(compressed);
            MemoryStream outfile = new MemoryStream();

            UInt32 gbaheader = new UInt32();
            byte[] temp = new byte[4];
            infile.Seek(offset + 4, SeekOrigin.Begin);
            infile.Read(temp, 0, 4);
            gbaheader = BitConverter.ToUInt32(temp, 0);

            decomp_size = gbaheader >> 8;
            byte[] text_buf = new byte[N + 17];

            for (i = 0; i < N - F; i++) text_buf[i] = 0xdf;
            r = N - F; flags = 7; z = 7;

            while (true)
            {
                flags <<= 1;
                z++;
                if (z == 8)
                {
                    if ((c = (char)infile.ReadByte()) == -1) break;
                    flags = (uint)c;
                    z = 0;
                }
                if ((flags & 0x80) == 0)
                {
                    if ((c = infile.ReadByte()) == infile.Length - 1) break;
                    if (cur_size < decomp_size) outfile.WriteByte((byte)c);
                    text_buf[r++] = (byte)c;
                    r &= (N - 1);
                    cur_size++;
                }
                else
                {
                    if ((i = infile.ReadByte()) == -1) break;
                    if ((j = infile.ReadByte()) == -1) break;
                    j = j | ((i << 8) & 0xf00);
                    i = ((i >> 4) & 0x0f) + threshold;
                    for (k = 0; k <= i; k++)
                    {
                        c = text_buf[(r - j - 1) & (N - 1)];
                        if (cur_size < decomp_size) outfile.WriteByte((byte)c); text_buf[r++] = (byte)c; r &= (N - 1); cur_size++;
                    }
                }
            }

            return outfile.ToArray();
        }

        public static void InitTree()
        {
            int i;
            for (i = N + 1; i <= N + 256; i++) rson[i] = N;
            for (i = 0; i < N; i++) dad[i] = N;
        }

        public static void InsertNode(int r)
        {
            int i, p, cmp;
            cmp = 1;
            p = N + 1 + (text_buf[r] == 0xffff ? 0 : text_buf[r]); //text_buf[r];
            rson[r] = lson[r] = N; match_length = 0;
            for (; ; )
            {
                if (cmp >= 0)
                {
                    if (rson[p] != N) p = rson[p];
                    else { rson[p] = r; dad[r] = p; return; }
                }
                else
                {
                    if (lson[p] != N) p = lson[p];
                    else { lson[p] = r; dad[r] = p; return; }
                }
                for (i = 1; i < F; i++)
                    if ((cmp = text_buf[r + i] - text_buf[p + i]) != 0) break;
                if (i > match_length)
                {
                    match_position = p;
                    if ((match_length = i) >= F) break;
                }
            }
            dad[r] = dad[p]; lson[r] = lson[p]; rson[r] = rson[p];
            dad[lson[p]] = r; dad[rson[p]] = r;
            if (rson[dad[p]] == p) rson[dad[p]] = r;
            else lson[dad[p]] = r;
            dad[p] = N;
        }

        public static void DeleteNode(int p)
        {
            int q;

            if (dad[p] == N) return;  /* not in tree */
            if (rson[p] == N) q = lson[p];
            else if (lson[p] == N) q = rson[p];
            else
            {
                q = lson[p];
                if (rson[q] != N)
                {
                    do { q = rson[q]; } while (rson[q] != N);
                    rson[dad[q]] = lson[q]; dad[lson[q]] = dad[q];
                    lson[q] = lson[p]; dad[lson[p]] = q;
                }
                rson[q] = rson[p]; dad[rson[p]] = q;
            }
            dad[q] = dad[p];
            if (rson[dad[p]] == p) rson[dad[p]] = q; else lson[dad[p]] = q;
            dad[p] = N;
        }

        /// <summary>
        /// Lz77 compresses the given File
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static void Compress(string infile, string outfile)
        {
            byte[] thisfile = Tools.LoadFileToByteArray(infile);
            thisfile = Compress(thisfile);
            Tools.SaveFileFromByteArray(thisfile, outfile);
        }

        /// <summary>
        /// Lz77 compresses the given and saves it to the given Path
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static void Compress(byte[] file, string outfile)
        {
            byte[] temp = Compress(file);
            Tools.SaveFileFromByteArray(temp, outfile);
        }

        /// <summary>
        /// Lz77 compresses the given Byte Array
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public static byte[] Compress(byte[] file)
        {
            int i, c, len, r, s, last_match_length, code_buf_ptr;
            int[] code_buf = new int[17];
            int mask;
            UInt32 filesize = ((Convert.ToUInt32(file.Length)) << 8) + 0x10;
            byte[] filesizebytes = BitConverter.GetBytes(filesize);

            MemoryStream output = new MemoryStream();
            output.WriteByte((byte)'L'); output.WriteByte((byte)'Z'); output.WriteByte((byte)'7'); output.WriteByte((byte)'7');
            MemoryStream infile = new MemoryStream(file);

            output.Write(filesizebytes, 0, filesizebytes.Length);

            InitTree();
            code_buf[0] = 0;
            code_buf_ptr = 1;
            mask = 0x80;
            s = 0;
            r = N - F;

            for (i = s; i < r; i++) text_buf[i] = 0xffff;
            for (len = 0; len < F && (c = (int)infile.ReadByte()) != -1; len++)
                text_buf[r + len] = (ushort)c;

            if ((textsize = len) == 0) return file;

            for (i = 1; i <= F; i++) InsertNode(r - i);

            InsertNode(r);

            do
            {
                if (match_length > len) match_length = len;

                if (match_length <= threshold)
                {
                    match_length = 1;
                    code_buf[code_buf_ptr++] = text_buf[r];
                }
                else
                {
                    code_buf[0] |= mask;

                    code_buf[code_buf_ptr++] = (char)
                        (((r - match_position - 1) >> 8) & 0x0f) |
                        ((match_length - (threshold + 1)) << 4);

                    code_buf[code_buf_ptr++] = (char)((r - match_position - 1) & 0xff);
                }
                if ((mask >>= 1) == 0)
                {
                    for (i = 0; i < code_buf_ptr; i++)
                        output.WriteByte((byte)code_buf[i]);
                    codesize += code_buf_ptr;
                    code_buf[0] = 0; code_buf_ptr = 1;
                    mask = 0x80;
                }

                last_match_length = match_length;
                for (i = 0; i < last_match_length &&
                        (c = (int)infile.ReadByte()) != -1; i++)
                {
                    DeleteNode(s);
                    text_buf[s] = (ushort)c;
                    if (s < F - 1) text_buf[s + N] = (ushort)c;
                    s = (s + 1) & (N - 1); r = (r + 1) & (N - 1);
                    InsertNode(r);
                }

                while (i++ < last_match_length)
                {
                    DeleteNode(s);
                    s = (s + 1) & (N - 1); r = (r + 1) & (N - 1);
                    if (--len != 0) InsertNode(r);
                }
            } while (len > 0);


            if (code_buf_ptr > 1)
            {
                for (i = 0; i < code_buf_ptr; i++) output.WriteByte((byte)code_buf[i]);
                codesize += code_buf_ptr;
            }

            if (codesize % 4 != 0)
                for (i = 0; i < 4 - (codesize % 4); i++)
                    output.WriteByte(0x00);

            infile.Close();
            return output.ToArray();
        }
    }

    public class TPL
    {
        /// <summary>
        /// Converts a Tpl to a Bitmap
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static Bitmap ConvertFromTPL(string tpl)
        {
            byte[] tplarray = Wii.Tools.LoadFileToByteArray(tpl);
            return ConvertFromTPL(tplarray);
        }

        /// <summary>
        /// Converts a Tpl to a Bitmap
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static Bitmap ConvertFromTPL(byte[] tpl)
        {
            if (GetTextureCount(tpl) > 1) throw new Exception("Tpl's containing more than one Texture are not supported!");

            int width = GetTextureWidth(tpl);
            int height = GetTextureHeight(tpl);
            int format = GetTextureFormat(tpl);
            if (format == -1) throw new Exception("The Texture has an unsupported format!");

            switch (format)
            {
                case 0:
                    byte[] temp0 = FromI4(tpl);
                    return ConvertPixelToBitmap(temp0, width, height);
                case 1:
                    byte[] temp1 = FromI8(tpl);
                    return ConvertPixelToBitmap(temp1, width, height);
                case 2:
                    byte[] temp2 = FromIA4(tpl);
                    return ConvertPixelToBitmap(temp2, width, height);
                case 3:
                    byte[] temp3 = FromIA8(tpl);
                    return ConvertPixelToBitmap(temp3, width, height);
                case 4:
                    byte[] temp4 = FromRGB565(tpl);
                    return ConvertPixelToBitmap(temp4, width, height);
                case 5:
                    byte[] temp5 = FromRGB5A3(tpl);
                    return ConvertPixelToBitmap(temp5, width, height);
                case 6:
                    byte[] temp6 = FromRGBA8(tpl);
                    return ConvertPixelToBitmap(temp6, width, height);
                case 14:
                    byte[] temp14 = FromCMP(tpl);
                    return ConvertPixelToBitmap(temp14, width, height);
                default:
                    throw new Exception("The Texture has an unsupported format!");
            }
        }

        /// <summary>
        /// Converts the Pixel Data into a Png Image
        /// </summary>
        /// <param name="data">Byte array with pixel data</param>
        public static System.Drawing.Bitmap ConvertPixelToBitmap(byte[] data, int width, int height)
        {
            if (width == 0) width = 1;
            if (height == 0) height = 1;

            System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(
                                 new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
                                 System.Drawing.Imaging.ImageLockMode.WriteOnly, bmp.PixelFormat);

            System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
            bmp.UnlockBits(bmpData);
            return bmp;
        }

        /// <summary>
        /// Gets the Number of Textures in a Tpl
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static int GetTextureCount(byte[] tpl)
        {
            byte[] tmp = new byte[4];
            tmp[3] = tpl[4];
            tmp[2] = tpl[5];
            tmp[1] = tpl[6];
            tmp[0] = tpl[7];
            UInt32 count = BitConverter.ToUInt32(tmp, 0);
            return (int)count;
        }

        /// <summary>
        /// Gets the Format of the Texture in the Tpl
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static int GetTextureFormat(string tpl)
        {
            byte[] temp = Tools.LoadFileToByteArray(tpl, 0, 50);
            return GetTextureFormat(temp);
        }

        /// <summary>
        /// Gets the Format of the Texture in the Tpl
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static int GetTextureFormat(byte[] tpl)
        {
            byte[] tmp = new byte[4];
            tmp[3] = tpl[24];
            tmp[2] = tpl[25];
            tmp[1] = tpl[26];
            tmp[0] = tpl[27];
            UInt32 format = BitConverter.ToUInt32(tmp, 0);

            if (format == 0 ||
                format == 1 ||
                format == 2 ||
                format == 3 ||
                format == 4 ||
                format == 5 ||
                format == 6 ||
                format == 14) return (int)format;

            else return -1; //Unsupported Format
        }

        /// <summary>
        /// Gets the Format Name of the Texture in the Tpl
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static string GetTextureFormatName(byte[] tpl)
        {
            switch (GetTextureFormat(tpl))
            {
                case 0:
                    return "I4";
                case 1:
                    return "I8";
                case 2:
                    return "IA4";
                case 3:
                    return "IA8";
                case 4:
                    return "RGB565";
                case 5:
                    return "RGB5A3";
                case 6:
                    return "RGBA8";
                case 14:
                    return "CMP";
                default:
                    return "Unknown";
            }
        }

        public static int avg(int w0, int w1, int c0, int c1)
        {
            int a0 = c0 >> 11;
            int a1 = c1 >> 11;
            int a = (w0 * a0 + w1 * a1) / (w0 + w1);
            int c = (a << 11) & 0xffff;

            a0 = (c0 >> 5) & 63;
            a1 = (c1 >> 5) & 63;
            a = (w0 * a0 + w1 * a1) / (w0 + w1);
            c = c | ((a << 5) & 0xffff);

            a0 = c0 & 31;
            a1 = c1 & 31;
            a = (w0 * a0 + w1 * a1) / (w0 + w1);
            c = c | a;

            return c;
        }

        /// <summary>
        /// Gets the Width of the Texture in the Tpl
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static int GetTextureWidth(byte[] tpl)
        {
            byte[] tmp = new byte[2];
            tmp[1] = tpl[22];
            tmp[0] = tpl[23];
            UInt16 width = BitConverter.ToUInt16(tmp, 0);
            return (int)width;
        }

        /// <summary>
        /// Gets the Height of the Texture in the Tpl
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static int GetTextureHeight(byte[] tpl)
        {
            byte[] tmp = new byte[2];
            tmp[1] = tpl[20];
            tmp[0] = tpl[21];
            UInt16 height = BitConverter.ToUInt16(tmp, 0);
            return (int)height;
        }

        /// <summary>
        /// Gets the offset to the Texturedata in the Tpl
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static int GetTextureOffset(byte[] tpl)
        {
            byte[] tmp = new byte[4];
            tmp[3] = tpl[28];
            tmp[2] = tpl[29];
            tmp[1] = tpl[30];
            tmp[0] = tpl[31];
            UInt32 offset = BitConverter.ToUInt32(tmp, 0);
            return (int)offset;
        }

        /// <summary>
        /// Converts RGBA8 Tpl Array to RGBA Byte Array
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static byte[] FromRGBA8(byte[] tpl)
        {
            int width = GetTextureWidth(tpl);
            int height = GetTextureHeight(tpl);
            int offset = GetTextureOffset(tpl);
            UInt32[] output = new UInt32[width * height];
            int inp = 0;
            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 4)
                {
                    for (int k = 0; k < 2; k++)
                    {
                        for (int y1 = y; y1 < y + 4; y1++)
                        {
                            for (int x1 = x; x1 < x + 4; x1++)
                            {
                                byte[] pixelbytes = new byte[2];
                                pixelbytes[1] = tpl[offset + inp * 2];
                                pixelbytes[0] = tpl[offset + inp * 2 + 1];
                                UInt16 pixel = BitConverter.ToUInt16(pixelbytes, 0);
                                inp++;

                                if ((x1 >= width) || (y1 >= height))
                                    continue;

                                if (k == 0)
                                {
                                    int a = (pixel >> 8) & 0xff;
                                    int r = (pixel >> 0) & 0xff;
                                    output[x1 + (y1 * width)] |= (UInt32)((r << 16) | (a << 24));
                                }
                                else
                                {
                                    int g = (pixel >> 8) & 0xff;
                                    int b = (pixel >> 0) & 0xff;
                                    output[x1 + (y1 * width)] |= (UInt32)((g << 8) | (b << 0));
                                }
                            }
                        }
                    }
                }
            }

            return Tools.UInt32ArrayToByteArray(output);
        }

        /// <summary>
        /// Converts RGB5A3 Tpl Array to RGBA Byte Array
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static byte[] FromRGB5A3(byte[] tpl)
        {
            int width = GetTextureWidth(tpl);
            int height = GetTextureHeight(tpl);
            int offset = GetTextureOffset(tpl);
            UInt32[] output = new UInt32[width * height];
            int inp = 0;
            int r, g, b;
            int a = 0;
            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 4)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 4; x1++)
                        {
                            byte[] pixelbytes = new byte[2];
                            pixelbytes[1] = tpl[offset + inp * 2];
                            pixelbytes[0] = tpl[offset + inp * 2 + 1];
                            UInt16 pixel = BitConverter.ToUInt16(pixelbytes, 0);
                            inp++;

                            if (y1 >= height || x1 >= width)
                                continue;

                            if ((pixel & (1 << 15)) != 0)
                            {
                                b = (((pixel >> 10) & 0x1F) * 255) / 31;
                                g = (((pixel >> 5) & 0x1F) * 255) / 31;
                                r = (((pixel >> 0) & 0x1F) * 255) / 31;
                                a = 255;
                            }
                            else
                            {
                                a = (((pixel >> 12) & 0x07) * 255) / 7;
                                b = (((pixel >> 8) & 0x0F) * 255) / 15;
                                g = (((pixel >> 4) & 0x0F) * 255) / 15;
                                r = (((pixel >> 0) & 0x0F) * 255) / 15;
                            }

                            int rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
                            output[(y1 * width) + x1] = (UInt32)rgba;
                        }
                    }
                }
            }

            return Tools.UInt32ArrayToByteArray(output);
        }

        /// <summary>
        /// Converts RGB565 Tpl Array to RGBA Byte Array
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static byte[] FromRGB565(byte[] tpl)
        {
            int width = GetTextureWidth(tpl);
            int height = GetTextureHeight(tpl);
            int offset = GetTextureOffset(tpl);
            UInt32[] output = new UInt32[width * height];
            int inp = 0;
            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 4)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 4; x1++)
                        {
                            byte[] pixelbytes = new byte[2];
                            pixelbytes[1] = tpl[offset + inp * 2];
                            pixelbytes[0] = tpl[offset + inp * 2 + 1];
                            UInt16 pixel = BitConverter.ToUInt16(pixelbytes, 0);
                            inp++;

                            if (y1 >= height || x1 >= width)
                                continue;

                            int b = (((pixel >> 11) & 0x1F) << 3) & 0xff;
                            int g = (((pixel >> 5) & 0x3F) << 2) & 0xff;
                            int r = (((pixel >> 0) & 0x1F) << 3) & 0xff;
                            int a = 255;

                            int rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
                            output[y1 * width + x1] = (UInt32)rgba;
                        }
                    }
                }
            }

            return Tools.UInt32ArrayToByteArray(output);
        }

        /// <summary>
        /// Converts I4 Tpl Array to RGBA Byte Array
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static byte[] FromI4(byte[] tpl)
        {
            int width = GetTextureWidth(tpl);
            int height = GetTextureHeight(tpl);
            int offset = GetTextureOffset(tpl);
            UInt32[] output = new UInt32[width * height];
            int inp = 0;
            for (int y = 0; y < height; y += 8)
            {
                for (int x = 0; x < width; x += 8)
                {
                    for (int y1 = y; y1 < y + 8; y1++)
                    {
                        for (int x1 = x; x1 < x + 8; x1 += 2)
                        {
                            int pixel = tpl[offset + inp];

                            if (y1 >= height || x1 >= width)
                                continue;

                            int r = (pixel >> 4) * 255 / 15;
                            int g = (pixel >> 4) * 255 / 15;
                            int b = (pixel >> 4) * 255 / 15;
                            int a = (pixel >> 4) * 255 / 15;

                            int rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
                            output[y1 * width + x1] = (UInt32)rgba;

                            pixel = tpl[offset + inp];
                            inp++;

                            if (y1 >= height || x1 >= width)
                                continue;

                            r = (pixel & 0x0F) * 255 / 15;
                            g = (pixel & 0x0F) * 255 / 15;
                            b = (pixel & 0x0F) * 255 / 15;
                            a = (pixel & 0x0F) * 255 / 15;

                            rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
                            output[y1 * width + x1 + 1] = (UInt32)rgba;
                        }
                    }
                }
            }

            return Tools.UInt32ArrayToByteArray(output);
        }

        /// <summary>
        /// Converts IA4 Tpl Array to RGBA Byte Array
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static byte[] FromIA4(byte[] tpl)
        {
            int width = GetTextureWidth(tpl);
            int height = GetTextureHeight(tpl);
            int offset = GetTextureOffset(tpl);
            UInt32[] output = new UInt32[width * height];
            int inp = 0;
            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 8)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 8; x1++)
                        {
                            int pixel = tpl[offset + inp];
                            inp++;

                            if (y1 >= height || x1 >= width)
                                continue;

                            int r = ((pixel & 0x0F) * 255 / 15) & 0xff;
                            int g = ((pixel & 0x0F) * 255 / 15) & 0xff;
                            int b = ((pixel & 0x0F) * 255 / 15) & 0xff;
                            int a = (((pixel >> 4) * 255) / 15) & 0xff;

                            int rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
                            output[y1 * width + x1] = (UInt32)rgba;
                        }
                    }
                }
            }

            return Tools.UInt32ArrayToByteArray(output);
        }

        /// <summary>
        /// Converts I8 Tpl Array to RGBA Byte Array
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static byte[] FromI8(byte[] tpl)
        {
            int width = GetTextureWidth(tpl);
            int height = GetTextureHeight(tpl);
            int offset = GetTextureOffset(tpl);
            UInt32[] output = new UInt32[width * height];
            int inp = 0;
            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 8)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 8; x1++)
                        {
                            int pixel = tpl[offset + inp];
                            inp++;

                            if (y1 >= height || x1 >= width)
                                continue;

                            int r = pixel;
                            int g = pixel;
                            int b = pixel;
                            int a = 255;

                            int rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
                            output[y1 * width + x1] = (UInt32)rgba;
                        }
                    }
                }
            }

            return Tools.UInt32ArrayToByteArray(output);
        }

        /// <summary>
        /// Converts IA8 Tpl Array to RGBA Byte Array
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static byte[] FromIA8(byte[] tpl)
        {
            int width = GetTextureWidth(tpl);
            int height = GetTextureHeight(tpl);
            int offset = GetTextureOffset(tpl);
            UInt32[] output = new UInt32[width * height];
            int inp = 0;
            for (int y = 0; y < height; y += 4)
            {
                for (int x = 0; x < width; x += 4)
                {
                    for (int y1 = y; y1 < y + 4; y1++)
                    {
                        for (int x1 = x; x1 < x + 4; x1++)
                        {
                            byte[] pixelbytes = new byte[2];
                            pixelbytes[1] = tpl[offset + inp * 2];
                            pixelbytes[0] = tpl[offset + inp * 2 + 1];
                            UInt16 pixel = BitConverter.ToUInt16(pixelbytes, 0);
                            inp++;

                            if (y1 >= height || x1 >= width)
                                continue;

                            int r = (pixel >> 8);// &0xff;
                            int g = (pixel >> 8);// &0xff;
                            int b = (pixel >> 8);// &0xff;
                            int a = (pixel >> 8) & 0xff;

                            int rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
                            output[y1 * width + x1] = (UInt32)rgba;
                        }
                    }
                }
            }

            return Tools.UInt32ArrayToByteArray(output);
        }

        /// <summary>
        /// Converts CMP Tpl Array to RGBA Byte Array
        /// </summary>
        /// <param name="tpl"></param>
        /// <returns></returns>
        public static byte[] FromCMP(byte[] tpl)
        {
            int width = GetTextureWidth(tpl);
            int height = GetTextureHeight(tpl);
            int offset = GetTextureOffset(tpl);
            UInt32[] output = new UInt32[width * height];
            UInt16[] c = new UInt16[4];
            int[] pix = new int[3];
            int inp = 0;

            for (int y = 0; y < height; y++)
            {
                for (int x = 0; x < width; x++)
                {
                    int ww = Tools.AddPadding(width, 8);

                    int x0 = x & 0x03;
                    int x1 = (x >> 2) & 0x01;
                    int x2 = x >> 3;

                    int y0 = y & 0x03;
                    int y1 = (y >> 2) & 0x01;
                    int y2 = y >> 3;

                    int off = (8 * x1) + (16 * y1) + (32 * x2) + (4 * ww * y2);

                    byte[] tmp1 = new byte[2];
                    tmp1[1] = tpl[offset + off];
                    tmp1[0] = tpl[offset + off + 1];
                    c[0] = BitConverter.ToUInt16(tmp1, 0);
                    tmp1[1] = tpl[offset + off + 2];
                    tmp1[0] = tpl[offset + off + 3];
                    c[1] = BitConverter.ToUInt16(tmp1, 0);

                    if (c[0] > c[1])
                    {
                        c[2] = (UInt16)avg(2, 1, c[0], c[1]);
                        c[3] = (UInt16)avg(1, 2, c[0], c[1]);
                    }
                    else
                    {
                        c[2] = (UInt16)avg(1, 1, c[0], c[1]);
                        c[3] = 0;
                    }

                    byte[] pixeldata = new byte[4];
                    pixeldata[3] = tpl[offset + off + 4];
                    pixeldata[2] = tpl[offset + off + 5];
                    pixeldata[1] = tpl[offset + off + 6];
                    pixeldata[0] = tpl[offset + off + 7];
                    UInt32 pixel = BitConverter.ToUInt32(pixeldata, 0);

                    int ix = x0 + (4 * y0);
                    int raw = c[(pixel >> (30 - (2 * ix))) & 0x03];

                    pix[0] = (raw >> 8) & 0xf8;
                    pix[1] = (raw >> 3) & 0xf8;
                    pix[2] = (raw << 3) & 0xf8;

                    int intout = (pix[0] << 16) | (pix[1] << 8) | (pix[2] << 0) | (255 << 24);
                    output[inp] = (UInt32)intout;
                    inp++;
                }
            }

            return Tools.UInt32ArrayToByteArray(output);
        }

        /// <summary>
        /// Gets the pixel data of a Bitmap as an Byte Array
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        public static uint[] BitmapToRGBA(Bitmap img)
        {
            int x = img.Width;
            int y = img.Height;
            UInt32[] rgba = new UInt32[x * y];

            for (int i = 0; i < y; i += 4)
            {
                for (int j = 0; j < x; j += 4)
                {
                    for (int y1 = i; y1 < i + 4; y1++)
                    {
                        for (int x1 = j; x1 < j + 4; x1++)
                        {
                            if (y1 >= y || x1 >= x)
                                continue;

                            Color color = img.GetPixel(x1, y1);
                            rgba[x1 + (y1 * x)] = (UInt32)color.ToArgb();
                        }
                    }
                }
            }

            return rgba;
        }

        /// <summary>
        /// Converts an Image to a Tpl
        /// </summary>
        /// <param name="img"></param>
        /// <param name="format">4 = RGB565, 5 = RGB5A3, 6 = RGBA8</param>
        /// <returns></returns>
        public static void ConvertToTPL(Bitmap img, string destination, int format)
        {
            byte[] tpl = ConvertToTPL(img, format);

            using (FileStream fs = new FileStream(destination, FileMode.Create))
            {
                fs.Write(tpl, 0, tpl.Length);
            }
        }

        /// <summary>
        /// Converts an Image to a Tpl
        /// </summary>
        /// <param name="img"></param>
        /// <param name="format">4 = RGB565, 5 = RGB5A3, 6 = RGBA8</param>
        /// <returns></returns>
        public static void ConvertToTPL(Image img, string destination, int format)
        {
            byte[] tpl = ConvertToTPL((Bitmap)img, format);

            using (FileStream fs = new FileStream(destination, FileMode.Create))
            {
                fs.Write(tpl, 0, tpl.Length);
            }
        }

        /// <summary>
        /// Converts an Image to a Tpl
        /// </summary>
        /// <param name="img"></param>
        /// <param name="format">4 = RGB565, 5 = RGB5A3, 6 = RGBA8</param>
        /// <returns></returns>
        public static byte[] ConvertToTPL(Image img, int format)
        {
            return ConvertToTPL((Bitmap)img, format);
        }

        /// <summary>
        /// Converts an Image to a Tpl
        /// </summary>
        /// <param name="img"></param>
        /// <param name="format">4 = RGB565, 5 = RGB5A3, 6 = RGBA8</param>
        /// <returns></returns>
        public static byte[] ConvertToTPL(Bitmap img, int format)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                byte[] rgbaData;

                UInt32 tplmagic = 0x20af30;
                UInt32 ntextures = 0x1;
                UInt32 headersize = 0xc;
                UInt32 texheaderoff = 0x14;
                UInt32 texpaletteoff = 0x0;

                UInt16 texheight = (UInt16)img.Height;
                UInt16 texwidth = (UInt16)img.Width;
                UInt32 texformat;
                UInt32 texdataoffset = 0x40;
                byte[] rest = new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00, 01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00 };
                //This should do it for our needs.. rest includes padding

                switch (format)
                {
                    case 4: //RGB565
                        texformat = 0x4;
                        rgbaData = ToRGB565(img);
                        break;
                    case 5: //RGB5A3
                        texformat = 0x5;
                        rgbaData = ToRGB5A3(img);
                        break;
                    default: //RGBA8 = 6
                        texformat = 0x6;
                        rgbaData = ToRGBA8(img);
                        break;
                }

                byte[] buffer = BitConverter.GetBytes(tplmagic); Array.Reverse(buffer);
                ms.Seek(0, SeekOrigin.Begin);
                ms.Write(buffer, 0, buffer.Length);

                buffer = BitConverter.GetBytes(ntextures); Array.Reverse(buffer);
                ms.Write(buffer, 0, buffer.Length);

                buffer = BitConverter.GetBytes(headersize); Array.Reverse(buffer);
                ms.Write(buffer, 0, buffer.Length);

                buffer = BitConverter.GetBytes(texheaderoff); Array.Reverse(buffer);
                ms.Write(buffer, 0, buffer.Length);

                buffer = BitConverter.GetBytes(texpaletteoff); Array.Reverse(buffer);
                ms.Write(buffer, 0, buffer.Length);

                buffer = BitConverter.GetBytes(texheight); Array.Reverse(buffer);
                ms.Write(buffer, 0, buffer.Length);

                buffer = BitConverter.GetBytes(texwidth); Array.Reverse(buffer);
                ms.Write(buffer, 0, buffer.Length);

                buffer = BitConverter.GetBytes(texformat); Array.Reverse(buffer);
                ms.Write(buffer, 0, buffer.Length);

                buffer = BitConverter.GetBytes(texdataoffset); Array.Reverse(buffer);
                ms.Write(buffer, 0, buffer.Length);

                ms.Write(rest, 0, rest.Length);

                ms.Write(rgbaData, 0, rgbaData.Length);

                return ms.ToArray();
            }
        }

        /// <summary>
        /// Converts an Image to RGBA8 Tpl data
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        public static byte[] ToRGBA8(Bitmap img)
        {
            uint[] pixeldata = BitmapToRGBA(img);
            int w = img.Width;
            int h = img.Height;
            int z = 0, iv = 0;
            byte[] output = new byte[Tools.AddPadding(w, 4) * Tools.AddPadding(h, 4) * 4];
            uint[] lr = new uint[32], lg = new uint[32], lb = new uint[32], la = new uint[32];

            for (int y1 = 0; y1 < h; y1 += 4)
            {
                for (int x1 = 0; x1 < w; x1 += 4)
                {
                    for (int y = y1; y < (y1 + 4); y++)
                    {
                        for (int x = x1; x < (x1 + 4); x++)
                        {
                            UInt32 rgba;

                            if (y >= h || x >= w)
                            {
                                rgba = 0;
                            }
                            else
                            {
                                rgba = pixeldata[x + (y * w)];
                            }

                            lr[z] = (uint)(rgba >> 16) & 0xff;
                            lg[z] = (uint)(rgba >> 8) & 0xff;
                            lb[z] = (uint)(rgba >> 0) & 0xff;
                            la[z] = (uint)(rgba >> 24) & 0xff;

                            z++;
                        }
                    }

                    if (z == 16)
                    {
                        for (int i = 0; i < 16; i++)
                        {
                            output[iv++] = (byte)(la[i]);
                            output[iv++] = (byte)(lr[i]);
                        }
                        for (int i = 0; i < 16; i++)
                        {
                            output[iv++] = (byte)(lg[i]);
                            output[iv++] = (byte)(lb[i]);
                        }

                        z = 0;
                    }
                }
            }


            return output;
        }

        /// <summary>
        /// Converts an Image to RGBA565 Tpl data
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        public static byte[] ToRGB565(Bitmap img)
        {
            uint[] pixeldata = BitmapToRGBA(img);
            int w = img.Width;
            int h = img.Height;
            int z = -1;
            byte[] output = new byte[Tools.AddPadding(w, 4) * Tools.AddPadding(h, 4) * 2];

            for (int y1 = 0; y1 < h; y1 += 4)
            {
                for (int x1 = 0; x1 < w; x1 += 4)
                {
                    for (int y = y1; y < y1 + 4; y++)
                    {
                        for (int x = x1; x < x1 + 4; x++)
                        {
                            UInt16 newpixel;

                            if (y >= h || x >= w)
                            {
                                newpixel = 0;
                            }
                            else
                            {
                                uint rgba = pixeldata[x + (y * w)];

                                uint b = (rgba >> 16) & 0xff;
                                uint g = (rgba >> 8) & 0xff;
                                uint r = (rgba >> 0) & 0xff;

                                newpixel = (UInt16)(((b >> 3) << 11) | ((g >> 2) << 5) | ((r >> 3) << 0));
                            }

                            byte[] temp = BitConverter.GetBytes(newpixel);
                            Array.Reverse(temp);

                            output[++z] = temp[0];
                            output[++z] = temp[1];
                        }
                    }
                }
            }

            return output;
        }

        /// <summary>
        /// Converts an Image to RGBA5A3 Tpl data
        /// </summary>
        /// <param name="img"></param>
        /// <returns></returns>
        public static byte[] ToRGB5A3(Bitmap img)
        {
            uint[] pixeldata = BitmapToRGBA(img);
            int w = img.Width;
            int h = img.Height;
            int z = -1;
            byte[] output = new byte[Tools.AddPadding(w, 4) * Tools.AddPadding(h, 4) * 2];

            for (int y1 = 0; y1 < h; y1 += 4)
            {
                for (int x1 = 0; x1 < w; x1 += 4)
                {
                    for (int y = y1; y < y1 + 4; y++)
                    {
                        for (int x = x1; x < x1 + 4; x++)
                        {
                            int newpixel;

                            if (y >= h || x >= w)
                            {
                                newpixel = 0;
                            }
                            else
                            {
                                int rgba = (int)pixeldata[x + (y * w)];
                                newpixel = 0;

                                int r = (rgba >> 16) & 0xff;
                                int g = (rgba >> 8) & 0xff;
                                int b = (rgba >> 0) & 0xff;
                                int a = (rgba >> 24) & 0xff;

                                if (a <= 0xda)
                                {
                                    //RGB4A3

                                    newpixel &= ~(1 << 15);

                                    r = ((r * 15) / 255) & 0xf;
                                    g = ((g * 15) / 255) & 0xf;
                                    b = ((b * 15) / 255) & 0xf;
                                    a = ((a * 7) / 255) & 0x7;

                                    newpixel |= a << 12;
                                    newpixel |= b << 0;
                                    newpixel |= g << 4;
                                    newpixel |= r << 8;
                                }
                                else
                                {
                                    //RGB5

                                    newpixel |= (1 << 15);

                                    r = ((r * 31) / 255) & 0x1f;
                                    g = ((g * 31) / 255) & 0x1f;
                                    b = ((b * 31) / 255) & 0x1f;

                                    newpixel |= b << 0;
                                    newpixel |= g << 5;
                                    newpixel |= r << 10;
                                }
                            }

                            byte[] temp = BitConverter.GetBytes((UInt16)newpixel);
                            Array.Reverse(temp);

                            output[++z] = temp[0];
                            output[++z] = temp[1];
                        }
                    }
                }
            }

            return output;
        }
    }

    public class NAND
    {
        /// <summary>
        /// Backups all Saves from a NAND Backup
        /// </summary>
        /// <param name="nandpath"></param>
        /// <param name="destinationpath"></param>
        public static void BackupSaves(string nandpath, string destinationpath)
        {
            string titlefolder = nandpath + "\\title";
            string[] lowerdirs = Directory.GetDirectories(titlefolder);
            Tools.ChangeProgress(0);

            for (int i = 0; i < lowerdirs.Length; i++)
            {
                Tools.ChangeProgress((i + 1) * 100 / lowerdirs.Length);
                string[] upperdirs = Directory.GetDirectories(lowerdirs[i]);

                for (int j = 0; j < upperdirs.Length; j++)
                {
                    if (Directory.Exists(upperdirs[j] + "\\data"))
                    {
                        if (Directory.GetFiles(upperdirs[j] + "\\data").Length > 0 ||
                            Directory.GetDirectories(upperdirs[j] + "\\data").Length > 0)
                        {
                            Tools.CopyDirectory(upperdirs[j] + "\\data", (upperdirs[j] + "\\data").Replace(nandpath, destinationpath).Replace("\\title", ""));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Restores all Saves for existing titles to a NAND Backup
        /// </summary>
        /// <param name="backupdir"></param>
        /// <param name="nandpath"></param>
        public static void RestoreSaves(string backuppath, string nandpath)
        {
            string titlefolder = nandpath + "\\title";
            string[] lowerdirs = Directory.GetDirectories(backuppath);
            Tools.ChangeProgress(0);

            for (int i = 0; i < lowerdirs.Length; i++)
            {
                Tools.ChangeProgress((i + 1) * 100 / lowerdirs.Length);
                string[] upperdirs = Directory.GetDirectories(lowerdirs[i]);

                for (int j = 0; j < upperdirs.Length; j++)
                {
                    string[] datafiles = Directory.GetFiles(upperdirs[j] + "\\data");
                    string upperdirnand = upperdirs[j].Replace(backuppath, titlefolder);

                    if (Directory.Exists(upperdirnand) &&
                        (Directory.GetFiles(upperdirs[j] + "\\data").Length > 0 ||
                         Directory.GetDirectories(upperdirs[j] + "\\data").Length > 0))
                    {
                        if (!Directory.Exists(upperdirnand + "\\data")) Directory.CreateDirectory(upperdirnand + "\\data");
                        Tools.CopyDirectory(upperdirs[j] + "\\data", (upperdirs[j] + "\\data").Replace(backuppath, titlefolder));
                    }
                }
            }

            Tools.ChangeProgress(100);
        }

        /// <summary>
        /// Backups a single Save
        /// </summary>
        /// <param name="nandpath"></param>
        /// <param name="titlepath">Format: XXXXXXXX\XXXXXXXX</param>
        /// <param name="destinationpath"></param>
        public static void BackupSingleSave(string nandpath, string titlepath, string destinationpath)
        {
            string datafolder = nandpath + "\\title\\" + titlepath + "\\data";

            if (Directory.GetFiles(datafolder).Length > 0 ||
                Directory.GetDirectories(datafolder).Length > 0)
            {
                string savefolder = datafolder.Replace(nandpath, destinationpath).Replace("\\title", "");
                if (!Directory.Exists(savefolder)) Directory.CreateDirectory(savefolder);

                Tools.CopyDirectory(datafolder, savefolder);
            }
            else
            {
                throw new Exception("No save data was found!");
            }
        }

        /// <summary>
        /// Restores a singe Save, if the title exists on NAND Backup
        /// </summary>
        /// <param name="backuppath"></param>
        /// <param name="titlepath">Format: XXXXXXXX\XXXXXXXX</param>
        /// <param name="nandpath"></param>
        public static void RestoreSingleSave(string backuppath, string titlepath, string nandpath)
        {
            string titlefoldernand = nandpath + "\\title\\" + titlepath;
            string titlefolder = titlefoldernand.Replace(nandpath, backuppath).Replace("\\title", "");

            if (Directory.Exists(titlefoldernand) &&
                (Directory.GetFiles(titlefolder + "\\data").Length > 0 ||
                 Directory.GetDirectories(titlefolder + "\\data").Length > 0))
            {
                if (!Directory.Exists(titlefoldernand + "\\data")) Directory.CreateDirectory(titlefoldernand + "\\data");
                Tools.CopyDirectory(titlefolder + "\\data", titlefoldernand + "\\data");
            }
            else
            {
                throw new Exception("Title not found in NAND Backup!");
            }
        }

        /// <summary>
        /// Checks, if save data exists in the given title folder
        /// </summary>
        /// <param name="nandpath"></param>
        /// <param name="titlepath">Format: XXXXXXXX\XXXXXXXX</param>
        public static bool CheckForSaveData(string nandpath, string titlepath)
        {
            string datafolder = nandpath + "\\title\\" + titlepath + "\\data";

            if (!Directory.Exists(datafolder)) return false;
            else
            {
                string[] datafiles = Directory.GetFiles(datafolder);

                if (datafiles.Length > 0) return true;
                else return false;
            }
        }

        /// <summary>
        /// Checks, if save data exists in the given title folder
        /// </summary>
        /// <param name="nandpath"></param>
        /// <param name="titlepath">Format: XXXXXXXX\XXXXXXXX</param>
        public static bool CheckForBackupData(string backuppath, string titlepath)
        {
            string datafolder = backuppath + "\\" + titlepath + "\\data";

            if (!Directory.Exists(datafolder)) return false;
            else
            {
                string[] datafiles = Directory.GetFiles(datafolder);

                if (datafiles.Length > 0) return true;
                else return false;
            }
        }
    }

    public class Sound
    {
        /// <summary>
        /// Checks if the given Wave is a proper PCM WAV file
        /// </summary>
        /// <param name="wavefile"></param>
        /// <returns></returns>
        public static bool CheckWave(string wavefile)
        {
            byte[] wave = Tools.LoadFileToByteArray(wavefile, 0, 256);
            return CheckWave(wave);
        }

        /// <summary>
        /// Checks if the given Wave is a proper PCM WAV file
        /// </summary>
        /// <param name="wavefile"></param>
        /// <returns></returns>
        public static bool CheckWave(byte[] wavefile)
        {
            if (wavefile[0] != 'R' ||
                wavefile[1] != 'I' ||
                wavefile[2] != 'F' ||
                wavefile[3] != 'F' ||

                wavefile[8] != 'W' ||
                wavefile[9] != 'A' ||
                wavefile[10] != 'V' ||
                wavefile[11] != 'E' ||

                wavefile[12] != 'f' ||
                wavefile[13] != 'm' ||
                wavefile[14] != 't' ||

                wavefile[20] != 0x01 || //Format = PCM
                wavefile[21] != 0x00 ||

                wavefile[34] != 0x10 || //Bitrate (16bit)
                wavefile[35] != 0x00
                ) return false;

            return true;
        }

        /// <summary>
        /// Returns the playlength of the Wave file in seconds
        /// </summary>
        /// <param name="wavefile"></param>
        /// <returns></returns>
        public static int GetWaveLength(string wavefile)
        {
            byte[] wave = Tools.LoadFileToByteArray(wavefile, 0, 256);
            return GetWaveLength(wave);
        }

        /// <summary>
        /// Returns the playlength of the Wave file in seconds
        /// </summary>
        /// <param name="wavefile"></param>
        /// <returns></returns>
        public static int GetWaveLength(byte[] wavefile)
        {
            if (CheckWave(wavefile) == true)
            {
                byte[] BytesPerSec = new byte[] { wavefile[28], wavefile[29], wavefile[30], wavefile[31] };
                int bps = BitConverter.ToInt32(BytesPerSec, 0);

                byte[] Chunksize = new byte[] { wavefile[4], wavefile[5], wavefile[6], wavefile[7] };
                int chunks = BitConverter.ToInt32(Chunksize, 0);

                return Math.Abs(chunks / bps);
            }
            else
                throw new Exception("This is not a supported PCM Wave file!");
        }

        /// <summary>
        /// Converts a wave file to a sound.bin
        /// </summary>
        /// <param name="wavefile"></param>
        /// <param name="soundbin"></param>
        public static void WaveToSoundBin(string wavefile, string soundbin, bool compress)
        {
            if (CheckWave(wavefile) == true)
            {
                byte[] sound = Tools.LoadFileToByteArray(wavefile);
                if (compress == true) sound = Lz77.Compress(sound);
                sound = U8.AddHeaderIMD5(sound);
                Wii.Tools.SaveFileFromByteArray(sound, soundbin);
            }
            else
                throw new Exception("This is not a supported 16bit PCM Wave file!");
        }

        /// <summary>
        /// Converts a sound.bin to a wave file
        /// </summary>
        /// <param name="soundbin"></param>
        /// <param name="wavefile"></param>
        public static void SoundBinToAudio(string soundbin, string audiofile)
        {
            FileStream fs = new FileStream(soundbin, FileMode.Open);
            byte[] audio = new byte[fs.Length - 32];
            int offset = 0;

            fs.Seek(32, SeekOrigin.Begin);
            fs.Read(audio, 0, audio.Length);
            fs.Close();

            if ((offset = Lz77.GetLz77Offset(audio)) != -1)
                audio = Lz77.Decompress(audio, offset);

            Tools.SaveFileFromByteArray(audio, audiofile);
        }

        /// <summary>
        /// Converts a BNS file to a sound.bin
        /// </summary>
        /// <param name="bnsFile"></param>
        /// <param name="soundBin"></param>
        public static void BnsToSoundBin(string bnsFile, string soundBin, bool compress)
        {
            byte[] bns = Tools.LoadFileToByteArray(bnsFile);

            if (bns[0] != 'B' || bns[1] != 'N' || bns[2] != 'S')
                throw new Exception("This is not a supported BNS file!");

            if (compress) bns = Lz77.Compress(bns);
            bns = U8.AddHeaderIMD5(bns);

            Tools.SaveFileFromByteArray(bns, soundBin);
        }

        /// <summary>
        /// Returns the length of the BNS audio file in seconds
        /// </summary>
        /// <param name="bnsFile"></param>
        /// <returns></returns>
        public static int GetBnsLength(string bnsFile)
        {
            byte[] temp = Tools.LoadFileToByteArray(bnsFile, 0, 100);
            return GetBnsLength(temp);
        }

        /// <summary>
        /// Returns the length of the BNS audio file in seconds
        /// </summary>
        /// <param name="bnsFile"></param>
        /// <returns></returns>
        public static int GetBnsLength(byte[] bnsFile)
        {
            byte[] temp = new byte[4];
            temp[0] = bnsFile[45];
            temp[1] = bnsFile[44];

            int sampleRate = BitConverter.ToInt16(temp, 0);

            temp[0] = bnsFile[55];
            temp[1] = bnsFile[54];
            temp[2] = bnsFile[53];
            temp[3] = bnsFile[52];

            int sampleCount = BitConverter.ToInt32(temp, 0);

            return sampleCount / sampleRate;
        }
    }

    public class Brlyt
    {
        /// <summary>
        /// Checks, if the TPLs match the TPLs specified in the brlyt
        /// </summary>
        /// <param name="brlyt"></param>
        /// <param name="tpls"></param>
        /// <returns></returns>
        public static bool CheckBrlytTpls(string brlyt, string[] tpls)
        {
            byte[] brlytArray = Tools.LoadFileToByteArray(brlyt);
            return CheckBrlytTpls(brlytArray, tpls);
        }

        /// <summary>
        /// Checks, if the TPLs match the TPLs specified in the brlyt
        /// </summary>
        /// <param name="brlyt"></param>
        /// <param name="tpls"></param>
        /// <returns></returns>
        public static bool CheckBrlytTpls(byte[] brlyt, string[] tpls)
        {
            int texcount = Tools.HexStringToInt(brlyt[44].ToString("x2") + brlyt[45].ToString("x2"));
            if (tpls.Length != texcount) return false;

            int texnamepos = 48 + (texcount * 8);
            for (int i = 0; i < texcount; i++)
            {
                string thisTex = "";
                while (brlyt[texnamepos] != 0x00)
                {
                    thisTex += Convert.ToChar(brlyt[texnamepos]);
                    texnamepos++;
                }
                texnamepos++;

                bool exists = Array.Exists(tpls,tpl => tpl == thisTex);
                if (exists == false) return false;
            }

            return true;
        }

        /// <summary>
        /// Checks, if one or more Tpls specified in the brlyt are missing and returns 
        /// the names of the missing ones.
        /// </summary>
        /// <param name="brlyt"></param>
        /// <param name="tpls"></param>
        /// <param name="missingtpls"></param>
        /// <returns></returns>
        public static bool CheckForMissingTpls(string brlyt, string[] tpls, out string[] missingtpls)
        {
            byte[] brlytArray = Tools.LoadFileToByteArray(brlyt);
            return CheckForMissingTpls(brlytArray, tpls, out missingtpls);
        }

        /// <summary>
        /// Checks, if one or more Tpls specified in the brlyt are missing and returns 
        /// the names of the missing ones.
        /// </summary>
        /// <param name="brlyt"></param>
        /// <param name="tpls"></param>
        /// <param name="missingtpls"></param>
        /// <returns></returns>
        public static bool CheckForMissingTpls(byte[] brlyt, string[] tpls, out string[] missingtpls)
        {
            List<string> missings = new List<string>();
            string[] brlytTpls = GetBrlytTpls(brlyt);
            bool missing = false;

            for (int i = 0; i < brlytTpls.Length; i++)
            {
                if (Tools.StringExistsInStringArray(brlytTpls[i], tpls) == false)
                {
                    missings.Add(brlytTpls[i]);
                    missing = true;
                }
            }

            missingtpls = missings.ToArray();
            return missing;
        }

        /// <summary>
        /// Checks, if one or more Tpls are not specified in the brlyt and returns 
        /// the names of the missing ones.
        /// </summary>
        /// <param name="brly"></param>
        /// <param name="tpls"></param>
        /// <param name="unusedtpls"></param>
        /// <returns></returns>
        public static bool CheckForUnusedTpls(string brlyt, string[] tpls, out string[] unusedtpls)
        {
            byte[] brlytArray = Tools.LoadFileToByteArray(brlyt);
            return CheckForUnusedTpls(brlytArray, tpls, out unusedtpls);
        }

        /// <summary>
        /// Checks, if one or more Tpls are not specified in the brlyt and returns 
        /// the names of the missing ones.
        /// </summary>
        /// <param name="brly"></param>
        /// <param name="tpls"></param>
        /// <param name="unusedtpls"></param>
        /// <returns></returns>
        public static bool CheckForUnusedTpls(byte[] brlyt, string[] tpls, out string[] unusedtpls)
        {
            List<string> unuseds = new List<string>();
            string[] brlytTpls = GetBrlytTpls(brlyt);
            bool missing = false;

            for (int i = 0; i < tpls.Length; i++)
            {
                if (Tools.StringExistsInStringArray(tpls[i], brlytTpls) == false)
                {
                    string wonum = tpls[i].Remove(tpls[i].LastIndexOf('.') - 1) + "00.tpl";
                    string wonum2 = tpls[i].Remove(tpls[i].LastIndexOf('.') - 2) + "00.tpl";

                    if (Tools.StringExistsInStringArray(wonum, brlytTpls) == false &&
                        Tools.StringExistsInStringArray(wonum2, brlytTpls) == false)
                    {
                        unuseds.Add(tpls[i]);
                        missing = true;
                    }
                }
            }

            unusedtpls = unuseds.ToArray();
            return missing;
        }

        /// <summary>
        /// Returns the name of all Tpls specified in the brlyt
        /// </summary>
        /// <param name="brlyt"></param>
        /// <returns></returns>
        public static string[] GetBrlytTpls(string brlyt)
        {
            byte[] temp = Tools.LoadFileToByteArray(brlyt);
            return GetBrlytTpls(temp);
        }

        /// <summary>
        /// Returns the name of all Tpls specified in the brlyt
        /// </summary>
        /// <param name="brlyt"></param>
        /// <returns></returns>
        public static string[] GetBrlytTpls(byte[] brlyt)
        {
            int texcount = Tools.HexStringToInt(brlyt[44].ToString("x2") + brlyt[45].ToString("x2"));
            int texnamepos = 48 + (texcount * 8);
            List<string> Tpls = new List<string>();

            for (int i = 0; i < texcount; i++)
            {
                string thisTex = "";
                while (brlyt[texnamepos] != 0x00)
                {
                    thisTex += Convert.ToChar(brlyt[texnamepos]);
                    texnamepos++;
                }
                Tpls.Add(thisTex);
                texnamepos++;
            }

            return Tpls.ToArray();
        }

        /// <summary>
        /// Returns true, if the given Tpl is specified in the brlyt.
        /// TplName must end with ".tpl"!
        /// </summary>
        /// <param name="brlyt"></param>
        /// <param name="TplName"></param>
        /// <returns></returns>
        public static bool IsTplInBrlyt(byte[] brlyt, string TplName)
        {
            string[] brlytTpls = GetBrlytTpls(brlyt);
            bool exists = Array.Exists(brlytTpls, Tpl => Tpl == TplName);
            return exists;
        }
    }

    public class ProgressChangedEventArgs : EventArgs
    {
        private readonly int p_Percent = 0;

        public int PercentProgress
        {
            get { return p_Percent; }
        }

        internal ProgressChangedEventArgs(int PercentProgress)
            : base()
        {
            this.p_Percent = PercentProgress;
        }
    }
}