// Read the table of contents of the ARC file private void ReadARCToC() { // Format of an ARC file // 0x08 - 4 bytes = # of files // 0x0C - 4 bytes = # of parts // 0x18 - 4 bytes = offset to directory structure // // Format of directory structure // 4-byte int = offset in file where this part begins // 4-byte int = size of compressed part // 4-byte int = size of uncompressed part // these triplets repeat for each part in the arc file // After these triplets are a bunch of null-terminated strings // which are the sub filenames. // After the subfilenames comes the subfile data: // 4-byte int = 3 == indicates start of subfile item (maybe compressed flag??) // 1 == maybe uncompressed flag?? // 4-byte int = offset in file where first part of this subfile begins // 4-byte int = compressed size of this file // 4-byte int = uncompressed size of this file // 4-byte crap // 4-byte crap // 4-byte crap // 4-byte int = numParts this file uses // 4-byte int = part# of first part for this file (starting at 0). // 4-byte int = length of filename string // 4-byte int = offset in directory structure for filename m_fileHasBeenRead = true; try { FileStream arcFile = new FileStream(m_filename, FileMode.Open, FileAccess.Read); BinaryReader reader = new BinaryReader(arcFile); try { // check the file header if (reader.ReadByte() != 0x41) { return; // A } if (reader.ReadByte() != 0x52) { return; // R } if (reader.ReadByte() != 0x43) { return; // C } if (arcFile.Length < 0x21) { return; } reader.BaseStream.Seek(0x08, SeekOrigin.Begin); int numEntries = reader.ReadInt32(); int numParts = reader.ReadInt32(); ARCPartEntry[] parts = new ARCPartEntry[numParts]; ARCDirEntry[] records = new ARCDirEntry[numEntries]; reader.BaseStream.Seek(0x18, SeekOrigin.Begin); int tocOffset = reader.ReadInt32(); if (arcFile.Length < (tocOffset + 4 * 3)) { return; } // Read in all of the part data reader.BaseStream.Seek(tocOffset, SeekOrigin.Begin); int i; for (i = 0; i < numParts; ++i) { parts[i] = new ARCPartEntry(); parts[i].fileOffset = reader.ReadInt32(); parts[i].compressedSize = reader.ReadInt32(); parts[i].realSize = reader.ReadInt32(); } // Now record this offset so we can come back and read in the filenames after we have // read in the file records int fileNamesOffset = (int)arcFile.Position; // Now seek to the location where the file record data is // This offset is from the end of the file. int fileRecordOffset = 44 * numEntries; arcFile.Seek(-1 * fileRecordOffset, SeekOrigin.End); for (i = 0; i < numEntries; ++i) { records[i] = new ARCDirEntry(); int storageType = reader.ReadInt32(); // storageType = 3 - compressed / 1- non compressed // Added by VillageIdiot to support stored types records[i].storageType = storageType; records[i].fileOffset = reader.ReadInt32(); records[i].compressedSize = reader.ReadInt32(); records[i].realSize = reader.ReadInt32(); int crap = reader.ReadInt32(); // crap crap = reader.ReadInt32(); // crap crap = reader.ReadInt32(); // crap int np = reader.ReadInt32(); if (np < 1) { records[i].parts = null; } else { records[i].parts = new ARCPartEntry[np]; } int firstPart = reader.ReadInt32(); crap = reader.ReadInt32(); // filename length crap = reader.ReadInt32(); // filename offset if (storageType != 1 && records[i].IsActive) { for (int ip = 0; ip < records[i].parts.Length; ++ip) { records[i].parts[ip] = parts[ip + firstPart]; } } } // Now read in the record names arcFile.Seek(fileNamesOffset, SeekOrigin.Begin); byte[] buffer = new byte[2048]; ASCIIEncoding ascii = new ASCIIEncoding(); for (i = 0; i < numEntries; ++i) { // only Active files have a filename entry if (records[i].IsActive) { // For each string, read bytes until I hit a 0x00 byte. int bufSize = 0; while ((buffer[bufSize++] = reader.ReadByte()) != 0x00) { if (buffer[bufSize - 1] == 0x03) { // god damn it arcFile.Seek(-1, SeekOrigin.Current); // backup bufSize--; buffer[bufSize] = 0x00; break; } } string newfile; if (bufSize >= 1) { // Now convert the buffer to a String char[] chars = new char[ascii.GetCharCount(buffer, 0, bufSize - 1)]; ascii.GetChars(buffer, 0, bufSize - 1, chars, 0); newfile = new string(chars); } else { newfile = string.Format("Null File {0}", i); } records[i].filename = NormalizeRecordPath(newfile); } } // Now convert the array of records into a hash table. Hashtable hash = new Hashtable(numEntries); for (i = 0; i < numEntries; ++i) { if (records[i].IsActive) { hash.Add(records[i].filename, records[i]); } } m_dir = hash; BuildKeyTable(); } finally { reader.Close(); } } catch (IOException) { // silently eat these errors } }
// Reads data from an ARC file and puts it into a Byte array (or NULL if not found) public byte[] GetData(string dataID) { if (!m_fileHasBeenRead) { ReadARCToC(); } if (m_dir == null) { return(null); // could not read the file } // First normalize the filename dataID = NormalizeRecordPath(dataID); // Find our file in the toc. // First strip off the leading folder since it is just the ARC name int firstPathDelim = dataID.IndexOf('\\'); if (firstPathDelim != -1) { dataID = dataID.Substring(firstPathDelim + 1); } // Now see if this file is in the toc. ARCDirEntry dirEntry = (ARCDirEntry)m_dir[dataID]; if (dirEntry == null) { return(null); // record not found } // Now open the ARC file and read in the record FileStream arcFile = new FileStream(m_filename, FileMode.Open, FileAccess.Read); try { // Allocate memory for the uncompressed data byte[] ans = new byte[dirEntry.realSize]; // Now process each part of this record int ipos = 0; if ((dirEntry.storageType == 1) && (dirEntry.compressedSize == dirEntry.realSize)) { arcFile.Seek(dirEntry.fileOffset, SeekOrigin.Begin); arcFile.Read(ans, 0, dirEntry.realSize); } else { for (int ipart = 0; ipart < dirEntry.parts.Length; ++ipart) { // seek to the part we want arcFile.Seek(dirEntry.parts[ipart].fileOffset, SeekOrigin.Begin); // Create a decompression stream ZInputStream zinput = new ZInputStream(arcFile); int len; int partLen = 0; while ((len = zinput.read(ans, ipos, ans.Length - ipos)) > 0) { ipos += len; partLen += len; // break out of the read loop if we have processed this part completely. if (partLen >= dirEntry.parts[ipart].realSize) { break; } } } } return(ans); } finally { if (arcFile != null) { arcFile.Close(); } } }
/// <summary> /// Read the table of contents of the ARC file /// </summary> private void ReadARCToC() { // Format of an ARC file // 0x08 - 4 bytes = # of files // 0x0C - 4 bytes = # of parts // 0x18 - 4 bytes = offset to directory structure // // Format of directory structure // 4-byte int = offset in file where this part begins // 4-byte int = size of compressed part // 4-byte int = size of uncompressed part // these triplets repeat for each part in the arc file // After these triplets are a bunch of null-terminated strings // which are the sub filenames. // After the subfilenames comes the subfile data: // 4-byte int = 3 == indicates start of subfile item (maybe compressed flag??) // 1 == maybe uncompressed flag?? // 4-byte int = offset in file where first part of this subfile begins // 4-byte int = compressed size of this file // 4-byte int = uncompressed size of this file // 4-byte crap // 4-byte crap // 4-byte crap // 4-byte int = numParts this file uses // 4-byte int = part# of first part for this file (starting at 0). // 4-byte int = length of filename string // 4-byte int = offset in directory structure for filename this.fileHasBeenRead = true; if (TQDebug.ArcFileDebugLevel > 0) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "ARCFile.ReadARCToC({0})", this.FileName)); } try { using (FileStream arcFile = new FileStream(this.FileName, FileMode.Open, FileAccess.Read)) { using (BinaryReader reader = new BinaryReader(arcFile)) { if (TQDebug.ArcFileDebugLevel > 1) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "File Length={0}", arcFile.Length)); } // check the file header if (reader.ReadByte() != 0x41) { return; } if (reader.ReadByte() != 0x52) { return; } if (reader.ReadByte() != 0x43) { return; } if (arcFile.Length < 0x21) { return; } reader.BaseStream.Seek(0x08, SeekOrigin.Begin); int numEntries = reader.ReadInt32(); int numParts = reader.ReadInt32(); if (TQDebug.ArcFileDebugLevel > 1) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "numEntries={0}, numParts={1}", numEntries, numParts)); } ARCPartEntry[] parts = new ARCPartEntry[numParts]; ARCDirEntry[] records = new ARCDirEntry[numEntries]; if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine("Seeking to tocOffset location"); } reader.BaseStream.Seek(0x18, SeekOrigin.Begin); int tocOffset = reader.ReadInt32(); if (TQDebug.ArcFileDebugLevel > 1) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "tocOffset = {0}", tocOffset)); } // Make sure all 3 entries exist for the toc entry. if (arcFile.Length < (tocOffset + 12)) { return; } // Read in all of the part data reader.BaseStream.Seek(tocOffset, SeekOrigin.Begin); int i; for (i = 0; i < numParts; ++i) { parts[i] = new ARCPartEntry(); parts[i].FileOffset = reader.ReadInt32(); parts[i].CompressedSize = reader.ReadInt32(); parts[i].RealSize = reader.ReadInt32(); if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "parts[{0}]", i)); TQDebug.DebugWriteLine(string.Format( CultureInfo.InvariantCulture, " fileOffset={0}, compressedSize={1}, realSize={2}", parts[i].FileOffset, parts[i].CompressedSize, parts[i].RealSize)); } } // Now record this offset so we can come back and read in the filenames // after we have read in the file records int fileNamesOffset = (int)arcFile.Position; // Now seek to the location where the file record data is // This offset is from the end of the file. int fileRecordOffset = 44 * numEntries; if (TQDebug.ArcFileDebugLevel > 1) { TQDebug.DebugWriteLine(string.Format( CultureInfo.InvariantCulture, "fileNamesOffset = {0}. Seeking to {1} to read file record data.", fileNamesOffset, fileRecordOffset)); } arcFile.Seek(-1 * fileRecordOffset, SeekOrigin.End); for (i = 0; i < numEntries; ++i) { records[i] = new ARCDirEntry(); // storageType = 3 - compressed / 1- non compressed int storageType = reader.ReadInt32(); if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "StorageType={0}", storageType)); } // Added by VillageIdiot to support stored types records[i].StorageType = storageType; records[i].FileOffset = reader.ReadInt32(); records[i].CompressedSize = reader.ReadInt32(); records[i].RealSize = reader.ReadInt32(); int crap = reader.ReadInt32(); // crap if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "Crap2={0}", crap)); } crap = reader.ReadInt32(); // crap if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "Crap3={0}", crap)); } crap = reader.ReadInt32(); // crap if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "Crap4={0}", crap)); } int numberOfParts = reader.ReadInt32(); if (numberOfParts < 1) { records[i].Parts = null; if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "File {0} is not compressed.", i)); } } else { records[i].Parts = new ARCPartEntry[numberOfParts]; } int firstPart = reader.ReadInt32(); crap = reader.ReadInt32(); // filename length if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "Filename Length={0}", crap)); } crap = reader.ReadInt32(); // filename offset if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "Filename Offset={0}", crap)); TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "record[{0}]", i)); TQDebug.DebugWriteLine(string.Format( CultureInfo.InvariantCulture, " offset={0} compressedSize={1} realSize={2}", records[i].FileOffset, records[i].CompressedSize, records[i].RealSize)); if (storageType != 1 && records[i].IsActive) { TQDebug.DebugWriteLine(string.Format( CultureInfo.InvariantCulture, " numParts={0} firstPart={1} lastPart={2}", records[i].Parts.Length, firstPart, firstPart + records[i].Parts.Length - 1)); } else { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, " INACTIVE firstPart={0}", firstPart)); } } if (storageType != 1 && records[i].IsActive) { for (int ip = 0; ip < records[i].Parts.Length; ++ip) { records[i].Parts[ip] = parts[ip + firstPart]; } } } // Now read in the record names arcFile.Seek(fileNamesOffset, SeekOrigin.Begin); byte[] buffer = new byte[2048]; ASCIIEncoding ascii = new ASCIIEncoding(); for (i = 0; i < numEntries; ++i) { // only Active files have a filename entry if (records[i].IsActive) { // For each string, read bytes until I hit a 0x00 byte. if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "Reading entry name {0:n0}", i)); } int bufferSize = 0; while ((buffer[bufferSize++] = reader.ReadByte()) != 0x00) { if (buffer[bufferSize - 1] == 0x03) { // File is null? arcFile.Seek(-1, SeekOrigin.Current); // backup bufferSize--; buffer[bufferSize] = 0x00; if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine("Null file - inactive?"); } break; } if (bufferSize >= buffer.Length) { TQDebug.DebugWriteLine("ARCFile.ReadARCToC() Error - Buffer size of 2048 has been exceeded."); if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine("Buffer contents:\n"); for (int j = 0; j < bufferSize; ++j) { TQDebug.DebugWrite(string.Format(CultureInfo.InvariantCulture, "0x{0:X}", buffer[j])); } TQDebug.DebugWriteLine(String.Empty); } } } if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format( CultureInfo.InvariantCulture, "Read {0:n0} bytes for name. Converting to string.", bufferSize)); } string newfile; if (bufferSize >= 1) { // Now convert the buffer to a string char[] chars = new char[ascii.GetCharCount(buffer, 0, bufferSize - 1)]; ascii.GetChars(buffer, 0, bufferSize - 1, chars, 0); newfile = new string(chars); } else { newfile = string.Format(CultureInfo.InvariantCulture, "Null File {0}", i); } records[i].FileName = TQData.NormalizeRecordPath(newfile); if (TQDebug.ArcFileDebugLevel > 2) { TQDebug.DebugWriteLine(string.Format(CultureInfo.InvariantCulture, "Name {0:n0} = '{1}'", i, records[i].FileName)); } } } // Now convert the array of records into a Dictionary. Dictionary <string, ARCDirEntry> dictionary = new Dictionary <string, ARCDirEntry>(numEntries); if (TQDebug.ArcFileDebugLevel > 1) { TQDebug.DebugWriteLine("Creating Dictionary"); } for (i = 0; i < numEntries; ++i) { if (records[i].IsActive) { dictionary.Add(records[i].FileName, records[i]); } } this.directoryEntries = dictionary; if (TQDebug.ArcFileDebugLevel > 0) { TQDebug.DebugWriteLine("Exiting ARCFile.ReadARCToC()"); } } } } catch (IOException exception) { // Turn on debugging. if (!TQDebug.DebugEnabled) { TQDebug.DebugEnabled = true; } // Write the errors to the debug log. TQDebug.DebugWriteLine("ARCFile.ReadARCToC() - Error reading arcfile"); TQDebug.DebugWriteLine(exception.ToString()); } }