Files
SabreTools.Serialization/SabreTools.Serialization/UnshieldSharp/Reader.cs
2025-08-29 09:14:33 -04:00

255 lines
8.8 KiB
C#

using System;
using System.IO;
using SabreTools.IO.Extensions;
using SabreTools.Models.InstallShieldCabinet;
using static SabreTools.Models.InstallShieldCabinet.Constants;
namespace UnshieldSharpInternal
{
internal class Reader : IDisposable
{
#region Private Instance Variables
/// <summary>
/// Cabinet file to read from
/// </summary>
private InstallShieldCabinet? _cabinet;
/// <summary>
/// Currently selected index
/// </summary>
private uint _index;
/// <summary>
/// File descriptor defining the currently selected index
/// </summary>
private FileDescriptor? _fileDescriptor;
/// <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
/// <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 = cabinet,
_index = (uint)index,
_fileDescriptor = fileDescriptor,
};
// If the cabinet header list is invalid
if (reader._cabinet.HeaderList == null)
{
Console.Error.WriteLine($"Header list is invalid");
return null;
}
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.HeaderList.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>
/// Open the next volume based on the current index
/// </summary>
public bool OpenNextVolume(out ushort nextVolume)
{
nextVolume = (ushort)(_volumeId + 1);
return OpenVolume(nextVolume);
}
/// <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
SabreTools.Serialization.Wrappers.InstallShieldCabinet.Deobfuscate(buffer, size, ref _obfuscationOffset);
return true;
}
/// <summary>
/// Open the volume at the inputted index
/// </summary>
private bool OpenVolume(ushort volume)
{
// Normalize the volume ID for odd cases
if (volume == ushort.MinValue || volume == ushort.MaxValue)
volume = 1;
_volumeFile?.Close();
_volumeFile = SabreTools.Serialization.Wrappers.InstallShieldCabinet.OpenFileForReading(_cabinet!.FilenamePattern, volume, CABINET_SUFFIX);
if (_volumeFile == null)
{
Console.Error.WriteLine($"Failed to open input cabinet file {volume}");
return false;
}
var commonHeader = _volumeFile.ReadType<CommonHeader>();
if (commonHeader == default)
return false;
_volumeHeader = SabreTools.Serialization.Deserializers.InstallShieldCabinet.ParseVolumeHeader(_volumeFile, _cabinet.HeaderList!.MajorVersion);
if (_volumeHeader == null)
return false;
// Enable support for split archives for IS5
if (_cabinet.HeaderList.MajorVersion == 5)
{
if (_index < (_cabinet.HeaderList.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 dataOffset, 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
}
}