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 /// /// Unpack all files from an index /// /// Pathname of the index 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(); } } /// /// Pack all files to index /// /// Pathname of the index /// Used to know if the program pack files in 1 ou more files 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(); } } /// /// List files of an index /// /// Pathname of the index 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 } }