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