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 /// /// Cabinet file to read from /// private InstallShieldCabinet? _cabinet; /// /// Currently selected index /// private uint _index; /// /// File descriptor defining the currently selected index /// private FileDescriptor? _fileDescriptor; /// /// Number of bytes left in the current volume /// private ulong _volumeBytesLeft; /// /// Handle to the current volume stream /// private Stream? _volumeFile; /// /// Current volume header /// private VolumeHeader? _volumeHeader; /// /// Current volume ID /// private ushort _volumeId; /// /// Offset for obfuscation seed /// private uint _obfuscationOffset; #endregion /// /// Create a new from an existing cabinet, index, and file descriptor /// 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; } /// /// Dispose of the current object /// public void Dispose() { _volumeFile?.Close(); } #region Reading /// /// Open the next volume based on the current index /// public bool OpenNextVolume(out ushort nextVolume) { nextVolume = (ushort)(_volumeId + 1); return OpenVolume(nextVolume); } /// /// Read a certain number of bytes from the current volume /// 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; } /// /// Open the volume at the inputted index /// 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(); 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 } }