11using System ;
22using System . IO ;
33using System . Text . RegularExpressions ;
4+ using SabreTools . IO . Compression . zlib ;
45using SabreTools . Models . InstallShieldCabinet ;
6+ using static SabreTools . Models . InstallShieldCabinet . Constants ;
57
68namespace SabreTools . Serialization . Wrappers
79{
@@ -72,6 +74,15 @@ public int MajorVersion
7274
7375 #endregion
7476
77+ #region Constants
78+
79+ /// <summary>
80+ /// Maximum size of the window in bits
81+ /// </summary>
82+ private const int MAX_WBITS = 15 ;
83+
84+ #endregion
85+
7586 #region Constructors
7687
7788 /// <inheritdoc/>
@@ -157,6 +168,73 @@ public InstallShieldCabinet(Cabinet? model, Stream? data)
157168 return new Regex ( @"\d+$" ) . Replace ( pattern , string . Empty ) ;
158169 }
159170
171+ /// <summary>
172+ /// Open a cabinet set for reading, if possible
173+ /// </summary>
174+ /// <param name="pattern">Filename pattern for matching cabinet files</param>
175+ /// <returns></returns>
176+ public static InstallShieldCabinet ? OpenSet ( string ? pattern )
177+ {
178+ // An invalid pattern means no cabinet files
179+ if ( string . IsNullOrEmpty ( pattern ) )
180+ return null ;
181+
182+ // Create a placeholder wrapper for output
183+ InstallShieldCabinet ? set = null ;
184+
185+ // Loop until there are no parts left
186+ bool iterate = true ;
187+ InstallShieldCabinet ? previous = null ;
188+ for ( int i = 1 ; iterate ; i ++ )
189+ {
190+ var file = OpenFileForReading ( pattern , i , HEADER_SUFFIX ) ;
191+ if ( file != null )
192+ iterate = false ;
193+ else
194+ file = OpenFileForReading ( pattern , i , CABINET_SUFFIX ) ;
195+
196+ if ( file == null )
197+ break ;
198+
199+ var header = Create ( file ) ;
200+ if ( header == null )
201+ break ;
202+
203+ if ( previous != null )
204+ previous . Next = header ;
205+ else
206+ previous = set = header ;
207+ }
208+
209+ return set ;
210+ }
211+
212+ /// <summary>
213+ /// Open a cabinet file for reading
214+ /// </summary>
215+ /// <param name="pattern">Filename pattern for matching cabinet files</param>
216+ /// <param name="index">Cabinet part index to be opened</param>
217+ /// <param name="suffix">Cabinet files suffix (e.g. `.cab`)</param>
218+ /// <returns>A Stream representing the cabinet part, null on error</returns>
219+ private static Stream ? OpenFileForReading ( string ? pattern , int index , string suffix )
220+ {
221+ // An invalid pattern means no cabinet files
222+ if ( string . IsNullOrEmpty ( pattern ) )
223+ return null ;
224+
225+ // Attempt lower-case extension
226+ string filename = $ "{ pattern } { index } .{ suffix } ";
227+ if ( File . Exists ( filename ) )
228+ return File . Open ( filename , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) ;
229+
230+ // Attempt upper-case extension
231+ filename = $ "{ pattern } { index } .{ suffix . ToUpperInvariant ( ) } ";
232+ if ( File . Exists ( filename ) )
233+ return File . Open ( filename , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) ;
234+
235+ return null ;
236+ }
237+
160238 #endregion
161239
162240 #region Component
@@ -271,6 +349,33 @@ public ulong GetExpandedFileSize(int index)
271349 return Model . FileDescriptors [ index ] ;
272350 }
273351
352+ /// <summary>
353+ /// Get the file descriptor at a given index, if possible
354+ /// </summary>
355+ /// <remarks>Verifies the file descriptor flags before returning</remarks>
356+ public FileDescriptor ? GetFileDescriptorWithVerification ( int index , out string ? error )
357+ {
358+ var fileDescriptor = GetFileDescriptor ( index ) ;
359+ if ( fileDescriptor == null )
360+ {
361+ error = $ "Failed to get file descriptor for file { index } ";
362+ return null ;
363+ }
364+
365+ #if NET20 || NET35
366+ if ( ( fileDescriptor . Flags & FileFlags . FILE_INVALID ) != 0 || fileDescriptor . DataOffset == 0 )
367+ #else
368+ if ( fileDescriptor . Flags . HasFlag ( FileFlags . FILE_INVALID ) || fileDescriptor . DataOffset == 0 )
369+ #endif
370+ {
371+ error = $ "File at { index } is marked as invalid";
372+ return null ;
373+ }
374+
375+ error = null ;
376+ return fileDescriptor ;
377+ }
378+
274379 /// <summary>
275380 /// Get the file name at a given index, if possible
276381 /// </summary>
@@ -287,6 +392,24 @@ public ulong GetExpandedFileSize(int index)
287392 return descriptor . Name ;
288393 }
289394
395+ /// <summary>
396+ /// Get the packed size of a file, if possible
397+ /// </summary>
398+ public static ulong GetReadableBytes ( FileDescriptor ? descriptor )
399+ {
400+ if ( descriptor == null )
401+ return 0 ;
402+
403+ #if NET20 || NET35
404+ if ( ( descriptor . Flags & FileFlags . FILE_COMPRESSED ) != 0 )
405+ #else
406+ if ( descriptor . Flags . HasFlag ( FileFlags . FILE_COMPRESSED ) )
407+ #endif
408+ return descriptor . CompressedSize ;
409+ else
410+ return descriptor . ExpandedSize ;
411+ }
412+
290413 #endregion
291414
292415 #region File Group
@@ -355,5 +478,83 @@ public ulong GetExpandedFileSize(int index)
355478 => GetFileGroupFromFile ( index ) ? . Name ;
356479
357480 #endregion
481+
482+ #region Extraction
483+
484+ /// <summary>
485+ /// Uncompress a source byte array to a destination
486+ /// </summary>
487+ public unsafe static int Uncompress ( byte [ ] dest , ref ulong destLen , byte [ ] source , ref ulong sourceLen )
488+ {
489+ fixed ( byte * sourcePtr = source )
490+ fixed ( byte * destPtr = dest )
491+ {
492+ var stream = new ZLib . z_stream_s
493+ {
494+ next_in = sourcePtr ,
495+ avail_in = ( uint ) sourceLen ,
496+ next_out = destPtr ,
497+ avail_out = ( uint ) destLen ,
498+ } ;
499+
500+ // make second parameter negative to disable checksum verification
501+ int err = ZLib . inflateInit2_ ( stream , - MAX_WBITS , ZLib . zlibVersion ( ) , source . Length ) ;
502+ if ( err != zlibConst . Z_OK )
503+ return err ;
504+
505+ err = ZLib . inflate ( stream , 1 ) ;
506+ if ( err != zlibConst . Z_STREAM_END )
507+ {
508+ ZLib . inflateEnd ( stream ) ;
509+ return err ;
510+ }
511+
512+ destLen = stream . total_out ;
513+ sourceLen = stream . total_in ;
514+ return ZLib . inflateEnd ( stream ) ;
515+ }
516+ }
517+
518+ /// <summary>
519+ /// Uncompress a source byte array to a destination (old version)
520+ /// </summary>
521+ public unsafe static int UncompressOld ( byte [ ] dest , ref ulong destLen , byte [ ] source , ref ulong sourceLen )
522+ {
523+ fixed ( byte * sourcePtr = source )
524+ fixed ( byte * destPtr = dest )
525+ {
526+ var stream = new ZLib . z_stream_s
527+ {
528+ next_in = sourcePtr ,
529+ avail_in = ( uint ) sourceLen ,
530+ next_out = destPtr ,
531+ avail_out = ( uint ) destLen ,
532+ } ;
533+
534+ destLen = 0 ;
535+ sourceLen = 0 ;
536+
537+ // make second parameter negative to disable checksum verification
538+ int err = ZLib . inflateInit2_ ( stream , - MAX_WBITS , ZLib . zlibVersion ( ) , source . Length ) ;
539+ if ( err != zlibConst . Z_OK )
540+ return err ;
541+
542+ while ( stream . avail_in > 1 )
543+ {
544+ err = ZLib . inflate ( stream , 1 ) ;
545+ if ( err != zlibConst . Z_OK )
546+ {
547+ ZLib . inflateEnd ( stream ) ;
548+ return err ;
549+ }
550+ }
551+
552+ destLen = stream . total_out ;
553+ sourceLen = stream . total_in ;
554+ return ZLib . inflateEnd ( stream ) ;
555+ }
556+ }
557+
558+ #endregion
358559 }
359560}
0 commit comments