mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-04-05 22:01:33 +00:00
This change looks dramatic, but it's just separating out the already-split namespaces into separate top-level folders. In theory, every single one could be built into their own Nuget package. `SabreTools.Serialization` still builds the normal Nuget package that is used by all other projects and includes all namespaces.
360 lines
11 KiB
C#
360 lines
11 KiB
C#
using System;
|
|
using System.IO;
|
|
using SabreTools.Data.Extensions;
|
|
using SabreTools.Data.Models.InstallShieldCabinet;
|
|
|
|
namespace SabreTools.Wrappers
|
|
{
|
|
public partial class InstallShieldCabinet : WrapperBase<Cabinet>
|
|
{
|
|
#region Descriptive Properties
|
|
|
|
/// <inheritdoc/>
|
|
public override string DescriptionString => "InstallShield Cabinet";
|
|
|
|
#endregion
|
|
|
|
#region Extension Properties
|
|
|
|
/// <inheritdoc cref="Cabinet.CommonHeader"/>
|
|
public CommonHeader CommonHeader => Model.CommonHeader;
|
|
|
|
/// <inheritdoc cref="Cabinet.Components"/>
|
|
public Component[] Components => Model.Components;
|
|
|
|
/// <summary>
|
|
/// Number of components in the cabinet set
|
|
/// </summary>
|
|
public int ComponentCount => Components.Length;
|
|
|
|
/// <summary>
|
|
/// Number of directories in the cabinet set
|
|
/// </summary>
|
|
public ushort DirectoryCount => Model.Descriptor.DirectoryCount;
|
|
|
|
/// <inheritdoc cref="Cabinet.DirectoryNames"/>
|
|
public string[] DirectoryNames => Model.DirectoryNames;
|
|
|
|
/// <summary>
|
|
/// Number of files in the cabinet set
|
|
/// </summary>
|
|
public uint FileCount => Model.Descriptor.FileCount;
|
|
|
|
/// <inheritdoc cref="Cabinet.FileDescriptors"/>
|
|
public FileDescriptor[] FileDescriptors => Model.FileDescriptors;
|
|
|
|
/// <inheritdoc cref="Cabinet.FileGroups"/>
|
|
public FileGroup[] FileGroups => Model.FileGroups;
|
|
|
|
/// <summary>
|
|
/// Number of file groups in the cabinet set
|
|
/// </summary>
|
|
public int FileGroupCount => Model.FileGroups.Length;
|
|
|
|
/// <summary>
|
|
/// Indicates if Unicode strings are used
|
|
/// </summary>
|
|
public bool IsUnicode => MajorVersion >= 17;
|
|
|
|
/// <summary>
|
|
/// The major version of the cabinet
|
|
/// </summary>
|
|
public int MajorVersion => Model.GetMajorVersion();
|
|
|
|
/// <inheritdoc cref="Cabinet.VolumeHeader"/>
|
|
public VolumeHeader VolumeHeader => Model.VolumeHeader;
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
/// <inheritdoc/>
|
|
public InstallShieldCabinet(Cabinet model, byte[] data) : base(model, data) { }
|
|
|
|
/// <inheritdoc/>
|
|
public InstallShieldCabinet(Cabinet model, byte[] data, int offset) : base(model, data, offset) { }
|
|
|
|
/// <inheritdoc/>
|
|
public InstallShieldCabinet(Cabinet model, byte[] data, int offset, int length) : base(model, data, offset, length) { }
|
|
|
|
/// <inheritdoc/>
|
|
public InstallShieldCabinet(Cabinet model, Stream data) : base(model, data) { }
|
|
|
|
/// <inheritdoc/>
|
|
public InstallShieldCabinet(Cabinet model, Stream data, long offset) : base(model, data, offset) { }
|
|
|
|
/// <inheritdoc/>
|
|
public InstallShieldCabinet(Cabinet model, Stream data, long offset, long length) : base(model, data, offset, length) { }
|
|
|
|
#endregion
|
|
|
|
#region Static Constructors
|
|
|
|
/// <summary>
|
|
/// Create an InstallShield Cabinet from a byte array and offset
|
|
/// </summary>
|
|
/// <param name="data">Byte array representing the cabinet</param>
|
|
/// <param name="offset">Offset within the array to parse</param>
|
|
/// <returns>A cabinet wrapper on success, null on failure</returns>
|
|
public static InstallShieldCabinet? Create(byte[]? data, int offset)
|
|
{
|
|
// If the data is invalid
|
|
if (data is null || data.Length == 0)
|
|
return null;
|
|
|
|
// If the offset is out of bounds
|
|
if (offset < 0 || offset >= data.Length)
|
|
return null;
|
|
|
|
// Create a memory stream and use that
|
|
var dataStream = new MemoryStream(data, offset, data.Length - offset);
|
|
return Create(dataStream);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a InstallShield Cabinet from a Stream
|
|
/// </summary>
|
|
/// <param name="data">Stream representing the cabinet</param>
|
|
/// <returns>A cabinet wrapper on success, null on failure</returns>
|
|
public static InstallShieldCabinet? Create(Stream? data)
|
|
{
|
|
// If the data is invalid
|
|
if (data is null || !data.CanRead)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
// Cache the current offset
|
|
long currentOffset = data.Position;
|
|
|
|
var model = new Serialization.Readers.InstallShieldCabinet().Deserialize(data);
|
|
if (model is null)
|
|
return null;
|
|
|
|
return new InstallShieldCabinet(model, data, currentOffset);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Component
|
|
|
|
/// <summary>
|
|
/// Get the component name at a given index, if possible
|
|
/// </summary>
|
|
public string? GetComponentName(int index)
|
|
{
|
|
if (Components is null)
|
|
return null;
|
|
|
|
if (index < 0 || index >= ComponentCount)
|
|
return null;
|
|
|
|
var component = Components[index];
|
|
if (component?.Identifier is null)
|
|
return null;
|
|
|
|
return component.Identifier.Replace('\\', '/');
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Directory
|
|
|
|
/// <summary>
|
|
/// Get the directory name at a given index, if possible
|
|
/// </summary>
|
|
public string? GetDirectoryName(int index)
|
|
{
|
|
if (index < 0 || index >= DirectoryNames.Length)
|
|
return null;
|
|
|
|
return DirectoryNames[index];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the directory index for the given file index
|
|
/// </summary>
|
|
/// <returns>Directory index if found, UInt32.MaxValue on error</returns>
|
|
public uint GetDirectoryIndexFromFile(int index)
|
|
{
|
|
FileDescriptor? descriptor = GetFileDescriptor(index);
|
|
if (descriptor is not null)
|
|
return descriptor.DirectoryIndex;
|
|
else
|
|
return uint.MaxValue;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region File
|
|
|
|
/// <summary>
|
|
/// Returns if the file at a given index is marked as valid
|
|
/// </summary>
|
|
public bool FileIsValid(int index)
|
|
{
|
|
if (index < 0 || index > FileCount)
|
|
return false;
|
|
|
|
FileDescriptor? descriptor = GetFileDescriptor(index);
|
|
if (descriptor is null)
|
|
return false;
|
|
|
|
if (descriptor.IsInvalid())
|
|
return false;
|
|
|
|
if (descriptor.NameOffset == default)
|
|
return false;
|
|
|
|
if (descriptor.DataOffset == default)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the reported expanded file size for a given index
|
|
/// </summary>
|
|
public ulong GetExpandedFileSize(int index)
|
|
{
|
|
FileDescriptor? descriptor = GetFileDescriptor(index);
|
|
if (descriptor is not null)
|
|
return descriptor.ExpandedSize;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the file descriptor at a given index, if possible
|
|
/// </summary>
|
|
public FileDescriptor? GetFileDescriptor(int index)
|
|
{
|
|
if (index < 0 || index >= FileDescriptors.Length)
|
|
return null;
|
|
|
|
return FileDescriptors[index];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the file descriptor at a given index, if possible
|
|
/// </summary>
|
|
/// <remarks>Verifies the file descriptor flags before returning</remarks>
|
|
public bool TryGetFileDescriptor(int index, out FileDescriptor? fileDescriptor)
|
|
{
|
|
fileDescriptor = GetFileDescriptor(index);
|
|
if (fileDescriptor is null)
|
|
{
|
|
Console.Error.WriteLine($"Failed to get file descriptor for file {index}");
|
|
return false;
|
|
}
|
|
|
|
if (fileDescriptor.IsInvalid() || fileDescriptor.DataOffset == 0)
|
|
{
|
|
Console.Error.WriteLine($"File at {index} is marked as invalid");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the file name at a given index, if possible
|
|
/// </summary>
|
|
public string? GetFileName(int index)
|
|
{
|
|
var descriptor = GetFileDescriptor(index);
|
|
if (descriptor is null || descriptor.IsInvalid())
|
|
return null;
|
|
|
|
return descriptor.Name;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the packed size of a file, if possible
|
|
/// </summary>
|
|
public static ulong GetReadableBytes(FileDescriptor? descriptor)
|
|
{
|
|
if (descriptor is null)
|
|
return 0;
|
|
|
|
return descriptor.IsCompressed()
|
|
? descriptor.CompressedSize
|
|
: descriptor.ExpandedSize;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the packed size of a file, if possible
|
|
/// </summary>
|
|
public static ulong GetWritableBytes(FileDescriptor? descriptor)
|
|
{
|
|
if (descriptor is null)
|
|
return 0;
|
|
|
|
return descriptor.ExpandedSize;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region File Group
|
|
|
|
/// <summary>
|
|
/// Get the file group at a given index, if possible
|
|
/// </summary>
|
|
public FileGroup? GetFileGroup(int index)
|
|
{
|
|
if (index < 0 || index >= FileGroups.Length)
|
|
return null;
|
|
|
|
return FileGroups[index];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the file group at a given name, if possible
|
|
/// </summary>
|
|
public FileGroup? GetFileGroup(string name)
|
|
=> Array.Find(FileGroups, fg => string.Equals(fg.Name, name));
|
|
|
|
/// <summary>
|
|
/// Get the file group for the given file index, if possible
|
|
/// </summary>
|
|
public FileGroup? GetFileGroupFromFile(int index)
|
|
{
|
|
if (index < 0 || index >= FileCount)
|
|
return null;
|
|
|
|
for (int i = 0; i < FileGroupCount; i++)
|
|
{
|
|
var fileGroup = GetFileGroup(i);
|
|
if (fileGroup is null)
|
|
continue;
|
|
|
|
if (fileGroup.FirstFile > index || fileGroup.LastFile < index)
|
|
continue;
|
|
|
|
return fileGroup;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the file group name at a given index, if possible
|
|
/// </summary>
|
|
public string? GetFileGroupName(int index)
|
|
=> GetFileGroup(index)?.Name;
|
|
|
|
/// <summary>
|
|
/// Get the file group name at a given file index, if possible
|
|
/// </summary>
|
|
public string? GetFileGroupNameFromFile(int index)
|
|
=> GetFileGroupFromFile(index)?.Name;
|
|
|
|
#endregion
|
|
}
|
|
}
|