/// <summary> /// Get the directory at the end of this path, or make it (and all /// intermediate dirs) if it doesn't exist. /// </summary> /// <param name="path"></param> /// <returns></returns> private ArkDirectory makeOrGetDir(string path) { if (path == null || path == "") { path = "."; } string[] breadcrumbs = path.Split('/'); IDirectory last = root; IDirectory current; if (breadcrumbs[0] == "." && breadcrumbs.Length == 1) { return(root); } for (var idx = 0; idx < breadcrumbs.Length; idx++) { if (!last.TryGetDirectory(breadcrumbs[idx], out current)) { current = new ArkDirectory(last, breadcrumbs[idx]); (last as ArkDirectory).AddDir(current as ArkDirectory); } last = current; } return(last as ArkDirectory); }
private void readOldArkHeader(IFile hdrFile, Stream header) { root = new ArkDirectory(null, ROOT_DIR); contentFileMeta = hdrFile.GetStream(); header.Position = 8; long fileTableOffset = header.ReadUInt32LE(); uint numFiles = header.ReadUInt32LE(); long dirTableOffset = header.ReadUInt32LE(); string[] dirs = new string[header.ReadUInt32LE()]; header.Position = 0x24; long blockSize = header.ReadUInt32LE(); header.Position = dirTableOffset; for (int dir = 0; dir < dirs.Length; dir++) { header.Position = dirTableOffset + (8 * dir) + 4; header.Position = header.ReadUInt32LE(); dirs[dir] = header.ReadASCIINullTerminated(); } for (int file = 0; file < numFiles; file++) { header.Position = fileTableOffset + (24 * file) + 4; long filenameOffset = header.ReadUInt32LE(); int dir = header.ReadUInt16LE(); uint blockOffset = header.ReadUInt16LE(); uint block = header.ReadUInt32LE(); uint compressedSize = header.ReadUInt32LE(); header.Position = filenameOffset; string filename = header.ReadASCIINullTerminated(); ArkDirectory parent = dir > 0 ? makeOrGetDir(dirs[dir]) : root; parent.AddFile(new OffsetFile(filename, parent, contentFileMeta, block * blockSize + blockOffset, compressedSize)); } }
/// <summary> /// Read the filename table, which is a blob of strings, /// then read the filename pointer table which links files to filenames /// </summary> private void readFileTable(Stream header, uint version, bool brokenv4) { string[] fileNames = readFileNames(header, version != 1); if (version > 2) { // Version 3+: // Go to end of the filename pointer table which we already read // filename blob filename pointer table header.Seek(fileNameTableOffset + fileNameTableSize + 4 + 4 * numFileNames, SeekOrigin.Begin); } else { // Version 2: still at beginning of file. (after version number ofc) header.Seek(4, SeekOrigin.Begin); } // Now the number of *actual* files in the archive. // Directories are not explicitly stored. Rather, they are inferred // by the path string each file has, which tells you in which folder // the file lives. uint numFiles = header.ReadUInt32LE(); for (var i = 0; i < numFiles; i++) { // Version 3 uses 32-bit file offsets long arkFileOffset = 0; if (version != 1) { arkFileOffset = (brokenv4 || version <= 3 ? header.ReadUInt32LE() : header.ReadInt64LE()); } int filenameStringId = header.ReadInt32LE(); int dirStringId = header.ReadInt32LE(); if (version == 1) { arkFileOffset = header.ReadUInt32LE(); } uint size = header.ReadUInt32LE(); uint zero = header.ReadUInt32LE(); // Version 7 uses this differently. now, files marked as 0 should be skipped, // while NON-zero values mean real files. I think. if ((version == 7 && zero != 0) || (version != 7 && zero == 0)) { ArkDirectory parent = dirStringId > 0 ? makeOrGetDir(fileNames[dirStringId]) : root; parent.AddFile(new OffsetFile(fileNames[filenameStringId], parent, contentFileMeta, arkFileOffset, size)); } } }
private void readHeader(Stream header, IFile hdrFile, uint version, bool brokenv4 = false) { root = new ArkDirectory(null, ROOT_DIR); if (version >= 6) // Versions 6,7 have some sort of hash/key at the beginning? { header.Seek(4 + 16, SeekOrigin.Current); // skip unknown data } if (version > 2) { // Version 3+: two counts of .ark files int numArks = header.ReadInt32LE(); int numArks2 = header.ReadInt32LE(); if (numArks != numArks2) { throw new InvalidDataException("Ark header appears to be invalid (.ark count mismatch)."); } arkFileSizes = new long[numArks]; for (var i = 0; i < numArks; i++) { // All versions except 4 use 32-bit file sizes. arkFileSizes[i] = (version == 4 ? header.ReadInt64LE() : header.ReadInt32LE()); if (version == 4 && arkFileSizes[i] > UInt32.MaxValue) { // RB TrackPack Classic has a broken v4, really a v5 mixed with v3 header.Position = 4; readHeader(header, hdrFile, 5, true); return; } totalArkFileSizes += arkFileSizes[i]; } // Version 5+: List of .ark file paths (from root of game disc) if (version >= 5) { int numArkPaths = header.ReadInt32LE(); if (numArkPaths != numArks) { throw new InvalidDataException("Ark header appears to be invalid (.ark count mismatch)."); } contentFiles = new Stream[numArks]; for (var i = 0; i < numArkPaths; i++) { IFile arkFile = hdrFile.Parent.GetFile(header.ReadLengthUTF8().Split('/').Last()); if (version == 10) { var tmpStream = arkFile.GetStream(); if (tmpStream.Length > 32) { tmpStream.Seek(-32, SeekOrigin.End); if (tmpStream.ReadASCIINullTerminated(32) == "mcnxyxcmvmcxyxcmskdldkjshagsdhfj") { contentFiles[i] = new ProtectedFileStream(tmpStream); continue; } } tmpStream.Close(); } contentFiles[i] = arkFile.GetStream(); } // Versions 6,7,9: appear to have checksums or something for each content file? if (version >= 6 && version <= 9) { uint numChecksums = header.ReadUInt32LE(); header.Seek(4 * numChecksums, SeekOrigin.Current); } } else // Versions <5: Just infer the .ark paths based on the .hdr filename. { contentFiles = new Stream[numArks]; for (var i = 0; i < numArks2; i++) { IFile arkFile = hdrFile.Parent.GetFile(hdrFile.Name.Substring(0, hdrFile.Name.Length - 4) + "_" + i + ".ark"); contentFiles[i] = arkFile.GetStream(); } } contentFileMeta = new MultiStream(contentFiles); // new in version 7+: some sort of string table with game-specific data if (version >= 7) { uint root_count = header.ReadUInt32LE(); while (root_count-- > 0) { uint num_strs = header.ReadUInt32LE(); while (num_strs-- > 0) // skip past strings because we don't need them. { header.Seek(header.ReadUInt32LE(), SeekOrigin.Current); } } } if (version < 8) // Gonna assume 8 has new file table { readFileTable(header, version, brokenv4); } else { readNewFileTable(header, version < 10); } } else if (version == 1 || version == 2) { // Version 1,2: Here be file records. Skip 'em for now. uint numRecords = header.ReadUInt32LE(); header.Close(); contentFileMeta = hdrFile.GetStream(); contentFileMeta.Seek(8 + numRecords * 20, SeekOrigin.Begin); readFileTable(contentFileMeta, version, brokenv4); } }