Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions SabreTools.Serialization/Readers/MicrosoftCabinet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,13 @@ private static CFDATA ParseDataBlock(Stream data, byte dataReservedSize)
dataBlock.Checksum = data.ReadUInt32LittleEndian();
dataBlock.CompressedSize = data.ReadUInt16LittleEndian();
dataBlock.UncompressedSize = data.ReadUInt16LittleEndian();

if (dataReservedSize > 0)
dataBlock.ReservedData = data.ReadBytes(dataReservedSize);
data.SeekIfPossible(dataReservedSize, SeekOrigin.Current);

if (dataBlock.CompressedSize > 0)
dataBlock.CompressedData = data.ReadBytes(dataBlock.CompressedSize);

data.SeekIfPossible(dataBlock.CompressedSize, SeekOrigin.Current);
return dataBlock;
}

Expand Down
79 changes: 76 additions & 3 deletions SabreTools.Serialization/Wrappers/MicrosoftCabinet.Extraction.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.Data.Models.MicrosoftCabinet;
using SabreTools.IO.Extensions;
Expand Down Expand Up @@ -174,12 +175,22 @@ public bool Extract(string outputDirectory, bool includeDebug)
// Loop through the current folders
for (int f = 0; f < cabinet.Folders.Length; f++)
{
if (f == 0 && (cabinet.Files[0].FolderIndex == FolderIndex.CONTINUED_PREV_AND_NEXT
|| cabinet.Files[0].FolderIndex == FolderIndex.CONTINUED_FROM_PREV))
continue;

var folder = cabinet.Folders[f];
allExtracted &= cabinet.ExtractFolder(Filename, outputDirectory, folder, f, ignorePrev, includeDebug);
}

// Move to the next cabinet, if possible
Array.ForEach(cabinet.Folders, folder => folder.DataBlocks = []);

cabinet = cabinet.Next;
cabinet?.Prev = null;

// TODO: already-extracted data isn't being cleared from memory, at least not nearly enough.

if (cabinet?.Folders == null || cabinet.Folders.Length == 0)
break;
}
Expand All @@ -192,7 +203,7 @@ public bool Extract(string outputDirectory, bool includeDebug)
return false;
}
}

/// <summary>
/// Extract the contents of a single folder
/// </summary>
Expand All @@ -217,15 +228,77 @@ private bool ExtractFolder(string? filename,

// Loop through the files
bool allExtracted = true;
var files = GetFiles(folderIndex, ignorePrev);
var filterFiles = GetSpannedFiles(filename, folderIndex, ignorePrev);
List<CFFILE> fileList = [];

// Filtering, add debug output eventually
for (int i = 0; i < filterFiles.Length; i++)
{
var file = filterFiles[i];

if (file.FolderIndex == FolderIndex.CONTINUED_PREV_AND_NEXT ||
file.FolderIndex == FolderIndex.CONTINUED_FROM_PREV)
{
// debug output for inconsistencies would go here
continue;
}

fileList.Add(file);
}

CFFILE[] files = fileList.ToArray();
blockStream.SeekIfPossible(0, SeekOrigin.Begin);
for (int i = 0; i < files.Length; i++)
{
var file = files[i];
allExtracted &= ExtractFile(outputDirectory, blockStream, file, includeDebug);

allExtracted &= ExtractFiles(outputDirectory, blockStream, file, includeDebug);
}

return allExtracted;
}

// TODO: this will apparently improve memory usage/performance, but it's not clear if this implementation is enough for that to happen
/// <summary>
/// Extract the contents of a single file, intended to be used with all files in a straight shot
/// </summary>
/// <param name="outputDirectory">Path to the output directory</param>
/// <param name="blockStream">Stream representing the uncompressed block data</param>
/// <param name="file">File information</param>
/// <param name="includeDebug">True to include debug data, false otherwise</param>
/// <returns>True if the file extracted, false otherwise</returns>
private static bool ExtractFiles(string outputDirectory, Stream blockStream, CFFILE file, bool includeDebug)
{
try
{
byte[] fileData = blockStream.ReadBytes((int)file.FileSize);

// Ensure directory separators are consistent
string filename = file.Name;
if (Path.DirectorySeparatorChar == '\\')
filename = filename.Replace('/', '\\');
else if (Path.DirectorySeparatorChar == '/')
filename = filename.Replace('\\', '/');

// Ensure the full output directory exists
filename = Path.Combine(outputDirectory, filename);
var directoryName = Path.GetDirectoryName(filename);
if (directoryName != null && !Directory.Exists(directoryName))
Directory.CreateDirectory(directoryName);

// Open the output file for writing
using var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.None);
fs.Write(fileData, 0, fileData.Length);
fs.Flush();
}
catch (Exception ex)
{
if (includeDebug) Console.Error.WriteLine(ex);
return false;
}

return true;
}

