From 65a86d87f0092facfbb9e5ecba1976f5ac32f792 Mon Sep 17 00:00:00 2001
From: HeroponRikiBestest
<50224630+HeroponRikiBestest@users.noreply.github.com>
Date: Mon, 5 Jan 2026 11:45:20 -0700
Subject: [PATCH] Fix mszip relying on exceptions (#52)
* Better fix
* initial
* Fixed
* Move function to proper location, clear memory better
* work so far
* more
* more work
* No more per file seeking
* Finished work for now.
* First round of fixes.
* Return if array is empty too
---
.../Readers/MicrosoftCabinet.cs | 8 +-
.../Wrappers/MicrosoftCabinet.Extraction.cs | 79 ++++++++++++-
.../Wrappers/MicrosoftCabinet.cs | 105 +++++++++++++++++-
3 files changed, 184 insertions(+), 8 deletions(-)
diff --git a/SabreTools.Serialization/Readers/MicrosoftCabinet.cs b/SabreTools.Serialization/Readers/MicrosoftCabinet.cs
index 875f35a7..25d2fdc2 100644
--- a/SabreTools.Serialization/Readers/MicrosoftCabinet.cs
+++ b/SabreTools.Serialization/Readers/MicrosoftCabinet.cs
@@ -203,13 +203,13 @@ namespace SabreTools.Serialization.Readers
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;
}
diff --git a/SabreTools.Serialization/Wrappers/MicrosoftCabinet.Extraction.cs b/SabreTools.Serialization/Wrappers/MicrosoftCabinet.Extraction.cs
index 9b754325..8831b0d8 100644
--- a/SabreTools.Serialization/Wrappers/MicrosoftCabinet.Extraction.cs
+++ b/SabreTools.Serialization/Wrappers/MicrosoftCabinet.Extraction.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.IO;
using SabreTools.Data.Models.MicrosoftCabinet;
using SabreTools.IO.Extensions;
@@ -174,12 +175,22 @@ namespace SabreTools.Serialization.Wrappers
// 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;
}
@@ -192,7 +203,7 @@ namespace SabreTools.Serialization.Wrappers
return false;
}
}
-
+
///
/// Extract the contents of a single folder
///
@@ -217,15 +228,77 @@ namespace SabreTools.Serialization.Wrappers
// Loop through the files
bool allExtracted = true;
- var files = GetFiles(folderIndex, ignorePrev);
+ var filterFiles = GetSpannedFiles(filename, folderIndex, ignorePrev);
+ List 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
+ ///
+ /// Extract the contents of a single file, intended to be used with all files in a straight shot
+ ///
+ /// Path to the output directory
+ /// Stream representing the uncompressed block data
+ /// File information
+ /// True to include debug data, false otherwise
+ /// True if the file extracted, false otherwise
+ 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;
+ }
///
/// Extract the contents of a single file
diff --git a/SabreTools.Serialization/Wrappers/MicrosoftCabinet.cs b/SabreTools.Serialization/Wrappers/MicrosoftCabinet.cs
index 49ab1983..78743500 100644
--- a/SabreTools.Serialization/Wrappers/MicrosoftCabinet.cs
+++ b/SabreTools.Serialization/Wrappers/MicrosoftCabinet.cs
@@ -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
{
@@ -315,6 +316,8 @@ namespace SabreTools.Serialization.Wrappers
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)
@@ -335,7 +338,7 @@ namespace SabreTools.Serialization.Wrappers
// 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) ?? [];
}
@@ -360,6 +363,106 @@ namespace SabreTools.Serialization.Wrappers
// Return all found blocks in order
return [.. prevBlocks, .. folder.DataBlocks, .. nextBlocks];
}
+
+ ///
+ /// Loads in all the datablocks for the current folder.
+ ///
+ /// The folder to have the datablocks loaded for
+ 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;
+ }
+ }
+
+ }
+
+ ///
+ /// Get all files for the current folder, plus connected spanned folders.
+ ///
+ /// Index of the folder in the cabinet
+ /// True to ignore previous links, false otherwise
+ /// Array of all files for the folder
+ 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];
+ }
///
/// Get all files for the current folder index