/* This file is part of libWiiSharp * Copyright (C) 2009 Leathl * * libWiiSharp is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * libWiiSharp is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Security.Cryptography; namespace libWiiSharp { public enum U8_NodeType : ushort { File = 0x0000, Directory = 0x0100, } public class U8 : IDisposable { private const int dataPadding = 32; private Headers.HeaderType headerType = Headers.HeaderType.None; private object header = null; private U8_Header u8Header = new U8_Header(); private U8_Node rootNode = new U8_Node(); private List u8Nodes = new List(); private List stringTable = new List(); private List data = new List(); private int iconSize = -1; private int bannerSize = -1; private int soundSize = -1; private bool lz77 = false; /// /// The type of the Header of the U8 file. /// public Headers.HeaderType HeaderType { get { return headerType; } } /// /// The Header of the U8 file as an object. Will be null if the file has no Header. /// public object Header { get { return header; } } /// /// The Rootnode of the U8 file. /// public U8_Node RootNode { get { return rootNode; } } /// /// The Nodes of the U8 file. /// public List Nodes { get { return u8Nodes; } } /// /// The string table of the U8 file. /// public string[] StringTable { get { return stringTable.ToArray(); } } /// /// The actual data (files) in the U8 file. Will be an empty byte array for directory entries. /// public byte[][] Data { get { return data.ToArray(); } } /// /// The Number of Nodes WITHOUT the Rootnode. /// public int NumOfNodes { get { return (int)rootNode.SizeOfData - 1; } } /// /// The size of the icon.bin (if the U8 files contains an icon.bin). /// public int IconSize { get { return iconSize; } } /// /// The size of the banner.bin (if the U8 files contains an banner.bin). /// public int BannerSize { get { return bannerSize; } } /// /// The size of the sound.bin (if the U8 files contains an sound.bin). /// public int SoundSize { get { return soundSize; } } /// /// If true, the U8 file will be Lz77 compressed while saving. /// public bool Lz77Compress { get { return lz77; } set { lz77 = value; } } public U8() { rootNode.Type = U8_NodeType.Directory; } #region IDisposable Members private bool isDisposed = false; ~U8() { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing && !isDisposed) { header = null; u8Header = null; rootNode = null; u8Nodes.Clear(); u8Nodes = null; stringTable.Clear(); stringTable = null; data.Clear(); data = null; } isDisposed = true; } #endregion #region Public Functions /// /// Checks whether a file is a U8 file or not. /// /// /// public static bool IsU8(string pathToFile) { return IsU8(File.ReadAllBytes(pathToFile)); } /// /// Checks whether a file is a U8 file or not. /// /// /// public static bool IsU8(byte[] file) { if (Lz77.IsLz77Compressed(file)) { byte[] partOfFile = new byte[(file.Length > 2000) ? 2000 : file.Length]; for (int i = 0; i < partOfFile.Length; i++) partOfFile[i] = file[i]; Lz77 l = new Lz77(); partOfFile = l.Decompress(partOfFile); return IsU8(partOfFile); } else { Headers.HeaderType h = Headers.DetectHeader(file); return (Shared.Swap(BitConverter.ToUInt32(file, (int)h)) == 0x55AA382D); } } /// /// Loads a U8 file. /// /// /// public static U8 Load(string pathToU8) { return Load(File.ReadAllBytes(pathToU8)); } /// /// Loads a U8 file. /// /// /// public static U8 Load(byte[] u8File) { U8 u = new U8(); MemoryStream ms = new MemoryStream(u8File); try { u.parseU8(ms); } catch { ms.Dispose(); throw; } ms.Dispose(); return u; } /// /// Loads a U8 file. /// /// /// public static U8 Load(Stream u8File) { U8 u = new U8(); u.parseU8(u8File); return u; } /// /// Creates a U8 file. /// /// /// public static U8 FromDirectory(string pathToDirectory) { U8 u = new U8(); u.createFromDir(pathToDirectory); return u; } /// /// Loads a U8 file. /// /// public void LoadFile(string pathToU8) { LoadFile(File.ReadAllBytes(pathToU8)); } /// /// Loads a U8 file. /// /// public void LoadFile(byte[] u8File) { MemoryStream ms = new MemoryStream(u8File); try { parseU8(ms); } catch { ms.Dispose(); throw; } ms.Dispose(); } /// /// Loads a U8 file. /// /// public void LoadFile(Stream u8File) { parseU8(u8File); } /// /// Creates a U8 file. /// /// public void CreateFromDirectory(string pathToDirectory) { createFromDir(pathToDirectory); } /// /// Saves the U8 file. /// /// public void Save(string savePath) { if (File.Exists(savePath)) File.Delete(savePath); using (FileStream fs = new FileStream(savePath, FileMode.Create)) writeToStream(fs); } /// /// Returns the U8 file as a memory stream. /// /// public MemoryStream ToMemoryStream() { MemoryStream ms = new MemoryStream(); try { writeToStream(ms); } catch { ms.Dispose(); throw; } return ms; } /// /// Returns the U8 file as a byte array. /// /// public byte[] ToByteArray() { return ToMemoryStream().ToArray(); } /// /// Unpacks the U8 file to given directory. /// /// public void Unpack(string saveDir) { unpackToDir(saveDir); } /// /// Unpacks the U8 file to given directory. /// /// public void Extract(string saveDir) { unpackToDir(saveDir); } /// /// Adds an IMET Header to the U8 file. /// /// /// public void AddHeaderImet(bool shortImet, params string[] titles) { if (iconSize == -1) throw new Exception("icon.bin wasn't found!"); else if (bannerSize == -1) throw new Exception("banner.bin wasn't found!"); else if (soundSize == -1) throw new Exception("sound.bin wasn't found!"); header = Headers.IMET.Create(shortImet, iconSize, bannerSize, soundSize, titles); headerType = (shortImet) ? Headers.HeaderType.ShortIMET : Headers.HeaderType.IMET; } /// /// Adds an IMD5 Header to the U8 file. /// public void AddHeaderImd5() { headerType = Headers.HeaderType.IMD5; } /// /// Replaces the file with the given index. /// /// /// public void ReplaceFile(int fileIndex, string pathToNewFile, bool changeFileName = false) { if (u8Nodes[fileIndex].Type == U8_NodeType.Directory) throw new Exception("You can't replace a directory with a file!"); data[fileIndex] = File.ReadAllBytes(pathToNewFile); if (changeFileName) stringTable[fileIndex] = Path.GetFileName(pathToNewFile); if (stringTable[fileIndex].ToLower() == "icon.bin") iconSize = getRealSize(File.ReadAllBytes(pathToNewFile)); else if (stringTable[fileIndex].ToLower() == "banner.bin") bannerSize = getRealSize(File.ReadAllBytes(pathToNewFile)); else if (stringTable[fileIndex].ToLower() == "sound.bin") soundSize = getRealSize(File.ReadAllBytes(pathToNewFile)); } /// /// Replaces the file with the given index. /// /// /// public void ReplaceFile(int fileIndex, byte[] newData) { if (u8Nodes[fileIndex].Type == U8_NodeType.Directory) throw new Exception("You can't replace a directory with a file!"); data[fileIndex] = newData; if (stringTable[fileIndex].ToLower() == "icon.bin") iconSize = getRealSize(newData); else if (stringTable[fileIndex].ToLower() == "banner.bin") bannerSize = getRealSize(newData); else if (stringTable[fileIndex].ToLower() == "sound.bin") soundSize = getRealSize(newData); } /// /// Returns the index of the directory or file with the given name. /// If no matching Node is found, -1 will be returned. /// /// /// public int GetNodeIndex(string fileOrDirName) { for (int i = 0; i < u8Nodes.Count; i++) if (stringTable[i].ToLower() == fileOrDirName.ToLower()) return i; return -1; } /// /// Changes the name of a node. /// /// /// public void RenameNode(int index, string newName) { stringTable[index] = newName; } /// /// Changes the name of a node. /// /// /// public void RenameNode(string oldName, string newName) { stringTable[GetNodeIndex(oldName)] = newName; } /// /// Adds a directory to the U8 file. /// Path must be like this: "/arc/timg/newFolder". /// /// public void AddDirectory(string path) { addEntry(path, new byte[0]); } /// /// Adds a file to the U8 file. /// Path must be like this: "/arc/timg/newFile.tpl". /// /// /// public void AddFile(string path, byte[] data) { addEntry(path, data); } /// /// Removes a directory from the U8 file. /// If the directory contains files/dirs, they will also be deleted. /// Path must be like this: "/arc/timg/folderToDelete". /// /// public void RemoveDirectory(string path) { removeEntry(path); } /// /// Removes a file from the U8 file. /// Path must be like this: "/arc/timg/fileToDelete.tpl". /// /// public void RemoveFile(string path) { removeEntry(path); } #endregion #region Private Functions private void writeToStream(Stream writeStream) { fireDebug("Writing U8 File..."); //Update Rootnode fireDebug(" Updating Rootnode..."); rootNode.SizeOfData = (uint)u8Nodes.Count + 1; MemoryStream u8Stream = new MemoryStream(); //Write Stringtable u8Stream.Seek(u8Header.OffsetToRootNode + ((u8Nodes.Count + 1) * 12), SeekOrigin.Begin); fireDebug(" Writing String Table... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper()); u8Stream.WriteByte(0x00); int stringTablePosition = (int)u8Stream.Position - 1; for (int i = 0; i < u8Nodes.Count; i++) { fireDebug(" -> Entry #{1} of {2}: \"{3}\"... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper(), i + 1, u8Nodes.Count, stringTable[i]); u8Nodes[i].OffsetToName = (ushort)(u8Stream.Position - stringTablePosition); byte[] stringBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(stringTable[i]); u8Stream.Write(stringBytes, 0, stringBytes.Length); u8Stream.WriteByte(0x00); } u8Header.HeaderSize = (uint)(u8Stream.Position - u8Header.OffsetToRootNode); u8Header.OffsetToData = 0; //Write Data for (int i = 0; i < u8Nodes.Count; i++) { fireProgress((i + 1) * 100 / u8Nodes.Count); if (u8Nodes[i].Type == U8_NodeType.File) { u8Stream.Seek(Shared.AddPadding((int)u8Stream.Position, dataPadding), SeekOrigin.Begin); fireDebug(" Writing Data #{1} of {2}... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper(), i + 1, u8Nodes.Count); if (u8Header.OffsetToData == 0) u8Header.OffsetToData = (uint)u8Stream.Position; u8Nodes[i].OffsetToData = (uint)u8Stream.Position; u8Nodes[i].SizeOfData = (uint)data[i].Length; u8Stream.Write(data[i], 0, data[i].Length); } else fireDebug(" Node #{0} of {1} is a Directory...", i + 1, u8Nodes.Count); } //Pad End to 16 bytes while (u8Stream.Position % 16 != 0) u8Stream.WriteByte(0x00); //Write Header + Nodes u8Stream.Seek(0, SeekOrigin.Begin); fireDebug(" Writing Header... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper()); u8Header.Write(u8Stream); fireDebug(" Writing Rootnode... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper()); rootNode.Write(u8Stream); for (int i = 0; i < u8Nodes.Count; i++) { fireDebug(" Writing Node Entry #{1} of {2}... (Offset: 0x{0})", u8Stream.Position.ToString("x8").ToUpper(), i + 1, u8Nodes.Count); u8Nodes[i].Write(u8Stream); } byte[] u8Array = u8Stream.ToArray(); u8Stream.Dispose(); if (lz77) { fireDebug(" Lz77 Compressing U8 File..."); Lz77 l = new Lz77(); u8Array = l.Compress(u8Array); } //Write File Header if (headerType == Headers.HeaderType.IMD5) { fireDebug(" Adding IMD5 Header..."); writeStream.Seek(0, SeekOrigin.Begin); Headers.IMD5 h = Headers.IMD5.Create(u8Array); h.Write(writeStream); } else if (headerType == Headers.HeaderType.IMET || headerType == Headers.HeaderType.ShortIMET) { fireDebug(" Adding IMET Header..."); ((Headers.IMET)header).IconSize = (uint)iconSize; ((Headers.IMET)header).BannerSize = (uint)bannerSize; ((Headers.IMET)header).SoundSize = (uint)soundSize; writeStream.Seek(0, SeekOrigin.Begin); ((Headers.IMET)header).Write(writeStream); } writeStream.Write(u8Array, 0, u8Array.Length); fireDebug("Writing U8 File Finished..."); } private void unpackToDir(string saveDir) { fireDebug("Unpacking U8 File to: {0}", saveDir); if (!Directory.Exists(saveDir)) Directory.CreateDirectory(saveDir); string[] dirs = new string[u8Nodes.Count]; dirs[0] = saveDir; int[] dirCount = new int[u8Nodes.Count]; int dirIndex = 0; for (int i = 0; i < u8Nodes.Count; i++) { fireDebug(" Unpacking Entry #{0} of {1}", i + 1, u8Nodes.Count); fireProgress((i + 1) * 100 / u8Nodes.Count); switch (u8Nodes[i].Type) { case U8_NodeType.Directory: fireDebug(" -> Directory: \"{0}\"", stringTable[i]); if (dirs[dirIndex][dirs[dirIndex].Length - 1] != Path.DirectorySeparatorChar) { dirs[dirIndex] = dirs[dirIndex] + Path.DirectorySeparatorChar; } Directory.CreateDirectory(dirs[dirIndex] + stringTable[i]); dirs[dirIndex + 1] = dirs[dirIndex] + stringTable[i]; dirIndex++; dirCount[dirIndex] = (int)u8Nodes[i].SizeOfData; break; default: fireDebug(" -> File: \"{0}\"", stringTable[i]); fireDebug(" -> Size: {0} bytes", data[i].Length); using (FileStream fs = new FileStream(dirs[dirIndex] + Path.DirectorySeparatorChar + stringTable[i], FileMode.Create)) fs.Write(data[i], 0, data[i].Length); break; } while (dirIndex > 0 && dirCount[dirIndex] == i + 2) { dirIndex--; } } fireDebug("Unpacking U8 File Finished"); } private void parseU8(Stream u8File) { fireDebug("Pasing U8 File..."); u8Header = new U8_Header(); rootNode = new U8_Node(); u8Nodes = new List(); stringTable = new List(); data = new List(); fireDebug(" Detecting Header..."); headerType = Headers.DetectHeader(u8File); Headers.HeaderType tempType = headerType; fireDebug(" -> {0}", headerType.ToString()); if (headerType == Headers.HeaderType.IMD5) { fireDebug(" Reading IMD5 Header..."); header = Headers.IMD5.Load(u8File); byte[] file = new byte[u8File.Length]; u8File.Read(file, 0, file.Length); MD5 m = MD5.Create(); byte[] newHash = m.ComputeHash(file, (int)headerType, (int)u8File.Length - (int)headerType); m.Clear(); if (!Shared.CompareByteArrays(newHash, ((Headers.IMD5)header).Hash)) { fireDebug(@"/!\ /!\ /!\ Hashes do not match /!\ /!\ /!\"); fireWarning(string.Format("Hashes of IMD5 header and file do not match! The content might be corrupted!")); } } else if (headerType == Headers.HeaderType.IMET || headerType == Headers.HeaderType.ShortIMET) { fireDebug(" Reading IMET Header..."); header = Headers.IMET.Load(u8File); if (!((Headers.IMET)header).HashesMatch) { fireDebug(@"/!\ /!\ /!\ Hashes do not match /!\ /!\ /!\"); fireWarning(string.Format("The hash stored in the IMET header doesn't match the headers hash! The header and/or file might be corrupted!")); } } fireDebug(" Checking for Lz77 Compression..."); if (Lz77.IsLz77Compressed(u8File)) { fireDebug(" -> Lz77 Compression Found..."); fireDebug(" Decompressing U8 Data..."); Lz77 l = new Lz77(); Stream decompressedFile = l.Decompress(u8File); tempType = Headers.DetectHeader(decompressedFile); u8File = decompressedFile; lz77 = true; } u8File.Seek((int)tempType, SeekOrigin.Begin); byte[] temp = new byte[4]; //Read U8 Header fireDebug(" Reading U8 Header: Magic... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper()); u8File.Read(temp, 0, 4); if (Shared.Swap(BitConverter.ToUInt32(temp, 0)) != u8Header.U8Magic) { fireDebug(" -> Invalid Magic!"); throw new Exception("U8 Header: Invalid Magic!"); } fireDebug(" Reading U8 Header: Offset to Rootnode... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper()); u8File.Read(temp, 0, 4); if (Shared.Swap(BitConverter.ToUInt32(temp, 0)) != u8Header.OffsetToRootNode) { fireDebug(" -> Invalid Offset to Rootnode"); throw new Exception("U8 Header: Invalid Offset to Rootnode!"); } fireDebug(" Reading U8 Header: Header Size... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper()); u8File.Read(temp, 0, 4); u8Header.HeaderSize = Shared.Swap(BitConverter.ToUInt32(temp, 0)); fireDebug(" Reading U8 Header: Offset to Data... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper()); u8File.Read(temp, 0, 4); u8Header.OffsetToData = Shared.Swap(BitConverter.ToUInt32(temp, 0)); u8File.Seek(16, SeekOrigin.Current); //Read Rootnode fireDebug(" Reading Rootnode... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper()); u8File.Read(temp, 0, 4); rootNode.Type = (U8_NodeType)Shared.Swap(BitConverter.ToUInt16(temp, 0)); rootNode.OffsetToName = Shared.Swap(BitConverter.ToUInt16(temp, 2)); u8File.Read(temp, 0, 4); rootNode.OffsetToData = Shared.Swap(BitConverter.ToUInt32(temp, 0)); u8File.Read(temp, 0, 4); rootNode.SizeOfData = Shared.Swap(BitConverter.ToUInt32(temp, 0)); int stringTablePosition = (int)((int)tempType + u8Header.OffsetToRootNode + rootNode.SizeOfData * 12); int nodePosition = (int)u8File.Position; //Read Nodes for (int i = 0; i < rootNode.SizeOfData - 1; i++) { fireDebug(" Reading Node #{1} of {2}... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper(), i + 1, rootNode.SizeOfData - 1); fireProgress((int)((i + 1) * 100 / (rootNode.SizeOfData - 1))); U8_Node tempNode = new U8_Node(); string tempName = string.Empty; byte[] tempData = new byte[0]; //Read Node Entry u8File.Seek(nodePosition, SeekOrigin.Begin); fireDebug(" -> Reading Node Entry... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper()); u8File.Read(temp, 0, 4); tempNode.Type = (U8_NodeType)Shared.Swap(BitConverter.ToUInt16(temp, 0)); tempNode.OffsetToName = Shared.Swap(BitConverter.ToUInt16(temp, 2)); u8File.Read(temp, 0, 4); tempNode.OffsetToData = Shared.Swap(BitConverter.ToUInt32(temp, 0)); u8File.Read(temp, 0, 4); tempNode.SizeOfData = Shared.Swap(BitConverter.ToUInt32(temp, 0)); nodePosition = (int)u8File.Position; fireDebug(" -> {0}", tempNode.Type.ToString()); //Read Node Name u8File.Seek(stringTablePosition + tempNode.OffsetToName, SeekOrigin.Begin); fireDebug(" -> Reading Node Name... (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper()); for (;;) { char tempChar = (char)u8File.ReadByte(); if (tempChar == 0x00) break; tempName += tempChar; if (tempName.Length > 255) break; } fireDebug(" -> {0}", tempName); //Read Node Data if (tempNode.Type == U8_NodeType.File) { u8File.Seek((int)tempType + tempNode.OffsetToData, SeekOrigin.Begin); fireDebug(" -> Reading Node Data (Offset: 0x{0})", u8File.Position.ToString("x8").ToUpper()); tempData = new byte[tempNode.SizeOfData]; u8File.Read(tempData, 0, tempData.Length); } if (tempName.ToLower() == "icon.bin") iconSize = getRealSize(tempData); else if (tempName.ToLower() == "banner.bin") bannerSize = getRealSize(tempData); else if (tempName.ToLower() == "sound.bin") soundSize = getRealSize(tempData); u8Nodes.Add(tempNode); stringTable.Add(tempName); data.Add(tempData); } fireDebug("Pasing U8 File Finished..."); } private void createFromDir(string path) { fireDebug("Creating U8 File from: {0}", path); if (path[path.Length - 1] != Path.DirectorySeparatorChar) path += Path.DirectorySeparatorChar; fireDebug(" Collecting Content..."); string[] dirEntries = getDirContent(path, true); int offsetToName = 1; int offsetToData = 0; fireDebug(" Creating U8 Header..."); u8Header = new U8_Header(); rootNode = new U8_Node(); u8Nodes = new List(); stringTable = new List(); data = new List(); //Create Rootnode fireDebug(" Creating Rootnode..."); rootNode.Type = U8_NodeType.Directory; rootNode.OffsetToName = 0; rootNode.OffsetToData = 0; rootNode.SizeOfData = (uint)dirEntries.Length + 1; //Create Nodes for (int i = 0; i < dirEntries.Length; i++) { fireDebug(" Creating Node #{0} of {1}", i + 1, dirEntries.Length); fireProgress((i + 1) * 100 / dirEntries.Length); U8_Node tempNode = new U8_Node(); byte[] tempData = new byte[0]; string tempDirEntry = dirEntries[i].Remove(0, path.Length - 1); if (Directory.Exists(dirEntries[i])) //It's a dir { fireDebug(" -> Directory"); tempNode.Type = U8_NodeType.Directory; tempNode.OffsetToData = (uint)Shared.CountCharsInString(tempDirEntry, Path.DirectorySeparatorChar); //Recursion int size = u8Nodes.Count + 2; for (int j = 0; j < dirEntries.Length; j++) if (dirEntries[j].Contains(dirEntries[i] + "\\")) size++; tempNode.SizeOfData = (uint)size; } else //It's a file { fireDebug(" -> File"); fireDebug(" -> Reading File Data..."); tempData = File.ReadAllBytes(dirEntries[i]); tempNode.Type = U8_NodeType.File; tempNode.OffsetToData = (uint)offsetToData; tempNode.SizeOfData = (uint)tempData.Length; offsetToData += Shared.AddPadding(offsetToData + tempData.Length, dataPadding); } tempNode.OffsetToName = (ushort)offsetToName; offsetToName += (Path.GetFileName(dirEntries[i])).Length + 1; fireDebug(" -> Reading Name..."); string tempName = Path.GetFileName(dirEntries[i]); if (tempName.ToLower() == "icon.bin") iconSize = getRealSize(tempData); else if (tempName.ToLower() == "banner.bin") bannerSize = getRealSize(tempData); else if (tempName.ToLower() == "sound.bin") soundSize = getRealSize(tempData); u8Nodes.Add(tempNode); stringTable.Add(tempName); data.Add(tempData); } //Update U8 Header fireDebug(" Updating U8 Header..."); u8Header.HeaderSize = (uint)(((u8Nodes.Count + 1) * 12) + offsetToName); u8Header.OffsetToData = (uint)Shared.AddPadding((int)u8Header.OffsetToRootNode + (int)u8Header.HeaderSize, dataPadding); //Update dataOffsets fireDebug(" Calculating Data Offsets..."); for (int i = 0; i < u8Nodes.Count; i++) { fireDebug(" -> Node #{0} of {1}...", i + 1, u8Nodes.Count); int tempOffset = (int)u8Nodes[i].OffsetToData; u8Nodes[i].OffsetToData = (uint)(u8Header.OffsetToData + tempOffset); } fireDebug("Creating U8 File Finished..."); } private string[] getDirContent(string dir, bool root) { string[] files = Directory.GetFiles(dir); string[] dirs = Directory.GetDirectories(dir); string all = ""; if (!root) 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); } private int getRealSize(byte[] data) { if (data[0] == 'I' && data[1] == 'M' && data[2] == 'D' && data[3] == '5') if (data[0x20] == 'L' && data[0x21] == 'Z' && data[0x22] == '7' && data[0x23] == '7') return (BitConverter.ToInt32(data, 0x24)) >> 8; else return data.Length - 32; return data.Length; } private void addEntry(string nodePath, byte[] fileData) { //Parse path if (nodePath.StartsWith("/")) nodePath = nodePath.Remove(0, 1); string[] path = nodePath.Split('/'); //Last entry is the filename int nodeIndex = -1; int maxIndex = (u8Nodes.Count > 0) ? u8Nodes.Count - 1 : 0; int currentIndex = 0; List pathIndices = new List(); for (int i = 0; i < path.Length - 1; i++) { for (int j = currentIndex; j <= maxIndex; j++) { if (stringTable[j].ToLower() == path[i].ToLower()) { if (i == path.Length - 2) nodeIndex = j; maxIndex = (int)u8Nodes[j].SizeOfData - 1; currentIndex = j + 1; pathIndices.Add(j); break; } if (j == maxIndex - 1) throw new Exception("Path wasn't found!"); } } //Get last entry in current dir int lastEntry; if (nodeIndex > -1) lastEntry = (int)u8Nodes[nodeIndex].SizeOfData - 2; else lastEntry = (rootNode.SizeOfData > 1) ? (int)rootNode.SizeOfData - 2 : -1; //Create and insert node + data U8_Node tempNode = new U8_Node(); tempNode.Type = (fileData.Length == 0) ? U8_NodeType.Directory : U8_NodeType.File; tempNode.SizeOfData = (uint)((fileData.Length == 0) ? lastEntry + 2 : fileData.Length); tempNode.OffsetToData = (uint)((fileData.Length == 0) ? Shared.CountCharsInString(nodePath, '/') : 0); stringTable.Insert(lastEntry + 1, path[path.Length - 1]); u8Nodes.Insert(lastEntry + 1, tempNode); data.Insert(lastEntry + 1, fileData); //Update rootnode and path sizes (+1) rootNode.SizeOfData += 1; foreach (int index in pathIndices) if (u8Nodes[index].Type == U8_NodeType.Directory) u8Nodes[index].SizeOfData += 1; for (int i = lastEntry + 1; i < u8Nodes.Count; i++) if (u8Nodes[i].Type == U8_NodeType.Directory) u8Nodes[i].SizeOfData += 1; } private void removeEntry(string nodePath) { //Parse path if (nodePath.StartsWith("/")) nodePath = nodePath.Remove(0, 1); string[] path = nodePath.Split('/'); //Last entry is the filename int nodeIndex = -1; int maxIndex = u8Nodes.Count - 1; int currentIndex = 0; List pathIndices = new List(); for (int i = 0; i < path.Length; i++) { for (int j = currentIndex; j < maxIndex; j++) { if (stringTable[j].ToLower() == path[i].ToLower()) { if (i == path.Length - 1) nodeIndex = j; else pathIndices.Add(j); maxIndex = (int)u8Nodes[j].SizeOfData - 1; currentIndex = j + 1; break; } if (j == maxIndex - 1) throw new Exception("Path wasn't found!"); } } //Remove Node (and subnodes if node is dir) int removed = 0; if (u8Nodes[nodeIndex].Type == U8_NodeType.Directory) { for (int i = (int)u8Nodes[nodeIndex].SizeOfData - 2; i >= nodeIndex; i--) { stringTable.RemoveAt(i); u8Nodes.RemoveAt(i); data.RemoveAt(i); removed++; } } else { stringTable.RemoveAt(nodeIndex); u8Nodes.RemoveAt(nodeIndex); data.RemoveAt(nodeIndex); removed++; } //Update rootnode and path sizes rootNode.SizeOfData -= (uint)removed; foreach (int index in pathIndices) if (u8Nodes[index].Type == U8_NodeType.Directory) u8Nodes[index].SizeOfData -= (uint)removed; for (int i = nodeIndex + 1; i < u8Nodes.Count; i++) if (u8Nodes[i].Type == U8_NodeType.Directory) u8Nodes[i].SizeOfData -= (uint)removed; } #endregion #region Events /// /// Fires the Progress of various operations /// public event EventHandler Progress; /// /// Fires warnings (e.g. when hashes do not match) /// public event EventHandler Warning; /// /// Fires debugging messages. You may write them into a log file or log textbox. /// public event EventHandler Debug; private void fireWarning(string warningMessage) { EventHandler warning = Warning; if (warning != null) warning(new object(), new MessageEventArgs(warningMessage)); } private void fireDebug(string debugMessage, params object[] args) { EventHandler debug = Debug; if (debug != null) debug(new object(), new MessageEventArgs(string.Format(debugMessage, args))); } private void fireProgress(int progressPercentage) { EventHandler progress = Progress; if (progress != null) progress(new object(), new ProgressChangedEventArgs(progressPercentage, string.Empty)); } #endregion } public class U8_Header { private uint u8Magic = 0x55AA382D; private uint offsetToRootNode = 0x20; private uint headerSize; private uint offsetToData; private byte[] padding = new byte[16]; public uint U8Magic { get { return u8Magic; } } public uint OffsetToRootNode { get { return offsetToRootNode; } } public uint HeaderSize { get { return headerSize; } set { headerSize = value; } } public uint OffsetToData { get { return offsetToData; } set { offsetToData = value; } } public byte[] Padding { get { return padding; } } public void Write(Stream writeStream) { writeStream.Write(BitConverter.GetBytes(Shared.Swap(u8Magic)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(offsetToRootNode)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(headerSize)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(offsetToData)), 0, 4); writeStream.Write(padding, 0, 16); } } public class U8_Node { private ushort type; private ushort offsetToName; private uint offsetToData; private uint sizeOfData; public U8_NodeType Type { get { return (U8_NodeType)type; } set { type = (ushort)value; } } public ushort OffsetToName { get { return offsetToName; } set { offsetToName = value; } } public uint OffsetToData { get { return offsetToData; } set { offsetToData = value; } } public uint SizeOfData { get { return sizeOfData; } set { sizeOfData = value; } } public void Write(Stream writeStream) { writeStream.Write(BitConverter.GetBytes(Shared.Swap(type)), 0, 2); writeStream.Write(BitConverter.GetBytes(Shared.Swap(offsetToName)), 0, 2); writeStream.Write(BitConverter.GetBytes(Shared.Swap(offsetToData)), 0, 4); writeStream.Write(BitConverter.GetBytes(Shared.Swap(sizeOfData)), 0, 4); } } }