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
This commit is contained in:
HeroponRikiBestest
2026-01-05 11:45:20 -07:00
committed by GitHub
parent d7131dfa4c
commit 65a86d87f0
3 changed files with 184 additions and 8 deletions

View File

@@ -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;
}

View File

@@ -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;
}
}
/// <summary>
/// Extract the contents of a single folder
/// </summary>
@@ -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<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

View File

@@ -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];
}
/// <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