/// <summary> /// Standard constructor. /// If disposeStreams is set to true, the binary streams will be disposed after use. /// </summary> public IndexFile(XivDataFile dataFile, BinaryReader index1Stream, BinaryReader index2Stream, bool disposeStreams = false) { DataFile = dataFile; ReadIndex1File(index1Stream); if (index2Stream != null) { ReadIndex2File(index2Stream); ReadOnlyMode = false; } else { ReadOnlyMode = true; } if (disposeStreams) { index1Stream.Dispose(); if (index2Stream != null) { index2Stream.Dispose(); } } }
/// <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> /// 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; })); }
public Tex(DirectoryInfo gameDirectory, XivDataFile dataFile) { _gameDirectory = gameDirectory; _index = new Index(_gameDirectory); _dat = new Dat(_gameDirectory); _dataFile = dataFile; }
/// <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> /// Checks the dat counts in the index file /// </summary> /// <returns>Flag for problem found</returns> private bool CheckIndexDatCounts() { var problemFound = false; var filesToCheck = new XivDataFile[] { XivDataFile._0A_Exd, XivDataFile._01_Bgcommon, XivDataFile._04_Chara, XivDataFile._06_Ui }; foreach (var file in filesToCheck) { AddText($"\t{file.GetDataFileName()} Index Files", textColor); var result = _problemChecker.CheckIndexDatCounts(file); if (result) { _indexDatRepairList.Add(file); AddText("\t\u2716\n", "Red"); problemFound = true; } else { AddText("\t\u2714\n", "Green"); } } return(problemFound); }
/// <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> /// 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> /// Get all the hashed values of the files 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 List <int> GetAllHashedFilesInFolder(int hashedFolder, XivDataFile dataFile) { var fileHashesList = new List <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) { fileHashesList.Add(hashedFile); } br.ReadBytes(4); } } return(fileHashesList); }
/// <summary> /// Deletes a file descriptor/stub from the Index files. /// </summary> /// <param name="fullPath">Full internal file path to the file that should be deleted.</param> /// <param name="dataFile">Which data file to use</param> /// <returns></returns> public async Task <bool> DeleteFileDescriptor(string fullPath, XivDataFile dataFile, bool updateCache = true) { await UpdateDataOffset(0, fullPath, false, true); // This is a metadata entry being deleted, we'll need to restore the metadata entries back to default. if (fullPath.EndsWith(".meta")) { var root = await XivCache.GetFirstRoot(fullPath); await ItemMetadata.RestoreDefaultMetadata(root); } if (fullPath.EndsWith(".rgsp")) { await CMP.RestoreDefaultScaling(fullPath); } if (updateCache) { // Queue us for updating, *after* updating the associated metadata files. XivCache.QueueDependencyUpdate(fullPath); } return(true); }
/// <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> /// 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> /// Checks the index for the number of dats the game will attempt to read /// </summary> /// <returns>True if there is a problem, False otherwise</returns> public Task <bool> CheckForLargeDats(XivDataFile dataFile) { return(Task.Run(() => { var largestDatNum = _dat.GetLargestDatNumber(dataFile) + 1; var fileSizeList = new List <long>(); for (var i = 0; i < largestDatNum; i++) { var fileInfo = new FileInfo(Path.Combine(_gameDirectory.FullName, $"{dataFile.GetDataFileName()}.win32.dat{i}")); try { fileSizeList.Add(fileInfo.Length); } catch { return true; } } if (largestDatNum > 8 || fileSizeList.FindAll(x => x.Equals(2048)).Count > 1) { return true; } return false; })); }
/// <summary> /// Gets the description from the enum value, in this case the File Name /// </summary> /// <param name="value">The enum value</param> /// <returns>The File Name</returns> public static string GetDataFileName(this XivDataFile value) { var field = value.GetType().GetField(value.ToString()); var attribute = (XivDataFileDescriptionAttribute[])field.GetCustomAttributes(typeof(XivDataFileDescriptionAttribute), false); return(attribute.Length > 0 ? attribute[0].CatNumber : value.ToString()); }
public bool ValidateIndexFiles() { bool keepGoing = true; if (MainClass._gameDirectory != null) { string modlistPath = Path.Combine(MainClass._gameDirectory.FullName, "XivMods.json"); if (!File.Exists(modlistPath)) { ProblemChecker problemChecker = new ProblemChecker(MainClass._indexDirectory); var filesToCheck = new XivDataFile[] { XivDataFile._0A_Exd, XivDataFile._01_Bgcommon, XivDataFile._04_Chara, XivDataFile._06_Ui }; bool modifiedIndex = false; foreach (var file in filesToCheck) { var datCountCheck = problemChecker.CheckIndexDatCounts(file); datCountCheck.Wait(); if (datCountCheck.Result) { modifiedIndex = true; break; } } if (modifiedIndex) { main.PrintMessage("HERE BE DRAGONS\nPreviously modified game files found\nUse the originally used tool to start over, or reinstall the game before using this tool", 2); keepGoing = PromptContinuation(); } } else { var modData = JsonConvert.DeserializeObject <ModList>(File.ReadAllText(modlistPath)); bool unsupportedSource = false; string unknownSource = ""; //List of acceptable mod sources //FFXIV_Modding_Tool is used by this tool //FilesAddedByTexTools is hardcoded in the framework and is used in certain situations //BLANK seems to be caused by a framework bug as well, so we allow it List <string> acceptedSourcesList = new List <string> { "FFXIV_Modding_Tool", "FilesAddedByTexTools", "_INTERNAL_", "" }; foreach (Mod mod in modData.Mods) { if (!acceptedSourcesList.Contains(mod.source)) { unknownSource = mod.source; unsupportedSource = true; break; } } if (unsupportedSource) { main.PrintMessage($"Found a mod applied by an unknown application, game stability cannot be guaranteed: {unknownSource}", 3); keepGoing = PromptContinuation(); } } } return(keepGoing); }
/// <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> /// Repairs the dat count in the index files /// </summary> public Task RepairIndexDatCounts(XivDataFile dataFile) { return(Task.Run(() => { var largestDatNum = _dat.GetLargestDatNumber(dataFile); _index.UpdateIndexDatCount(dataFile, largestDatNum); })); }
/// <summary> /// Repairs the dat count in the index files /// </summary> public void RepairIndexDatCounts(XivDataFile dataFile) { var index = new Index(_gameDirectory); var dat = new Dat(_gameDirectory); var largestDatNum = dat.GetLargestDatNumber(dataFile); index.UpdateIndexDatCount(dataFile, largestDatNum); }
/// <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; })); }
public bool ValidateBackups() { main.PrintMessage("Checking backups before proceeding..."); bool keepGoing = true; bool problemFound = false; if (MainClass._backupDirectory == null) { main.PrintMessage($"No backup directory specified, can't check the status of backups.\nYou are strongly recommended to add a backup directory in {Path.Combine(MainClass._projectconfDirectory.FullName, "config.cfg")} and running the 'backup' command before proceeding", 2); problemFound = true; } else if (MainClass._gameDirectory == null) { main.PrintMessage("No game directory specified, can't check if backups are up to date", 2); problemFound = true; } else { var filesToCheck = new XivDataFile[] { XivDataFile._01_Bgcommon, XivDataFile._04_Chara, XivDataFile._06_Ui }; ProblemChecker problemChecker = new ProblemChecker(MainClass._indexDirectory); foreach (var file in filesToCheck) { if (!File.Exists(Path.Combine(MainClass._backupDirectory.FullName, $"{file.GetDataFileName()}.win32.index"))) { main.PrintMessage($"One or more index files could not be found in {MainClass._backupDirectory.FullName}. Creating new ones or downloading them from the TexTools discord is recommended", 2); problemFound = true; break; } var outdatedBackupsCheck = problemChecker.CheckForOutdatedBackups(file, MainClass._backupDirectory); outdatedBackupsCheck.Wait(); if (!outdatedBackupsCheck.Result) { main.PrintMessage($"One or more index files are out of date in {MainClass._backupDirectory.FullName}. Recreating or downloading them from the TexTools discord is recommended", 2); problemFound = true; break; } } } if (problemFound) { if (PromptContinuation("Would you like to back up now?", true)) { main.BackupIndexes(); keepGoing = true; } else { keepGoing = PromptContinuation(); } } else { main.PrintMessage("All backups present and up to date", 1); } return(keepGoing); }
/// <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 all the file offsets in a given folder path /// </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 of all of the offsets in the given folder</returns> public async Task <List <long> > GetAllFileOffsetsInFolder(int hashedFolder, XivDataFile dataFile) { var index = await GetIndexFile(dataFile, false, true); var entries = index.GetEntriesInFolder((uint)hashedFolder); var hashes = entries.Select(x => ((long)x.RawOffset) * 8L); return(hashes.ToList()); }
/// <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> /// Get all the hashed values of the files 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 async Task <List <int> > GetAllHashedFilesInFolder(int hashedFolder, XivDataFile dataFile) { var index = await GetIndexFile(dataFile, false, true); var entries = index.GetEntriesInFolder((uint)hashedFolder); var hashes = entries.Select(x => (int)x.FileNameHash); return(hashes.ToList()); }
/// <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(); } } }
/// <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()); }
public Task <bool> RestoreBackups(DirectoryInfo backupsDirectory) { return(Task.Run(async() => { var backupFiles = Directory.GetFiles(backupsDirectory.FullName); var filesToCheck = new XivDataFile[] { XivDataFile._0A_Exd, XivDataFile._04_Chara, XivDataFile._06_Ui, XivDataFile._01_Bgcommon }; var outdated = false; foreach (var xivDataFile in filesToCheck) { var backupFile = new DirectoryInfo($"{backupsDirectory.FullName}\\{xivDataFile.GetDataFileName()}.win32.index"); if (!File.Exists(backupFile.FullName)) { continue; } try { var outdatedCheck = await CheckForOutdatedBackups(xivDataFile, backupsDirectory); if (!outdatedCheck) { outdated = true; } } catch { // If the outdated check errored out, we likely have completely broken internal dat files. // ( Either deleted or 0 byte files ), so replacing them with *anything* is an improvement. } } var _index = new Index(_gameDirectory); // Make sure backups exist and are up to date unless called with forceRestore true if (backupFiles.Length != 0 && !outdated) { // Copy backups to ffxiv folder foreach (var backupFile in backupFiles) { if (backupFile.Contains(".win32.index")) { File.Copy(backupFile, $"{_gameDirectory}/{Path.GetFileName(backupFile)}", true); } } // Update all the index counts to be safe, in case the user's index backups were generated when some mod dats existed. _index.UpdateAllIndexDatCounts(); return true; } return false; })); }
/// <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; })); }