Privatize Reader class

This commit is contained in:
Matt Nadareski
2025-08-29 11:09:45 -04:00
parent a13c837202
commit 113464a7d1
2 changed files with 249 additions and 250 deletions

View File

@@ -1,249 +0,0 @@
using System;
using System.IO;
using SabreTools.Models.InstallShieldCabinet;
using Header = SabreTools.Serialization.Wrappers.InstallShieldCabinet;
namespace UnshieldSharpInternal
{
internal class Reader : IDisposable
{
#region Private Instance Variables
/// <summary>
/// Cabinet file to read from
/// </summary>
private readonly Header _cabinet;
/// <summary>
/// Currently selected index
/// </summary>
private readonly uint _index;
/// <summary>
/// File descriptor defining the currently selected index
/// </summary>
private readonly FileDescriptor _fileDescriptor;
/// <summary>
/// Offset in the data where the file exists
/// </summary>
private ulong _dataOffset;
/// <summary>
/// Number of bytes left in the current volume
/// </summary>
private ulong _volumeBytesLeft;
/// <summary>
/// Handle to the current volume stream
/// </summary>
private Stream? _volumeFile;
/// <summary>
/// Current volume header
/// </summary>
private VolumeHeader? _volumeHeader;
/// <summary>
/// Current volume ID
/// </summary>
private ushort _volumeId;
/// <summary>
/// Offset for obfuscation seed
/// </summary>
private uint _obfuscationOffset;
#endregion
#region Constructors
private Reader(Header cabinet, uint index, FileDescriptor fileDescriptor)
{
_cabinet = cabinet;
_index = index;
_fileDescriptor = fileDescriptor;
}
#endregion
/// <summary>
/// Create a new <see cref="Reader"> from an existing cabinet, index, and file descriptor
/// </summary>
public static Reader? Create(Header cabinet, int index, FileDescriptor fileDescriptor)
{
var reader = new Reader(cabinet, (uint)index, fileDescriptor);
for (; ; )
{
// If the volume is invalid
if (!reader.OpenVolume(fileDescriptor.Volume))
{
Console.Error.WriteLine($"Failed to open volume {fileDescriptor.Volume}");
return null;
}
else if (reader._volumeFile == null || reader._volumeHeader == null)
{
Console.Error.WriteLine($"Volume {fileDescriptor.Volume} is invalid");
return null;
}
// Start with the correct volume for IS5 cabinets
if (reader._cabinet.MajorVersion <= 5 && index > (int)reader._volumeHeader.LastFileIndex)
{
// Normalize the volume ID for odd cases
if (fileDescriptor.Volume == ushort.MinValue || fileDescriptor.Volume == ushort.MaxValue)
fileDescriptor.Volume = 1;
fileDescriptor.Volume++;
continue;
}
break;
}
return reader;
}
/// <summary>
/// Dispose of the current object
/// </summary>
public void Dispose()
{
_volumeFile?.Close();
}
#region Reading
/// <summary>
/// Read a certain number of bytes from the current volume
/// </summary>
public bool Read(byte[] buffer, int start, long size)
{
long bytesLeft = size;
while (bytesLeft > 0)
{
// Open the next volume, if necessary
if (_volumeBytesLeft == 0)
{
if (!OpenNextVolume(out _))
return false;
}
// Get the number of bytes to read from this volume
int bytesToRead = (int)Math.Min(bytesLeft, (long)_volumeBytesLeft);
if (bytesToRead == 0)
break;
// Read as much as possible from this volume
if (bytesToRead != _volumeFile!.Read(buffer, start, bytesToRead))
return false;
// Set the number of bytes left
bytesLeft -= bytesToRead;
_volumeBytesLeft -= (uint)bytesToRead;
}
#if NET20 || NET35
if ((_fileDescriptor.Flags & FileFlags.FILE_OBFUSCATED) != 0)
#else
if (_fileDescriptor.Flags.HasFlag(FileFlags.FILE_OBFUSCATED))
#endif
Header.Deobfuscate(buffer, size, ref _obfuscationOffset);
return true;
}
/// <summary>
/// Open the next volume based on the current index
/// </summary>
private bool OpenNextVolume(out ushort nextVolume)
{
nextVolume = (ushort)(_volumeId + 1);
return OpenVolume(nextVolume);
}
/// <summary>
/// Open the volume at the inputted index
/// </summary>
private bool OpenVolume(ushort volume)
{
// Read the volume from the cabinet set
var next = _cabinet.OpenVolume(volume, out var volumeStream);
if (next?.VolumeHeader == null || volumeStream == null)
{
Console.Error.WriteLine($"Failed to open input cabinet file {volume}");
return false;
}
// Assign the next items
_volumeFile?.Close();
_volumeFile = volumeStream;
_volumeHeader = next.VolumeHeader;
// Enable support for split archives for IS5
if (_cabinet.MajorVersion == 5)
{
if (_index < (_cabinet.FileCount - 1)
&& _index == _volumeHeader.LastFileIndex
&& _volumeHeader.LastFileSizeCompressed != _fileDescriptor.CompressedSize)
{
_fileDescriptor.Flags |= FileFlags.FILE_SPLIT;
}
else if (_index > 0
&& _index == _volumeHeader.FirstFileIndex
&& _volumeHeader.FirstFileSizeCompressed != _fileDescriptor.CompressedSize)
{
_fileDescriptor.Flags |= FileFlags.FILE_SPLIT;
}
}
ulong volumeBytesLeftCompressed, volumeBytesLeftExpanded;
#if NET20 || NET35
if ((_fileDescriptor.Flags & FileFlags.FILE_SPLIT) != 0)
#else
if (_fileDescriptor.Flags.HasFlag(FileFlags.FILE_SPLIT))
#endif
{
if (_index == _volumeHeader.LastFileIndex && _volumeHeader.LastFileOffset != 0x7FFFFFFF)
{
// can be first file too
_dataOffset = _volumeHeader.LastFileOffset;
volumeBytesLeftExpanded = _volumeHeader.LastFileSizeExpanded;
volumeBytesLeftCompressed = _volumeHeader.LastFileSizeCompressed;
}
else if (_index == _volumeHeader.FirstFileIndex)
{
_dataOffset = _volumeHeader.FirstFileOffset;
volumeBytesLeftExpanded = _volumeHeader.FirstFileSizeExpanded;
volumeBytesLeftCompressed = _volumeHeader.FirstFileSizeCompressed;
}
else
{
return true;
}
}
else
{
_dataOffset = _fileDescriptor.DataOffset;
volumeBytesLeftExpanded = _fileDescriptor.ExpandedSize;
volumeBytesLeftCompressed = _fileDescriptor.CompressedSize;
}
#if NET20 || NET35
if ((_fileDescriptor.Flags & FileFlags.FILE_COMPRESSED) != 0)
#else
if (_fileDescriptor.Flags.HasFlag(FileFlags.FILE_COMPRESSED))
#endif
_volumeBytesLeft = volumeBytesLeftCompressed;
else
_volumeBytesLeft = volumeBytesLeftExpanded;
_volumeFile.Seek((long)_dataOffset, SeekOrigin.Begin);
_volumeId = volume;
return true;
}
#endregion
}
}