/// <summary>
/// Extract the contents of a single file
Expand Down
105 changes: 104 additions & 1 deletion SabreTools.Serialization/Wrappers/MicrosoftCabinet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using SabreTools.Data.Models.MicrosoftCabinet;
using SabreTools.IO.Compression.MSZIP;
using SabreTools.IO.Extensions;

namespace SabreTools.Serialization.Wrappers
{
Expand Down Expand Up @@ -315,6 +316,8 @@ private static CompressionType GetCompressionType(CFFOLDER folder)
if (folder?.DataBlocks == null || folder.DataBlocks.Length == 0)
return null;

GetData(folder);

// Get all files for the folder
var files = GetFiles(folderIndex);
if (files.Length == 0)
Expand All @@ -335,7 +338,7 @@ private static CompressionType GetCompressionType(CFFOLDER folder)
// Get all blocks from Prev
if (Prev?.Header != null && Prev.Folders != null)
{
int prevFolderIndex = Prev.FolderCount;
int prevFolderIndex = Prev.FolderCount - 1;
var prevFolder = Prev.Folders[prevFolderIndex - 1];
prevBlocks = Prev.GetDataBlocks(filename, prevFolder, prevFolderIndex, skipNext: true) ?? [];
}
Expand All @@ -360,6 +363,106 @@ private static CompressionType GetCompressionType(CFFOLDER folder)
// Return all found blocks in order
return [.. prevBlocks, .. folder.DataBlocks, .. nextBlocks];
}

/// <summary>
/// Loads in all the datablocks for the current folder.
/// </summary>
/// <param name="folder">The folder to have the datablocks loaded for</param>
public void GetData(CFFOLDER folder)
{
if (folder.CabStartOffset <= 0)
return;

uint offset = folder.CabStartOffset;
for (int i = 0; i < folder.DataCount; i++)
{
offset += 8;

if (Header.DataReservedSize > 0)
{
folder.DataBlocks[i].ReservedData = ReadRangeFromSource(offset, Header.DataReservedSize);
offset += Header.DataReservedSize;
}

if (folder.DataBlocks[i].CompressedSize > 0)
{
folder.DataBlocks[i].CompressedData = ReadRangeFromSource(offset, folder.DataBlocks[i].CompressedSize);
offset += folder.DataBlocks[i].CompressedSize;
}
}

}

/// <summary>
/// Get all files for the current folder, plus connected spanned folders.
/// </summary>
/// <param name="folderIndex">Index of the folder in the cabinet</param>
/// <param name="ignorePrev">True to ignore previous links, false otherwise</param>
/// <returns>Array of all files for the folder</returns>
private CFFILE[] GetSpannedFiles(string? filename, int folderIndex, bool ignorePrev = false, bool skipPrev = false, bool skipNext = false)
{
// Ignore invalid archives
if (Files.IsNullOrEmpty())
return [];

// Get all files with a name and matching index
var files = Array.FindAll(Files, f =>
{
if (string.IsNullOrEmpty(f.Name))
return false;

// Ignore links to previous cabinets, if required
if (ignorePrev)
{
if (f.FolderIndex == FolderIndex.CONTINUED_FROM_PREV)
return false;
else if (f.FolderIndex == FolderIndex.CONTINUED_PREV_AND_NEXT)
return false;
}

int fileFolder = GetFolderIndex(f);
return fileFolder == folderIndex;
});

// Check if the folder spans in either direction
bool spanPrev = Array.Exists(files, f => f.FolderIndex == FolderIndex.CONTINUED_FROM_PREV || f.FolderIndex == FolderIndex.CONTINUED_PREV_AND_NEXT);
bool spanNext = Array.Exists(files, f => f.FolderIndex == FolderIndex.CONTINUED_TO_NEXT || f.FolderIndex == FolderIndex.CONTINUED_PREV_AND_NEXT);

// If the folder spans backward and Prev is not being skipped
CFFILE[] prevFiles = [];
if (!skipPrev && spanPrev)
{
// Try to get Prev if it doesn't exist
if (Prev?.Header == null)
Prev = OpenPrevious(filename);

// Get all files from Prev
if (Prev?.Header != null && Prev.Folders != null)
{
int prevFolderIndex = Prev.FolderCount - 1;
prevFiles = Prev.GetSpannedFiles(filename, prevFolderIndex, skipNext: true) ?? [];
}
}

// If the folder spans forward and Next is not being skipped
CFFILE[] nextFiles = [];
if (!skipNext && spanNext)
{
// Try to get Next if it doesn't exist
if (Next?.Header == null)
Next = OpenNext(filename);

// Get all files from Prev
if (Next?.Header != null && Next.Folders != null)
{
var nextFolder = Next.Folders[0];
nextFiles = Next.GetSpannedFiles(filename, 0, skipPrev: true) ?? [];
}
}

// Return all found files in order
return [.. prevFiles, .. files, .. nextFiles];
}

/// <summary>
/// Get all files for the current folder index
Expand Down