/// <summary> /// Update the dat count within the index files. /// </summary> /// <param name="dataFile">The data file to update the index for.</param> /// <param name="datNum">The dat number to update to.</param> public void UpdateIndexDatCount(XivDataFile dataFile, int datNum, bool alreadySemaphoreLocked = false) { var datCount = (byte)(datNum + 1); var indexPaths = new[] { Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{IndexExtension}"), Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{Index2Extension}") }; if (!alreadySemaphoreLocked) { _semaphoreSlim.Wait(); } try { foreach (var indexPath in indexPaths) { using (var bw = new BinaryWriter(File.OpenWrite(indexPath))) { bw.BaseStream.Seek(1104, SeekOrigin.Begin); bw.Write(datCount); } } } finally { if (!alreadySemaphoreLocked) { _semaphoreSlim.Release(); } } }
/// <summary> /// Gets the dat count within the index files. /// </summary> /// <param name="dataFile">The data file to update the index for.</param> public (int Index1, int Index2) GetIndexDatCount(XivDataFile dataFile) { int index1 = 0, index2 = 0; var indexPaths = new[] { _gameDirectory + "\\" + dataFile.GetDataFileName() + IndexExtension, _gameDirectory + "\\" + dataFile.GetDataFileName() + Index2Extension }; for (var i = 0; i < indexPaths.Length; i++) { using (var br = new BinaryReader(File.OpenRead(indexPaths[i]))) { br.BaseStream.Seek(1104, SeekOrigin.Begin); if (i == 0) { index1 = br.ReadByte(); } else { index2 = br.ReadByte(); } } } return(index1, index2); }
/// <summary> /// Gets the dat count within the index files. /// </summary> /// <param name="dataFile">The data file to update the index for.</param> public (int Index1, int Index2) GetIndexDatCount(XivDataFile dataFile) { int index1 = 0, index2 = 0; var indexPaths = new[] { Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{IndexExtension}"), Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{Index2Extension}") }; _semaphoreSlim.Wait(); try { for (var i = 0; i < indexPaths.Length; i++) { using (var br = new BinaryReader(File.OpenRead(indexPaths[i]))) { br.BaseStream.Seek(1104, SeekOrigin.Begin); if (i == 0) { index1 = br.ReadByte(); } else { index2 = br.ReadByte(); } } } } finally { _semaphoreSlim.Release(); } return(index1, index2); }
/// <summary> /// Creates an Index File object from the game index files. /// </summary> /// <param name="dataFile"></param> /// <returns></returns> public async Task <IndexFile> GetIndexFile(XivDataFile dataFile, bool alreadySemaphoreLocked = false, bool allowReadOnly = false) { var index1Path = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{IndexExtension}"); var index2Path = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{Index2Extension}"); if (!alreadySemaphoreLocked) { await _semaphoreSlim.WaitAsync(); } try { IndexFile index; if (!allowReadOnly) { // If we're getting a writeable index, we need to get a fresh copy to avoid polluting the cache. using (var index1Stream = new BinaryReader(File.OpenRead(index1Path))) { using (var index2Stream = new BinaryReader(File.OpenRead(index2Path))) { index = new IndexFile(dataFile, index1Stream, index2Stream); } } return(index); } else { var lastTime = File.GetLastWriteTimeUtc(index1Path).Ticks; var creationTime = File.GetCreationTimeUtc(index1Path).Ticks; // If we don't have the file cached or the write time doesn't match exactly. if (!_ReadOnlyIndexLastModifiedTime.ContainsKey(dataFile) || lastTime != _ReadOnlyIndexLastModifiedTime[dataFile] || lastTime == creationTime || lastTime == 0) { using (var index1Stream = new BinaryReader(File.OpenRead(index1Path))) { index = new IndexFile(dataFile, index1Stream, null); } _ReadOnlyIndexLastModifiedTime[dataFile] = lastTime; _CachedReadOnlyIndexFiles[dataFile] = index; return(index); } else { return(_CachedReadOnlyIndexFiles[dataFile]); } } } finally { if (!alreadySemaphoreLocked) { _semaphoreSlim.Release(); } } }
public Task <bool> CheckForOutdatedBackups(XivDataFile dataFile, DirectoryInfo backupsDirectory) { return(Task.Run(() => { var backupDataFile = new DirectoryInfo(Path.Combine(backupsDirectory.FullName, $"{dataFile.GetDataFileName()}.win32.index")); var currentDataFile = new DirectoryInfo(Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}.win32.index")); var backupHash = _index.GetIndexSection1Hash(backupDataFile); var currentHash = _index.GetIndexSection1Hash(currentDataFile); return backupHash.SequenceEqual(currentHash); })); }
/// <summary> /// Determines whether the given folder path exists /// </summary> /// <param name="folderHash">The hashed folder</param> /// <param name="dataFile">The data file</param> /// <returns>True if it exists, False otherwise</returns> public bool FolderExists(int folderHash, XivDataFile dataFile) { var indexPath = _gameDirectory + "\\" + dataFile.GetDataFileName() + IndexExtension; // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; using (var br = new BinaryReader(File.OpenRead(indexPath))) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var numOfFiles = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); for (var i = 0; i < numOfFiles; br.ReadBytes(4), i += 16) { var fileNameHash = br.ReadInt32(); var folderPathHash = br.ReadInt32(); if (folderPathHash == folderHash) { return(true); } br.ReadBytes(4); } } return(false); }
/// <summary> /// Gets all the folder hashes in a given folder path /// </summary> /// <param name="dataFile">The data file to look in</param> /// <returns>A list of all of the folder hashes</returns> public List <int> GetAllFolderHashes(XivDataFile dataFile) { var folderHashList = new HashSet <int>(); // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; var indexPath = _gameDirectory + "\\" + dataFile.GetDataFileName() + IndexExtension; using (var br = new BinaryReader(File.OpenRead(indexPath))) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var totalFiles = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); for (var i = 0; i < totalFiles; br.ReadBytes(4), i += 16) { br.ReadBytes(4); var folderPathHash = br.ReadInt32(); folderHashList.Add(folderPathHash); br.ReadBytes(4); } } return(folderHashList.ToList()); }
/// <summary> /// Checks to see whether the index file is locked /// </summary> /// <param name="dataFile">The data file to check</param> /// <returns>True if locked</returns> public bool IsIndexLocked(XivDataFile dataFile) { var fileName = dataFile.GetDataFileName(); var isLocked = false; var indexPath = Path.Combine(_gameDirectory.FullName, $"{fileName}{IndexExtension}"); var index2Path = Path.Combine(_gameDirectory.FullName, $"{fileName}{Index2Extension}"); FileStream stream = null; FileStream stream1 = null; try { stream = File.Open(indexPath, FileMode.Open); stream1 = File.Open(index2Path, FileMode.Open); } catch (Exception e) { isLocked = true; } finally { stream?.Dispose(); stream?.Close(); stream1?.Dispose(); stream1?.Close(); } return(isLocked); }
/// <summary> /// Get all the file hash and file offset in a given folder /// </summary> /// <param name="hashedFolder">The hashed value of the folder path</param> /// <param name="dataFile">The data file to look in</param> /// <returns>A list containing the hashed values of the files in the given folder</returns> public Dictionary <int, int> GetAllHashedFilesAndOffsetsInFolder(int hashedFolder, XivDataFile dataFile) { var fileHashesDict = new Dictionary <int, int>(); // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; var indexPath = _gameDirectory + "\\" + dataFile.GetDataFileName() + IndexExtension; using (var br = new BinaryReader(File.OpenRead(indexPath))) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var totalFiles = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); for (var i = 0; i < totalFiles; br.ReadBytes(4), i += 16) { var hashedFile = br.ReadInt32(); var folderPathHash = br.ReadInt32(); if (folderPathHash == hashedFolder) { fileHashesDict.Add(hashedFile, br.ReadInt32() * 8); } else { br.ReadBytes(4); } } } return(fileHashesDict); }
/// <summary> /// Gets all the folder hashes in a given folder path /// </summary> /// <param name="dataFile">The data file to look in</param> /// <returns>A list of all of the folder hashes</returns> public async Task <List <int> > GetAllFolderHashes(XivDataFile dataFile) { var folderHashList = new HashSet <int>(); // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; var indexPath = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{IndexExtension}"); await Task.Run(() => { using (var br = new BinaryReader(File.OpenRead(indexPath))) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var totalFiles = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); for (var i = 0; i < totalFiles; br.ReadBytes(4), i += 16) { br.ReadBytes(4); var folderPathHash = br.ReadInt32(); folderHashList.Add(folderPathHash); br.ReadBytes(4); } } }); return(folderHashList.ToList()); }
/// <summary> /// Determines whether the given folder path exists /// </summary> /// <param name="folderHash">The hashed folder</param> /// <param name="dataFile">The data file</param> /// <returns>True if it exists, False otherwise</returns> public async Task <bool> FolderExists(int folderHash, XivDataFile dataFile) { var indexPath = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{IndexExtension}"); // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; return(await Task.Run(() => { using (var br = new BinaryReader(File.OpenRead(indexPath))) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var numOfFiles = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); for (var i = 0; i < numOfFiles; br.ReadBytes(4), i += 16) { var fileNameHash = br.ReadInt32(); var folderPathHash = br.ReadInt32(); if (folderPathHash == folderHash) { return true; } br.ReadBytes(4); } } return false; })); }
/// <summary> /// Gets the file dictionary for the data in the .dat file /// </summary> /// <param name="dataFile">The data file to look in</param> /// <returns>Dictionary containing (concatenated string of file+folder hashes, offset) </returns> public Task <Dictionary <string, int> > GetFileDictionary(XivDataFile dataFile) { return(Task.Run(() => { var fileDictionary = new Dictionary <string, int>(); var indexPath = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{IndexExtension}"); // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; using (var br = new BinaryReader(File.OpenRead(indexPath))) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var fileCount = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); // loop through each file entry for (var i = 0; i < fileCount; br.ReadBytes(4), i += 16) { var fileNameHash = br.ReadInt32(); var folderPathHash = br.ReadInt32(); var offset = br.ReadInt32() * 8; fileDictionary.Add($"{fileNameHash}{folderPathHash}", offset); } } return fileDictionary; })); }
/// <summary> /// Checks to see whether the index file is locked /// </summary> /// <param name="dataFile">The data file to check</param> /// <returns>True if locked</returns> public bool IsIndexLocked(XivDataFile dataFile) { var fileName = dataFile.GetDataFileName(); var indexPath = $"{_gameDirectory}\\{fileName}{IndexExtension}"; var index2Path = $"{_gameDirectory}\\{fileName}{Index2Extension}"; FileStream stream = null; FileStream stream1 = null; try { stream = File.Open(indexPath, FileMode.Open); stream1 = File.Open(index2Path, FileMode.Open); } catch (Exception e) { return(true); } finally { stream?.Close(); stream1?.Close(); } return(false); }
/// <summary> /// Gets the offset for the data in the .dat file /// </summary> /// <param name="hashedFolder">The hashed value of the folder path</param> /// <param name="hashedFile">The hashed value of the file name</param> /// <param name="dataFile">The data file to look in</param> /// <returns>The offset to the data</returns> public Task <int> GetDataOffset(int hashedFolder, int hashedFile, XivDataFile dataFile) { return(Task.Run(async() => { var indexPath = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{IndexExtension}"); var offset = 0; // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; await _semaphoreSlim.WaitAsync(); try { using (var br = new BinaryReader(File.OpenRead(indexPath))) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var fileCount = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); // loop through each file entry for (var i = 0; i < fileCount; br.ReadBytes(4), i += 16) { var fileNameHash = br.ReadInt32(); // check if the provided file name hash matches the current file name hash if (fileNameHash == hashedFile) { var folderPathHash = br.ReadInt32(); // check if the provided folder path hash matches the current folder path hash if (folderPathHash == hashedFolder) { // this is the entry we are looking for, get the offset and break out of the loop offset = br.ReadInt32() * 8; break; } br.ReadBytes(4); } else { br.ReadBytes(8); } } } } finally { _semaphoreSlim.Release(); } return offset; })); }
/// <summary> /// Retrieves all of the offsets for an arbitrary list of files within the same data file, via their Index2 entries. /// </summary> /// <param name="dataFile"></param> /// <returns></returns> private async Task <Dictionary <string, long> > GetDataOffsetsIndex2(XivDataFile dataFile, Dictionary <uint, string> fileHashes) { var ret = new Dictionary <string, long>(); return(await Task.Run(async() => { var index2Path = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{Index2Extension}"); var SegmentHeaders = new int[4]; var SegmentOffsets = new int[4]; var SegmentSizes = new int[4]; // Segment header offsets SegmentHeaders[0] = 1028; // Files SegmentHeaders[1] = 1028 + (72 * 1) + 4; // Unknown SegmentHeaders[2] = 1028 + (72 * 2) + 4; // Unknown SegmentHeaders[3] = 1028 + (72 * 3) + 4; // Folders await _semaphoreSlim.WaitAsync(); try { // Might as well grab the whole thing since we're doing a full scan. byte[] originalIndex = File.ReadAllBytes(index2Path); // Get all the segment header data for (int i = 0; i < SegmentHeaders.Length; i++) { SegmentOffsets[i] = BitConverter.ToInt32(originalIndex, SegmentHeaders[i] + 4); SegmentSizes[i] = BitConverter.ToInt32(originalIndex, SegmentHeaders[i] + 8); } int fileCount = SegmentSizes[0] / 8; for (int i = 0; i < fileCount; i++) { int position = SegmentOffsets[0] + (i * 8); uint iFullPathHash = BitConverter.ToUInt32(originalIndex, position); uint iOffset = BitConverter.ToUInt32(originalIndex, position + 4); // Index 2 is just in hash order, so find the spot where we fit in. if (fileHashes.ContainsKey(iFullPathHash)) { long offset = (long)iOffset; ret.Add(fileHashes[iFullPathHash], offset * 8); } } } finally { _semaphoreSlim.Release(); } return ret; })); }
/// <summary> /// Updates the .index files offset for a given item. /// </summary> /// <param name="offset">The new offset to be used.</param> /// <param name="fullPath">The internal path of the file whos offset is to be updated.</param> /// <param name="dataFile">The data file to update the index for</param> /// <returns>The offset which was replaced.</returns> public async Task <int> UpdateIndex(long offset, string fullPath, XivDataFile dataFile) { fullPath = fullPath.Replace("\\", "/"); var folderHash = HashGenerator.GetHash(fullPath.Substring(0, fullPath.LastIndexOf("/", StringComparison.Ordinal))); var fileHash = HashGenerator.GetHash(Path.GetFileName(fullPath)); var oldOffset = 0; // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; var indexPath = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{IndexExtension}"); await Task.Run(() => { using (var index = File.Open(indexPath, FileMode.Open)) { using (var br = new BinaryReader(index)) { using (var bw = new BinaryWriter(index)) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var numOfFiles = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); for (var i = 0; i < numOfFiles; br.ReadBytes(4), i += 16) { var fileNameHash = br.ReadInt32(); if (fileNameHash == fileHash) { var folderPathHash = br.ReadInt32(); if (folderPathHash == folderHash) { oldOffset = br.ReadInt32(); bw.BaseStream.Seek(br.BaseStream.Position - 4, SeekOrigin.Begin); bw.Write(offset / 8); break; } br.ReadBytes(4); } else { br.ReadBytes(8); } } } } } }); return(oldOffset); }
/// <summary> /// Gets the largest dat number for a given data file. /// </summary> /// <param name="dataFile">The data file to check.</param> /// <returns>The largest dat number for the given data file.</returns> private int GetLargestDatNumber(XivDataFile dataFile) { var allFiles = Directory.GetFiles(_gameDirectory.FullName); var dataFiles = from file in allFiles where file.Contains(dataFile.GetDataFileName()) && file.Contains(".dat") select file; var max = dataFiles.Select(file => int.Parse(file.Substring(file.Length - 1))).Concat(new[] { 0 }).Max(); return(max); }
/// <summary> /// Update the dat count within the index files. /// </summary> /// <param name="dataFile">The data file to update the index for.</param> /// <param name="datNum">The dat number to update to.</param> public void UpdateIndexDatCount(XivDataFile dataFile, int datNum) { var datCount = (byte)(datNum + 1); var indexPaths = new[] { _gameDirectory + "\\" + dataFile.GetDataFileName() + IndexExtension, _gameDirectory + "\\" + dataFile.GetDataFileName() + Index2Extension }; foreach (var indexPath in indexPaths) { using (var bw = new BinaryWriter(File.OpenWrite(indexPath))) { bw.BaseStream.Seek(1104, SeekOrigin.Begin); bw.Write(datCount); } } }
public Task <bool> CheckForOutdatedBackups(XivDataFile dataFile, DirectoryInfo backupsDirectory) { return(Task.Run(() => { var backupDataFile = new DirectoryInfo(Path.Combine(backupsDirectory.FullName, $"{dataFile.GetDataFileName()}.win32.index")); var currentDataFile = new DirectoryInfo(Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}.win32.index")); // Since the material addition directly adds to section 1 we can no longer check for outdated using that section header // so instead compare the hahes of sections 2 and 3 var backupHashSection2 = _index.GetIndexSection2Hash(backupDataFile); var currentHashSection2 = _index.GetIndexSection2Hash(currentDataFile); var backupHashSection3 = _index.GetIndexSection3Hash(backupDataFile); var currentHashSection3 = _index.GetIndexSection3Hash(currentDataFile); return backupHashSection2.SequenceEqual(currentHashSection2) && backupHashSection3.SequenceEqual(currentHashSection3); })); }
public Task <bool> CheckForOutdatedBackups(XivDataFile dataFile, DirectoryInfo backupsDirectory) { return(Task.Run(async() => { var haveFilesAddedByTexTools = await _index.HaveFilesAddedByTexTools(dataFile); if (haveFilesAddedByTexTools) { return true; } var backupDataFile = new DirectoryInfo(Path.Combine(backupsDirectory.FullName, $"{dataFile.GetDataFileName()}.win32.index")); var currentDataFile = new DirectoryInfo(Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}.win32.index")); var backupHash = _index.GetIndexSection1Hash(backupDataFile); var currentHash = _index.GetIndexSection1Hash(currentDataFile); return backupHash.SequenceEqual(currentHash); })); }
/// <summary> /// This function returns TRUE if the backups should be used, and FALSE if they should not. /// </summary> /// <param name="dataFile"></param> /// <param name="backupsDirectory"></param> /// <returns></returns> public Task <bool> CheckForOutdatedBackups(XivDataFile dataFile, DirectoryInfo backupsDirectory) { return(Task.Run(() => { var backupDataFile = new DirectoryInfo(Path.Combine(backupsDirectory.FullName, $"{dataFile.GetDataFileName()}.win32.index")); var currentDataFile = new DirectoryInfo(Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}.win32.index")); // Since the material addition directly adds to section 1 we can no longer check for outdated using that section header // so instead compare the hashes of sections 2 and 3 byte[] currentHashSection2; byte[] currentHashSection3; byte[] backupHashSection2; byte[] backupHashSection3; try { currentHashSection2 = _index.GetIndexSection2Hash(currentDataFile); currentHashSection3 = _index.GetIndexSection3Hash(currentDataFile); } catch { // Base files are f****d, use *any* backups. return true; } try { backupHashSection2 = _index.GetIndexSection2Hash(backupDataFile); backupHashSection3 = _index.GetIndexSection3Hash(backupDataFile); } catch { // Backups are f****d, can't use those. return false; } return backupHashSection2.SequenceEqual(currentHashSection2) && backupHashSection3.SequenceEqual(currentHashSection3); })); }
/// <summary> /// Gets the data for type 2 files. /// </summary> /// <remarks> /// Type 2 files vary in content. /// </remarks> /// <param name="offset">The offset where the data is located.</param> /// <param name="dataFile">The data file that contains the data.</param> /// <returns>Byte array containing the decompressed type 2 data.</returns> public byte[] GetType2Data(int offset, XivDataFile dataFile) { var type2Bytes = new List <byte>(); // This formula is used to obtain the dat number in which the offset is located var datNum = ((offset / 8) & 0x0F) / 2; var datPath = _gameDirectory + "\\" + dataFile.GetDataFileName() + DatExtension + datNum; offset = OffsetCorrection(datNum, offset); using (var br = new BinaryReader(File.OpenRead(datPath))) { br.BaseStream.Seek(offset, SeekOrigin.Begin); var headerLength = br.ReadInt32(); br.ReadBytes(16); var dataBlockCount = br.ReadInt32(); for (var i = 0; i < dataBlockCount; i++) { br.BaseStream.Seek(offset + (24 + (8 * i)), SeekOrigin.Begin); var dataBlockOffset = br.ReadInt32(); br.BaseStream.Seek(offset + headerLength + dataBlockOffset, SeekOrigin.Begin); br.ReadBytes(8); var compressedSize = br.ReadInt32(); var uncompressedSize = br.ReadInt32(); // When the compressed size of a data block shows 32000, it is uncompressed. if (compressedSize == 32000) { type2Bytes.AddRange(br.ReadBytes(uncompressedSize)); } else { var compressedData = br.ReadBytes(compressedSize); var decompressedData = IOUtil.Decompressor(compressedData, uncompressedSize); type2Bytes.AddRange(decompressedData); } } } return(type2Bytes.ToArray()); }
/// <summary> /// Checks whether the index file contains any of the folders passed in /// </summary> /// <remarks> /// Runs through the index file once checking if the hashed folder value exists in the dictionary /// then adds it to the list if it does. /// </remarks> /// <param name="hashNumDictionary">A Dictionary containing the folder hash and item number</param> /// <param name="dataFile">The data file to look in</param> /// <returns></returns> public Task <List <int> > GetFolderExistsList(Dictionary <int, int> hashNumDictionary, XivDataFile dataFile) { return(Task.Run(async() => { await _semaphoreSlim.WaitAsync(); // HashSet because we don't want any duplicates var folderExistsList = new HashSet <int>(); try { // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; var indexPath = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{IndexExtension}"); using (var br = new BinaryReader(File.OpenRead(indexPath))) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var totalFiles = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); for (var i = 0; i < totalFiles; br.ReadBytes(4), i += 16) { br.ReadBytes(4); var folderPathHash = br.ReadInt32(); if (hashNumDictionary.ContainsKey(folderPathHash)) { folderExistsList.Add(hashNumDictionary[folderPathHash]); br.ReadBytes(4); } else { br.ReadBytes(4); } } } } finally { _semaphoreSlim.Release(); } return folderExistsList.ToList(); })); }
/// <summary> /// Retrieves all of the offsets for an arbitrary list of files within the same data file. /// </summary> /// <param name="dataFile"></param> /// <returns></returns> private async Task <Dictionary <string, long> > GetDataOffsets(XivDataFile dataFile, Dictionary <int, Dictionary <int, string> > FolderFiles) { var ret = new Dictionary <string, long>(); return(await Task.Run(() => { var indexPath = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{IndexExtension}"); // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; int count = 0; _semaphoreSlim.Wait(); try { using (var br = new BinaryReader(File.OpenRead(indexPath))) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var fileCount = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); // loop through each file entry for (var i = 0; i < fileCount; i += 16) { var fileNameHash = br.ReadInt32(); var folderPathHash = br.ReadInt32(); long offset = br.ReadUInt32(); var unused = br.ReadInt32(); if (FolderFiles.ContainsKey(folderPathHash)) { if (FolderFiles[folderPathHash].ContainsKey(fileNameHash)) { count++; ret.Add(FolderFiles[folderPathHash][fileNameHash], offset * 8); } } } } } finally { _semaphoreSlim.Release(); } return ret; })); }
/// <summary> /// Creates a backup of the index file. /// </summary> /// <param name="backupsDirectory">The directory in which to place the backup files. /// The directory will be created if it does not exist.</param> /// <param name="dataFile">The file to backup.</param> public void CreateIndexBackups(DirectoryInfo backupsDirectory, XivDataFile dataFile) { var fileName = dataFile.GetDataFileName(); var indexPath = _gameDirectory + "\\" + fileName + IndexExtension; var index2Path = _gameDirectory + "\\" + fileName + Index2Extension; var indexBackupPath = backupsDirectory.FullName + "\\" + fileName + IndexExtension; var index2BackupPath = backupsDirectory.FullName + "\\" + fileName + Index2Extension; Directory.CreateDirectory(backupsDirectory.FullName); File.Copy(indexPath, indexBackupPath, true); File.Copy(index2Path, index2BackupPath, true); }
/// <summary> /// Creates a backup of the index file. /// </summary> /// <param name="backupsDirectory">The directory in which to place the backup files. /// The directory will be created if it does not exist.</param> /// <param name="dataFile">The file to backup.</param> public void CreateIndexBackups(DirectoryInfo backupsDirectory, XivDataFile dataFile) { var fileName = dataFile.GetDataFileName(); var indexPath = Path.Combine(_gameDirectory.FullName, $"{fileName}{IndexExtension}"); var index2Path = Path.Combine(_gameDirectory.FullName, $"{fileName}{Index2Extension}"); var indexBackupPath = Path.Combine(backupsDirectory.FullName, $"{fileName}{IndexExtension}"); var index2BackupPath = Path.Combine(backupsDirectory.FullName, $"{fileName}{Index2Extension}"); Directory.CreateDirectory(backupsDirectory.FullName); File.Copy(indexPath, indexBackupPath, true); File.Copy(index2Path, index2BackupPath, true); }
/// <summary> /// Gets the offset for the data in the .dat file /// </summary> /// <param name="hashedFolder">The hashed value of the folder path</param> /// <param name="hashedFile">The hashed value of the file name</param> /// <param name="dataFile">The data file to look in</param> /// <returns>The offset to the data</returns> public int GetDataOffset(int hashedFolder, int hashedFile, XivDataFile dataFile) { var indexPath = _gameDirectory + "\\" + dataFile.GetDataFileName() + IndexExtension; var offset = 0; // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; using (var br = new BinaryReader(File.OpenRead(indexPath))) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var fileCount = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); // loop through each file entry for (var i = 0; i < fileCount; br.ReadBytes(4), i += 16) { var fileNameHash = br.ReadInt32(); // check if the provided file name hash matches the current file name hash if (fileNameHash == hashedFile) { var folderPathHash = br.ReadInt32(); // check if the provided folder path hash matches the current folder path hash if (folderPathHash == hashedFolder) { // this is the entry we are looking for, get the offset and break out of the loop offset = br.ReadInt32() * 8; break; } br.ReadBytes(4); } else { br.ReadBytes(8); } } } return(offset); }
private bool IsOriginalDat(XivDataFile dataFile) { // Checks if any entry in the modlist is within the datafile // If there is, then a modded dat has already been created using (var streamReader = new StreamReader(_modListDirectory.FullName)) { string line; while ((line = streamReader.ReadLine()) != null) { var modInfo = JsonConvert.DeserializeObject <ModInfo>(line); if (modInfo.datFile.Contains(dataFile.GetDataFileName())) { return(false); } } } return(true); }
/// <summary> /// Updates the .index2 files offset for a given item. /// </summary> /// <param name="offset">The new offset to be used.</param> /// <param name="fullPath">The internal path of the file whos offset is to be updated.</param> /// <param name="dataFile">The data file to update the index for</param> /// <returns>The offset which was replaced.</returns> public async Task UpdateIndex2(long offset, string fullPath, XivDataFile dataFile) { fullPath = fullPath.Replace("\\", "/"); var pathHash = HashGenerator.GetHash(fullPath); // These are the offsets to relevant data const int fileCountOffset = 1036; const int dataStartOffset = 2048; var index2Path = Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}{Index2Extension}"); await Task.Run(() => { using (var index = File.Open(index2Path, FileMode.Open)) { using (var br = new BinaryReader(index)) { using (var bw = new BinaryWriter(index)) { br.BaseStream.Seek(fileCountOffset, SeekOrigin.Begin); var numOfFiles = br.ReadInt32(); br.BaseStream.Seek(dataStartOffset, SeekOrigin.Begin); for (var i = 0; i < numOfFiles; i += 8) { var fullPathHash = br.ReadInt32(); if (fullPathHash == pathHash) { bw.BaseStream.Seek(br.BaseStream.Position, SeekOrigin.Begin); bw.Write((int)(offset / 8)); break; } br.ReadBytes(4); } } } } }); }
/// <summary> /// Creates a new dat file to store moddified data. /// </summary> /// <remarks> /// This will first find what the largest dat number is for a given data file /// It will then create a new dat file that is one number larger /// Lastly it will update the index files to reflect the new dat count /// </remarks> /// <param name="dataFile">The data file to create a new dat for.</param> /// <returns>The new dat number.</returns> public int CreateNewDat(XivDataFile dataFile) { var nextDatNumber = GetLargestDatNumber(dataFile) + 1; var datPath = _gameDirectory.FullName + "\\" + dataFile.GetDataFileName() + DatExtension + nextDatNumber; using (var fs = File.Create(datPath)) { using (var bw = new BinaryWriter(fs)) { bw.Write(MakeSqPackHeader()); bw.Write(MakeDatHeader()); } } var index = new Index(_gameDirectory); index.UpdateIndexDatCount(dataFile, nextDatNumber); return(nextDatNumber); }