Tool dedicated to isohacking for Xenosaga on Playstation 2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

540 lines
20 KiB

using System;
using System.IO;
using System.Diagnostics;
using System.Text;
using Hack.Xenosaga.Common;
namespace Hack.Xenosaga.Process
{
class Unpack
{
private static BinaryWriter _bw;
private const string _listExtension = ".lst";
private const string _copyright = "Hacked by BahaBulle (c)2016\0";
private const int _sectorSize = 0x800;
private const int _maxSizeFile = 0x40000000;
private struct element
{
public bool isDirectory;
public bool isCompressed;
public byte level;
public string name;
public UInt32 sector;
public UInt32 position;
public UInt32 sizeIn;
public UInt32 sizeOut;
}
#region Private methods
private static int getIdFile(UInt32 position, int indexSize)
{
double d = (position - indexSize) / _maxSizeFile;
return (int)Math.Floor(d) + 1;
}
private static bool readElement(BinaryReader br, ref element e)
{
byte size = br.ReadByte();
if (size == 0)
return false;
if ((size & 0x80) != 0)
{
size -= 2;
e.isDirectory = true;
e.level = br.ReadByte();
e.name = Encoding.ASCII.GetString(br.ReadBytes(size & 0x7F));
}
else
{
size--;
e.isDirectory = false;
e.level = 0;
e.name = Encoding.ASCII.GetString(br.ReadBytes(size & 0x3F));
e.sector = br.ReadUInt16() + (uint)br.ReadByte() * 0x10000;
e.position = e.sector * _sectorSize;
e.sizeIn = br.ReadUInt32();
if ((size & 0x40) != 0)
{
e.sizeOut = br.ReadUInt16() + (uint)br.ReadByte() * 0x10000;
e.isCompressed = true;
}
else
{
e.sizeOut = e.sizeIn;
e.isCompressed = false;
}
}
return true;
}
private static byte readIndex(string asIndexName, listPathElement path)
{
byte bIndexNbSector = 0;
int num = 0;
int level;
using (BinaryReader brIndex = new BinaryReader(File.Open(asIndexName, FileMode.Open)))
{
pathElement current = new pathElement(true);
bIndexNbSector = brIndex.ReadByte();
element e = new element();
while (readElement(brIndex, ref e))
{
level = e.level;
while (e.level-- > 0)
current = current.Parent;
pathElement entry = new pathElement(e.isDirectory) { Name = e.name, Position = e.position, Sector = e.sector, SizeIn = e.sizeIn, SizeOut = e.sizeOut, Id = num++, Level = level, IsCompressed = e.isCompressed };
current.addEntry(entry);
path.addToIndex(entry);
if (e.isDirectory)
current = entry;
}
}
return bIndexNbSector;
}
private static void writeIndex(string as_file, byte ai_nbSector, listPathElement index)
{
index.SortById();
using (BinaryWriter bwIndex = new BinaryWriter(File.Open(Variables.dirPack + as_file, FileMode.Create)))
{
bwIndex.Write(ai_nbSector);
foreach (pathElement entryPath in index.getEntries())
{
if (entryPath.Name != "")
{
if (entryPath.IsDirectory)
{
bwIndex.Write((byte)(entryPath.Name.Length + 0x82));
bwIndex.Write((byte)entryPath.Level);
bwIndex.Write(entryPath.Name.ToCharArray());
}
else
{
if (entryPath.IsCompressed)
bwIndex.Write((byte)(entryPath.Name.Length + 0x41));
else
bwIndex.Write((byte)(entryPath.Name.Length + 1));
bwIndex.Write(entryPath.Name.ToCharArray());
bwIndex.Write((UInt16)entryPath.Sector);
bwIndex.Write((byte)(entryPath.Sector / 0x10000));
bwIndex.Write((UInt32)entryPath.SizeIn);
if (entryPath.IsCompressed)
{
bwIndex.Write((UInt16)entryPath.SizeOut);
bwIndex.Write((byte)(entryPath.SizeOut / 0x10000));
}
}
}
}
bwIndex.Write((byte)0);
int size = (int)bwIndex.BaseStream.Length;
padding(bwIndex, ref size);
}
}
private static void padding(BinaryWriter a_bw, ref int size)
{
char[] hack = _copyright.ToCharArray();
int id = 0;
while (size % _sectorSize != 0)
{
a_bw.Write(hack[id++]);
size++;
if (id >= hack.Length)
id = 0;
}
}
private static int AddFileToStream(string pathname, ref int num, BinaryReader br, int size, bool regroup)
{
if (_bw == null)
_bw = new BinaryWriter(File.Open(string.Format("{0}{1:00}", pathname, num), FileMode.Create));
long pos = _bw.BaseStream.Position;
int size_rest = _maxSizeFile - (int)_bw.BaseStream.Length;
if (!regroup && size > size_rest)
{
_bw.Write(br.ReadBytes(size_rest), 0, size_rest);
_bw.Close();
_bw.Dispose();
num++;
_bw = new BinaryWriter(File.Open(string.Format("{0}{1:00}", pathname, num), FileMode.Create));
_bw.Write(br.ReadBytes(size - size_rest), 0, size - size_rest);
}
else
_bw.Write(br.ReadBytes(size), 0, size);
padding(_bw, ref size);
return size;
}
private static int findFile(pathElement entry, string numberFile)
{
// Check if the file is in the INSERT directory
if (File.Exists(Variables.dirInsert + entry.Name))
return 1;
// Check if the file is in the UNPACK directory
if (File.Exists(Variables.dirUnpack + numberFile + entry.FullPath))
return 2;
// Otherwise, get the file from iso files
return -1;
}
#endregion
#region Public methods
/// <summary>
/// Unpack all files from an index
/// </summary>
/// <param name="indexName">Pathname of the index</param>
public static void unpackIsoFiles(string indexName)
{
BinaryReader br = null;
byte bIndexNbSector = 0;
int numFileIndex;
int numFileOpen = -1;
int iIndexSize = 0;
string fileNameBase = Path.GetFileNameWithoutExtension(indexName);
int.TryParse(Path.GetExtension(indexName).Substring(1), out numFileIndex);
string directoryName = Variables.dirUnpack + string.Format("{0:D2}", numFileIndex);
listPathElement index = new listPathElement();
Trace.Write(string.Format("Reading index ({0}) : ", indexName));
// Lecture de l'index
try
{
bIndexNbSector = readIndex(indexName, index);
index.SortBySector();
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("ERROR : {0}", ex.Message));
return;
}
Trace.WriteLine("OK");
iIndexSize = bIndexNbSector * _sectorSize;
try
{
Trace.WriteLine("Extracting files");
Trace.Indent();
foreach (pathElement entryPath in index.getEntries())
{
if (!entryPath.IsDirectory)
{
Trace.WriteLine(string.Format("Create file {0}", entryPath.FullPath));
Directory.CreateDirectory(directoryName + Path.GetDirectoryName(entryPath.FullPath));
int id = getIdFile(entryPath.Position, iIndexSize) + numFileIndex;
double pos = (entryPath.Position - iIndexSize) % _maxSizeFile;
if (numFileOpen == -1 || numFileOpen != id)
{
if (br != null)
{
br.Close();
br.Dispose();
}
br = new BinaryReader(File.Open(string.Format("{0}.{1:D2}", fileNameBase, id), FileMode.Open));
numFileOpen = id;
}
br.BaseStream.Seek((int)pos, SeekOrigin.Begin);
int size_rest = (int)br.BaseStream.Length - (int)pos;
using (BinaryWriter bw = new BinaryWriter(File.Open(directoryName + entryPath.FullPath, FileMode.Create)))
{
if (size_rest >= entryPath.SizeIn)
bw.Write(br.ReadBytes((int)entryPath.SizeIn));
else
{
bw.Write(br.ReadBytes(size_rest));
int num = id + 1;
if (br != null)
{
br.Close();
br.Dispose();
}
br = new BinaryReader(File.Open(string.Format("{0}.{1:D2}", fileNameBase, num), FileMode.Open));
numFileOpen = num;
bw.Write(br.ReadBytes((int)entryPath.SizeIn - size_rest));
}
}
}
}
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("==> ERROR : {0}", ex.Message));
return;
}
finally
{
Trace.Unindent();
}
}
/// <summary>
/// Pack all files to index
/// </summary>
/// <param name="indexName">Pathname of the index</param>
/// <param name="regroup">Used to know if the program pack files in 1 ou more files</param>
public static void packIsoFiles(string indexName, bool regroup)
{
BinaryReader br = null;
byte bIndexNbSector = 0;
long l_sector = 0;
long l_position = 0;
int iIndexSize = 0;
int numFileIndex;
int numFileWrite = -1;
int idSave = -1;
int.TryParse(Path.GetExtension(indexName).Substring(1), out numFileIndex);
string fileNameBase = Path.GetFileNameWithoutExtension(indexName);
string filename = "";
string directoryUnpackName = Variables.dirUnpack + string.Format("{0:D2}", numFileIndex);
listPathElement index = new listPathElement();
Trace.Write(string.Format("Reading index ({0}) : ", indexName));
// Lecture de l'index
try
{
bIndexNbSector = readIndex(indexName, index);
index.SortBySector();
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("ERROR : {0}", ex.Message));
return;
}
l_sector += bIndexNbSector;
iIndexSize = bIndexNbSector * _sectorSize;
l_position += iIndexSize;
numFileWrite = numFileIndex + 1;
try
{
Trace.WriteLine("Inserting files");
Trace.Indent();
string s_pathname = string.Format("{0}{1}.", Variables.dirPack, fileNameBase);
Directory.CreateDirectory(Variables.dirPack);
foreach (pathElement entryPath in index.getEntries())
{
if (!entryPath.IsDirectory)
{
long size = 0;
int idFile = findFile(entryPath, string.Format("{0:D2}", numFileIndex));
if (idFile == 1 || idFile == 2)
{
if (idFile == 1)
{
Trace.WriteLine(string.Format("From {0} : file {1}", Variables.dirInsert, entryPath.FullPath));
filename = Variables.dirInsert + entryPath.Name;
}
else
{
Trace.WriteLine(string.Format("From {0} : file {1}", Variables.dirUnpack, entryPath.FullPath));
filename = directoryUnpackName + entryPath.FullPath;
}
using (BinaryReader brFile = new BinaryReader(File.Open(filename, FileMode.Open)))
{
size = brFile.BaseStream.Length;
if (entryPath.IsCompressed)
{
// Compression du fichier
entryPath.SizeIn = (UInt32)size;
//entryPath.SizeOut = entryPath.SizeIn;
}
else
{
entryPath.SizeIn = (UInt32)size;
//entryPath.SizeOut = entryPath.SizeIn;
}
int size_new = AddFileToStream(s_pathname, ref numFileWrite, brFile, (int)size, regroup);
entryPath.Position = (UInt32)l_position;
entryPath.Sector = entryPath.Position / _sectorSize;
l_position += size_new;
}
}
else
{
int id = getIdFile(entryPath.Position, iIndexSize) + numFileIndex;
filename = string.Format("{0}.{1:D2}", fileNameBase, id);
Trace.WriteLine(string.Format("From {0} : file {1}", filename, entryPath.FullPath));
size = entryPath.SizeIn;
double pos = (entryPath.Position - iIndexSize) % _maxSizeFile;
int size_new = 0;
if (br != null && id != idSave)
{
br.Close();
br.Dispose();
br = null;
}
if (br == null)
{
br = new BinaryReader(File.Open(filename, FileMode.Open));
idSave = id;
}
br.BaseStream.Seek((int)pos, SeekOrigin.Begin);
int size_rest = (int)br.BaseStream.Length - (int)pos;
if (size_rest >= size)
size_new = AddFileToStream(s_pathname, ref numFileWrite, br, (int)size, regroup);
else
{
int sizeTemp = AddFileToStream(s_pathname, ref numFileWrite, br, size_rest, regroup);
int num = id + 1;
size_new = sizeTemp;
if (br != null)
{
br.Close();
br.Dispose();
}
filename = string.Format("{0}.{1:D2}", fileNameBase, num);
br = new BinaryReader(File.Open(filename, FileMode.Open));
idSave = num;
sizeTemp = AddFileToStream(s_pathname, ref numFileWrite, br, (int)entryPath.SizeIn - size_rest, regroup);
size_new += sizeTemp;
}
entryPath.Position = (UInt32)l_position;
entryPath.Sector = entryPath.Position / _sectorSize;
l_position += size_new;
}
}
}
_bw.Close();
_bw.Dispose();
writeIndex(indexName, bIndexNbSector, index);
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("==> ERROR : {0}", ex.Message));
return;
}
finally
{
Trace.Unindent();
}
}
/// <summary>
/// List files of an index
/// </summary>
/// <param name="indexName">Pathname of the index</param>
public static void listFiles(string indexName)
{
string outputName;
byte bIndexNbSector = 0;
int iIndexSize;
int numFile;
int.TryParse(Path.GetExtension(indexName).Substring(1), out numFile);
listPathElement index = new listPathElement();
Trace.Write(string.Format("Reading index file ({0}) : ", indexName));
try
{
Directory.CreateDirectory(Variables.dirUnpack);
outputName = string.Format("{0}{1}{2}", Variables.dirUnpack, indexName, _listExtension);
using (StreamWriter sw = new StreamWriter(outputName))
{
Functions.ManageListener(false, true, sw);
bIndexNbSector = readIndex(indexName, index);
index.SortBySector();
iIndexSize = bIndexNbSector * _sectorSize;
foreach (pathElement entryPath in index.getEntries())
{
if (!entryPath.IsDirectory)
{
double d = (entryPath.Position - iIndexSize) / _maxSizeFile;
int id = (int)Math.Floor(d) + 1;
Trace.WriteLine(string.Format("{0,-36}Sector={1,-15}SizeIn={2,-15}SizeOut={3,-15}File=xenosaga.{4:D2}", entryPath.FullPath, entryPath.Sector, entryPath.SizeIn, entryPath.SizeOut, id + numFile));
}
}
Functions.ManageListener(true, false);
}
Trace.WriteLine("OK");
}
catch (Exception ex)
{
Trace.WriteLine(string.Format("ERROR : {0}", ex.Message));
return;
}
}
#endregion
}
}