View File

@@ -5,7 +5,6 @@ using SabreTools.Hashing;
using SabreTools.IO.Compression.zlib;
using SabreTools.Models.InstallShieldCabinet;
using SabreTools.Serialization.Interfaces;
using UnshieldSharpInternal;
using static SabreTools.Models.InstallShieldCabinet.Constants;
namespace SabreTools.Serialization.Wrappers
@@ -940,5 +939,254 @@ namespace SabreTools.Serialization.Wrappers
private static int ROL8(int x, byte n) => (x << n) | (x >> (8 - n));
#endregion
#region Helper Classes
/// <summary>
/// Helper to read a single file from a cabinet set
/// </summary>
private class Reader : IDisposable
{
#region Private Instance Variables
/// <summary>
/// Cabinet file to read from
/// </summary>
private readonly InstallShieldCabinet _cabinet;
/// <summary>
/// Currently selected index
/// </summary>
private readonly uint _index;
/// <summary>
/// File descriptor defining the currently selected index
/// </summary>
private readonly FileDescriptor _fileDescriptor;
/// <summary>
/// Offset in the data where the file exists
/// </summary>
private ulong _dataOffset;
/// <summary>
/// Number of bytes left in the current volume
/// </summary>
private ulong _volumeBytesLeft;
/// <summary>
/// Handle to the current volume stream
/// </summary>
private Stream? _volumeFile;
/// <summary>
/// Current volume header
/// </summary>
private VolumeHeader? _volumeHeader;
/// <summary>
/// Current volume ID
/// </summary>
private ushort _volumeId;
/// <summary>
/// Offset for obfuscation seed
/// </summary>
private uint _obfuscationOffset;
#endregion
#region Constructors
private Reader(InstallShieldCabinet cabinet, uint index, FileDescriptor fileDescriptor)
{
_cabinet = cabinet;
_index = index;
_fileDescriptor = fileDescriptor;
}
#endregion
/// <summary>
/// Create a new <see cref="Reader"> from an existing cabinet, index, and file descriptor
/// </summary>
public static Reader? Create(InstallShieldCabinet cabinet, int index, FileDescriptor fileDescriptor)
{
var reader = new Reader(cabinet, (uint)index, fileDescriptor);
for (; ; )
{
// If the volume is invalid
if (!reader.OpenVolume(fileDescriptor.Volume))
{
Console.Error.WriteLine($"Failed to open volume {fileDescriptor.Volume}");
return null;
}
else if (reader._volumeFile == null || reader._volumeHeader == null)
{
Console.Error.WriteLine($"Volume {fileDescriptor.Volume} is invalid");
return null;
}
// Start with the correct volume for IS5 cabinets
if (reader._cabinet.MajorVersion <= 5 && index > (int)reader._volumeHeader.LastFileIndex)
{
// Normalize the volume ID for odd cases
if (fileDescriptor.Volume == ushort.MinValue || fileDescriptor.Volume == ushort.MaxValue)
fileDescriptor.Volume = 1;
fileDescriptor.Volume++;
continue;
}
break;
}
return reader;
}
/// <summary>
/// Dispose of the current object
/// </summary>
public void Dispose()
{
_volumeFile?.Close();
}
#region Reading
/// <summary>
/// Read a certain number of bytes from the current volume
/// </summary>
public bool Read(byte[] buffer, int start, long size)
{
long bytesLeft = size;
while (bytesLeft > 0)
{
// Open the next volume, if necessary
if (_volumeBytesLeft == 0)
{
if (!OpenNextVolume(out _))
return false;
}
// Get the number of bytes to read from this volume
int bytesToRead = (int)Math.Min(bytesLeft, (long)_volumeBytesLeft);
if (bytesToRead == 0)
break;
// Read as much as possible from this volume
if (bytesToRead != _volumeFile!.Read(buffer, start, bytesToRead))
return false;
// Set the number of bytes left
bytesLeft -= bytesToRead;
_volumeBytesLeft -= (uint)bytesToRead;
}
#if NET20 || NET35
if ((_fileDescriptor.Flags & FileFlags.FILE_OBFUSCATED) != 0)
#else
if (_fileDescriptor.Flags.HasFlag(FileFlags.FILE_OBFUSCATED))
#endif
Deobfuscate(buffer, size, ref _obfuscationOffset);
return true;
}
/// <summary>
/// Open the next volume based on the current index
/// </summary>
private bool OpenNextVolume(out ushort nextVolume)
{
nextVolume = (ushort)(_volumeId + 1);
return OpenVolume(nextVolume);
}
/// <summary>
/// Open the volume at the inputted index
/// </summary>
private bool OpenVolume(ushort volume)
{
// Read the volume from the cabinet set
var next = _cabinet.OpenVolume(volume, out var volumeStream);
if (next?.VolumeHeader == null || volumeStream == null)
{
Console.Error.WriteLine($"Failed to open input cabinet file {volume}");
return false;
}
// Assign the next items
_volumeFile?.Close();
_volumeFile = volumeStream;
_volumeHeader = next.VolumeHeader;
// Enable support for split archives for IS5
if (_cabinet.MajorVersion == 5)
{
if (_index < (_cabinet.FileCount - 1)
&& _index == _volumeHeader.LastFileIndex
&& _volumeHeader.LastFileSizeCompressed != _fileDescriptor.CompressedSize)
{
_fileDescriptor.Flags |= FileFlags.FILE_SPLIT;
}
else if (_index > 0
&& _index == _volumeHeader.FirstFileIndex
&& _volumeHeader.FirstFileSizeCompressed != _fileDescriptor.CompressedSize)
{
_fileDescriptor.Flags |= FileFlags.FILE_SPLIT;
}
}
ulong volumeBytesLeftCompressed, volumeBytesLeftExpanded;
#if NET20 || NET35
if ((_fileDescriptor.Flags & FileFlags.FILE_SPLIT) != 0)
#else
if (_fileDescriptor.Flags.HasFlag(FileFlags.FILE_SPLIT))
#endif
{
if (_index == _volumeHeader.LastFileIndex && _volumeHeader.LastFileOffset != 0x7FFFFFFF)
{
// can be first file too
_dataOffset = _volumeHeader.LastFileOffset;
volumeBytesLeftExpanded = _volumeHeader.LastFileSizeExpanded;
volumeBytesLeftCompressed = _volumeHeader.LastFileSizeCompressed;
}
else if (_index == _volumeHeader.FirstFileIndex)
{
_dataOffset = _volumeHeader.FirstFileOffset;
volumeBytesLeftExpanded = _volumeHeader.FirstFileSizeExpanded;
volumeBytesLeftCompressed = _volumeHeader.FirstFileSizeCompressed;
}
else
{
return true;
}
}
else
{
_dataOffset = _fileDescriptor.DataOffset;
volumeBytesLeftExpanded = _fileDescriptor.ExpandedSize;
volumeBytesLeftCompressed = _fileDescriptor.CompressedSize;
}
#if NET20 || NET35
if ((_fileDescriptor.Flags & FileFlags.FILE_COMPRESSED) != 0)
#else
if (_fileDescriptor.Flags.HasFlag(FileFlags.FILE_COMPRESSED))
#endif
_volumeBytesLeft = volumeBytesLeftCompressed;
else
_volumeBytesLeft = volumeBytesLeftExpanded;
_volumeFile.Seek((long)_dataOffset, SeekOrigin.Begin);
_volumeId = volume;
return true;
}
#endregion
}
#endregion
}
}