mirror of
https://github.com/WB3000/nusdownloader.git
synced 2024-11-16 22:59:22 +01:00
c67e219ac3
common key now from libwiisharp
1121 lines
41 KiB
C#
1121 lines
41 KiB
C#
/* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
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<U8_Node> u8Nodes = new List<U8_Node>();
|
|
private List<string> stringTable = new List<string>();
|
|
private List<byte[]> data = new List<byte[]>();
|
|
private int iconSize = -1;
|
|
private int bannerSize = -1;
|
|
private int soundSize = -1;
|
|
private bool lz77 = false;
|
|
|
|
/// <summary>
|
|
/// The type of the Header of the U8 file.
|
|
/// </summary>
|
|
public Headers.HeaderType HeaderType { get { return headerType; } }
|
|
/// <summary>
|
|
/// The Header of the U8 file as an object. Will be null if the file has no Header.
|
|
/// </summary>
|
|
public object Header { get { return header; } }
|
|
/// <summary>
|
|
/// The Rootnode of the U8 file.
|
|
/// </summary>
|
|
public U8_Node RootNode { get { return rootNode; } }
|
|
/// <summary>
|
|
/// The Nodes of the U8 file.
|
|
/// </summary>
|
|
public List<U8_Node> Nodes { get { return u8Nodes; } }
|
|
/// <summary>
|
|
/// The string table of the U8 file.
|
|
/// </summary>
|
|
public string[] StringTable { get { return stringTable.ToArray(); } }
|
|
/// <summary>
|
|
/// The actual data (files) in the U8 file. Will be an empty byte array for directory entries.
|
|
/// </summary>
|
|
public byte[][] Data { get { return data.ToArray(); } }
|
|
|
|
/// <summary>
|
|
/// The Number of Nodes WITHOUT the Rootnode.
|
|
/// </summary>
|
|
public int NumOfNodes { get { return (int)rootNode.SizeOfData - 1; } }
|
|
/// <summary>
|
|
/// The size of the icon.bin (if the U8 files contains an icon.bin).
|
|
/// </summary>
|
|
public int IconSize { get { return iconSize; } }
|
|
/// <summary>
|
|
/// The size of the banner.bin (if the U8 files contains an banner.bin).
|
|
/// </summary>
|
|
public int BannerSize { get { return bannerSize; } }
|
|
/// <summary>
|
|
/// The size of the sound.bin (if the U8 files contains an sound.bin).
|
|
/// </summary>
|
|
public int SoundSize { get { return soundSize; } }
|
|
/// <summary>
|
|
/// If true, the U8 file will be Lz77 compressed while saving.
|
|
/// </summary>
|
|
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
|
|
/// <summary>
|
|
/// Checks whether a file is a U8 file or not.
|
|
/// </summary>
|
|
/// <param name="pathToFile"></param>
|
|
/// <returns></returns>
|
|
public static bool IsU8(string pathToFile)
|
|
{
|
|
return IsU8(File.ReadAllBytes(pathToFile));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks whether a file is a U8 file or not.
|
|
/// </summary>
|
|
/// <param name="file"></param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Loads a U8 file.
|
|
/// </summary>
|
|
/// <param name="pathToU8"></param>
|
|
/// <returns></returns>
|
|
public static U8 Load(string pathToU8)
|
|
{
|
|
return Load(File.ReadAllBytes(pathToU8));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a U8 file.
|
|
/// </summary>
|
|
/// <param name="u8File"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a U8 file.
|
|
/// </summary>
|
|
/// <param name="u8File"></param>
|
|
/// <returns></returns>
|
|
public static U8 Load(Stream u8File)
|
|
{
|
|
U8 u = new U8();
|
|
u.parseU8(u8File);
|
|
return u;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a U8 file.
|
|
/// </summary>
|
|
/// <param name="pathToDirectory"></param>
|
|
/// <returns></returns>
|
|
public static U8 FromDirectory(string pathToDirectory)
|
|
{
|
|
U8 u = new U8();
|
|
u.createFromDir(pathToDirectory);
|
|
return u;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Loads a U8 file.
|
|
/// </summary>
|
|
/// <param name="pathToU8"></param>
|
|
public void LoadFile(string pathToU8)
|
|
{
|
|
LoadFile(File.ReadAllBytes(pathToU8));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a U8 file.
|
|
/// </summary>
|
|
/// <param name="u8File"></param>
|
|
public void LoadFile(byte[] u8File)
|
|
{
|
|
MemoryStream ms = new MemoryStream(u8File);
|
|
|
|
try { parseU8(ms); }
|
|
catch { ms.Dispose(); throw; }
|
|
|
|
ms.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads a U8 file.
|
|
/// </summary>
|
|
/// <param name="u8File"></param>
|
|
public void LoadFile(Stream u8File)
|
|
{
|
|
parseU8(u8File);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a U8 file.
|
|
/// </summary>
|
|
/// <param name="pathToDirectory"></param>
|
|
public void CreateFromDirectory(string pathToDirectory)
|
|
{
|
|
createFromDir(pathToDirectory);
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Saves the U8 file.
|
|
/// </summary>
|
|
/// <param name="savePath"></param>
|
|
public void Save(string savePath)
|
|
{
|
|
if (File.Exists(savePath)) File.Delete(savePath);
|
|
|
|
using (FileStream fs = new FileStream(savePath, FileMode.Create))
|
|
writeToStream(fs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the U8 file as a memory stream.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public MemoryStream ToMemoryStream()
|
|
{
|
|
MemoryStream ms = new MemoryStream();
|
|
|
|
try { writeToStream(ms); }
|
|
catch { ms.Dispose(); throw; }
|
|
|
|
return ms;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the U8 file as a byte array.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public byte[] ToByteArray()
|
|
{
|
|
return ToMemoryStream().ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unpacks the U8 file to given directory.
|
|
/// </summary>
|
|
/// <param name="saveDir"></param>
|
|
public void Unpack(string saveDir)
|
|
{
|
|
unpackToDir(saveDir);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unpacks the U8 file to given directory.
|
|
/// </summary>
|
|
/// <param name="saveDir"></param>
|
|
public void Extract(string saveDir)
|
|
{
|
|
unpackToDir(saveDir);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds an IMET Header to the U8 file.
|
|
/// </summary>
|
|
/// <param name="shortImet"></param>
|
|
/// <param name="titles"></param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds an IMD5 Header to the U8 file.
|
|
/// </summary>
|
|
public void AddHeaderImd5()
|
|
{
|
|
headerType = Headers.HeaderType.IMD5;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces the file with the given index.
|
|
/// </summary>
|
|
/// <param name="fileIndex"></param>
|
|
/// <param name="pathToNewFile"></param>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replaces the file with the given index.
|
|
/// </summary>
|
|
/// <param name="fileIndex"></param>
|
|
/// <param name="newData"></param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the index of the directory or file with the given name.
|
|
/// If no matching Node is found, -1 will be returned.
|
|
/// </summary>
|
|
/// <param name="fileOrDirName"></param>
|
|
/// <returns></returns>
|
|
public int GetNodeIndex(string fileOrDirName)
|
|
{
|
|
for (int i = 0; i < u8Nodes.Count; i++)
|
|
if (stringTable[i].ToLower() == fileOrDirName.ToLower()) return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the name of a node.
|
|
/// </summary>
|
|
/// <param name="index"></param>
|
|
/// <param name="newName"></param>
|
|
public void RenameNode(int index, string newName)
|
|
{
|
|
stringTable[index] = newName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the name of a node.
|
|
/// </summary>
|
|
/// <param name="oldName"></param>
|
|
/// <param name="newName"></param>
|
|
public void RenameNode(string oldName, string newName)
|
|
{
|
|
stringTable[GetNodeIndex(oldName)] = newName;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a directory to the U8 file.
|
|
/// Path must be like this: "/arc/timg/newFolder".
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
public void AddDirectory(string path)
|
|
{
|
|
addEntry(path, new byte[0]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a file to the U8 file.
|
|
/// Path must be like this: "/arc/timg/newFile.tpl".
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
/// <param name="data"></param>
|
|
public void AddFile(string path, byte[] data)
|
|
{
|
|
addEntry(path, data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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".
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
public void RemoveDirectory(string path)
|
|
{
|
|
removeEntry(path);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a file from the U8 file.
|
|
/// Path must be like this: "/arc/timg/fileToDelete.tpl".
|
|
/// </summary>
|
|
/// <param name="path"></param>
|
|
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<U8_Node>();
|
|
stringTable = new List<string>();
|
|
data = new List<byte[]>();
|
|
|
|
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<U8_Node>();
|
|
stringTable = new List<string>();
|
|
data = new List<byte[]>();
|
|
|
|
//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<int> pathIndices = new List<int>();
|
|
|
|
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<int> pathIndices = new List<int>();
|
|
|
|
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
|
|
/// <summary>
|
|
/// Fires the Progress of various operations
|
|
/// </summary>
|
|
public event EventHandler<ProgressChangedEventArgs> Progress;
|
|
/// <summary>
|
|
/// Fires warnings (e.g. when hashes do not match)
|
|
/// </summary>
|
|
public event EventHandler<MessageEventArgs> Warning;
|
|
/// <summary>
|
|
/// Fires debugging messages. You may write them into a log file or log textbox.
|
|
/// </summary>
|
|
public event EventHandler<MessageEventArgs> Debug;
|
|
|
|
|
|
private void fireWarning(string warningMessage)
|
|
{
|
|
EventHandler<MessageEventArgs> warning = Warning;
|
|
if (warning != null)
|
|
warning(new object(), new MessageEventArgs(warningMessage));
|
|
}
|
|
|
|
private void fireDebug(string debugMessage, params object[] args)
|
|
{
|
|
EventHandler<MessageEventArgs> debug = Debug;
|
|
if (debug != null)
|
|
debug(new object(), new MessageEventArgs(string.Format(debugMessage, args)));
|
|
}
|
|
|
|
private void fireProgress(int progressPercentage)
|
|
{
|
|
EventHandler<ProgressChangedEventArgs> 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);
|
|
}
|
|
}
|
|
}
|