From ef2f0379091749bd5059923695548abbd3562ebf Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Sun, 25 Dec 2022 22:53:01 -0800 Subject: [PATCH] Add GCF extraction --- BurnOutSharp.Builders/GCF.cs | 12 +- BurnOutSharp.Wrappers/GCF.cs | 217 ++++++++++++++++++++++++++++++++++- 2 files changed, 218 insertions(+), 11 deletions(-) diff --git a/BurnOutSharp.Builders/GCF.cs b/BurnOutSharp.Builders/GCF.cs index af2d170c..4ad58ff0 100644 --- a/BurnOutSharp.Builders/GCF.cs +++ b/BurnOutSharp.Builders/GCF.cs @@ -13,32 +13,32 @@ namespace BurnOutSharp.Builders /// /// The item is a file. /// - private const int HL_GCF_FLAG_FILE = 0x00004000; + public const int HL_GCF_FLAG_FILE = 0x00004000; /// /// The item is encrypted. /// - private const int HL_GCF_FLAG_ENCRYPTED = 0x00000100; + public const int HL_GCF_FLAG_ENCRYPTED = 0x00000100; /// /// Backup the item before overwriting it. /// - private const int HL_GCF_FLAG_BACKUP_LOCAL = 0x00000040; + public const int HL_GCF_FLAG_BACKUP_LOCAL = 0x00000040; /// /// The item is to be copied to the disk. /// - private const int HL_GCF_FLAG_COPY_LOCAL = 0x0000000A; + public const int HL_GCF_FLAG_COPY_LOCAL = 0x0000000A; /// /// Don't overwrite the item if copying it to the disk and the item already exists. /// - private const int HL_GCF_FLAG_COPY_LOCAL_NO_OVERWRITE = 0x00000001; + public const int HL_GCF_FLAG_COPY_LOCAL_NO_OVERWRITE = 0x00000001; /// /// The maximum data allowed in a 32 bit checksum. /// - private const int HL_GCF_CHECKSUM_LENGTH = 0x00008000; + public const int HL_GCF_CHECKSUM_LENGTH = 0x00008000; #endregion diff --git a/BurnOutSharp.Wrappers/GCF.cs b/BurnOutSharp.Wrappers/GCF.cs index 115803f5..af8f5196 100644 --- a/BurnOutSharp.Wrappers/GCF.cs +++ b/BurnOutSharp.Wrappers/GCF.cs @@ -1,7 +1,6 @@ using System; +using System.Collections.Generic; using System.IO; -using System.Linq; -using BurnOutSharp.Utilities; namespace BurnOutSharp.Wrappers { @@ -301,7 +300,96 @@ namespace BurnOutSharp.Wrappers #region Extension Properties - // TODO: Figure out what extension properties are needed + /// + /// Set of all files and their information + /// + public FileInfo[] Files + { + get + { + // Use the cached value if we have it + if (_files != null) + return _files; + + // Otherwise, scan and build the files + var files = new List(); + for (int i = 0; i < DirectoryEntries.Length; i++) + { + // Get the directory entry + var directoryEntry = DirectoryEntries[i]; + var directoryMapEntry = DirectoryMapEntries[i]; + + // If we have a directory, skip for now + if ((directoryEntry.DirectoryFlags & Builders.GCF.HL_GCF_FLAG_FILE) == 0) + continue; + + // Otherwise, start building the file info + var fileInfo = new FileInfo() + { + Size = directoryEntry.ItemSize, + Encrypted = (directoryEntry.DirectoryFlags & Builders.GCF.HL_GCF_FLAG_ENCRYPTED) != 0, + }; + var pathParts = new List { directoryEntry.Name }; + var blockEntries = new List(); + + // Traverse the parent tree + uint index = directoryEntry.ParentIndex; + while (index != 0xFFFFFFFF) + { + var parentDirectoryEntry = DirectoryEntries[index]; + pathParts.Add(parentDirectoryEntry.Name); + index = parentDirectoryEntry.ParentIndex; + } + + // Traverse the block entries + index = directoryMapEntry.FirstBlockIndex; + while (index != DBH_BlockCount) + { + var nextBlock = BlockEntries[index]; + blockEntries.Add(nextBlock); + index = nextBlock.NextBlockEntryIndex; + } + + // Reverse the path parts because of traversal + pathParts.Reverse(); + + // Build the remaining file info + fileInfo.Path = Path.Combine(pathParts.ToArray()); + fileInfo.BlockEntries = blockEntries.ToArray(); + + // Add the file info and continue + files.Add(fileInfo); + } + + // Set and return the file infos + _files = files.ToArray(); + return _files; + } + } + + /// + /// Set of all data block offsets + /// + public long[] DataBlockOffsets + { + get + { + // Use the cached value if we have it + if (_dataBlockOffsets != null) + return _dataBlockOffsets; + + // Otherwise, build the data block set + _dataBlockOffsets = new long[DBH_BlockCount]; + for (int i = 0; i < DBH_BlockCount; i++) + { + long dataBlockOffset = DBH_FirstBlockOffset + (i * DBH_BlockSize); + _dataBlockOffsets[i] = dataBlockOffset; + } + + // Return the set of data blocks + return _dataBlockOffsets; + } + } #endregion @@ -312,6 +400,16 @@ namespace BurnOutSharp.Wrappers /// private Models.GCF.File _file; + /// + /// Set of all files and their information + /// + private FileInfo[] _files = null; + + /// + /// Set of all data block offsets + /// + private long[] _dataBlockOffsets = null; + #endregion #region Constructors @@ -852,7 +950,18 @@ namespace BurnOutSharp.Wrappers /// True if all files extracted, false otherwise public bool ExtractAll(string outputDirectory) { - return false; + // If we have no files + if (Files == null || Files.Length == 0) + return false; + + // Loop through and extract all files to the output + bool allExtracted = true; + for (int i = 0; i < Files.Length; i++) + { + allExtracted &= ExtractFile(i, outputDirectory); + } + + return allExtracted; } /// @@ -863,7 +972,105 @@ namespace BurnOutSharp.Wrappers /// True if the file extracted, false otherwise public bool ExtractFile(int index, string outputDirectory) { - return false; + // If we have no files + if (Files == null || Files.Length == 0) + return false; + + // If the files index is invalid + if (index < 0 || index >= Files.Length) + return false; + + // Get the file + var file = Files[index]; + if (file.Size == 0) + return false; + + // If the file is encrypted -- TODO: Revisit later + if (file.Encrypted) + return false; + + // Get all data block offsets needed for extraction + var dataBlockOffsets = new List(); + for (int i = 0; i < file.BlockEntries.Length; i++) + { + var blockEntry = file.BlockEntries[i]; + + uint dataBlockIndex = blockEntry.FirstDataBlockIndex; + long blockEntrySize = blockEntry.FileDataSize; + while (blockEntrySize > 0) + { + long dataBlockOffset = DataBlockOffsets[dataBlockIndex++]; + dataBlockOffsets.Add(dataBlockOffset); + blockEntrySize -= DBH_BlockSize; + } + } + + // Create the filename + string filename = file.Path; + + // If we have an invalid output directory + if (string.IsNullOrWhiteSpace(outputDirectory)) + return false; + + // Create the full output path + filename = Path.Combine(outputDirectory, filename); + + // Ensure the output directory is created + Directory.CreateDirectory(Path.GetDirectoryName(filename)); + + // Try to write the data + try + { + // Open the output file for writing + using (Stream fs = File.OpenWrite(filename)) + { + // Now read the data sequentially and write out while we have data left + long fileSize = file.Size; + for (int i = 0; i < dataBlockOffsets.Count; i++) + { + int readSize = (int)Math.Min(DBH_BlockSize, fileSize); + + byte[] data = ReadFromDataSource((int)dataBlockOffsets[i], readSize); + fs.Write(data, 0, data.Length); + } + } + } + catch + { + return false; + } + + return true; + } + + #endregion + + #region Helper Classes + + /// + /// Class to contain all necessary file information + /// + public sealed class FileInfo + { + /// + /// Full item path + /// + public string Path; + + /// + /// File size + /// + public uint Size; + + /// + /// Indicates if the block is encrypted + /// + public bool Encrypted; + + /// + /// Array of block entries + /// + public Models.GCF.BlockEntry[] BlockEntries; } #endregion