Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
ca115c0
Initial implementation
HeroponRikiBestest Jan 5, 2026
1499c84
Commit to the right branch this time
HeroponRikiBestest Jan 5, 2026
be8519a
Use newtonsoft instead of System.Text.Json
HeroponRikiBestest Jan 6, 2026
9481dd5
Don't parse constant strings
HeroponRikiBestest Jan 6, 2026
e20ed73
Add comments for documentation
HeroponRikiBestest Jan 6, 2026
2e7f9e1
Add reader and wrapper tests
HeroponRikiBestest Jan 6, 2026
9f98ef3
Fix Naming
HeroponRikiBestest Jan 23, 2026
2e5c5b3
Extract Ms-cabs while reading instead of loading all datablocks into …
HeroponRikiBestest Jan 24, 2026
6bfe68b
Formatting and comment cleanup
mnadareski Jan 24, 2026
f16ed97
Support WoD info CMP block type
mnadareski Jan 24, 2026
695fc4b
Change mscab extraction to use state handling, break into more helper…
HeroponRikiBestest Jan 25, 2026
4bfaf36
Formatting cleanup
mnadareski Jan 25, 2026
7f7d0f8
Re-enable stream extraction for MScab (#58)
HeroponRikiBestest Jan 25, 2026
5dfe6ae
Convert Installshield Executable code to use proper reader/wrapper in…
HeroponRikiBestest Jan 25, 2026
2959d82
Formatting cleanup
mnadareski Jan 25, 2026
ee85a84
Use newer "is null" syntax
mnadareski Jan 25, 2026
4300d94
Use newer "is not null" syntax
mnadareski Jan 25, 2026
a35e58e
Fix some errant formatting
mnadareski Jan 25, 2026
344132b
Add editorconfig, fix issues
mnadareski Jan 25, 2026
59b918c
Apparently didn't rename this
mnadareski Jan 26, 2026
69b6595
Missed this one too
mnadareski Jan 26, 2026
527b09c
Add debug flag to interface definitions (fixes #60)
mnadareski Jan 26, 2026
ecca11e
Flush before closing in microsoftcabinet.extraction (#61)
HeroponRikiBestest Jan 26, 2026
8f8d86d
Clean up some formatting things
mnadareski Jan 27, 2026
07cc01d
Extract Ms-cabs while reading instead of loading all datablocks into …
HeroponRikiBestest Jan 24, 2026
2d751e6
Formatting and comment cleanup
mnadareski Jan 24, 2026
d4090b1
Support WoD info CMP block type
mnadareski Jan 24, 2026
e16d112
Change mscab extraction to use state handling, break into more helper…
HeroponRikiBestest Jan 25, 2026
b50ebff
Formatting cleanup
mnadareski Jan 25, 2026
7213762
Re-enable stream extraction for MScab (#58)
HeroponRikiBestest Jan 25, 2026
57765c4
Convert Installshield Executable code to use proper reader/wrapper in…
HeroponRikiBestest Jan 25, 2026
2eecbdb
Formatting cleanup
mnadareski Jan 25, 2026
27684a4
Use newer "is null" syntax
mnadareski Jan 25, 2026
8be8e69
Use newer "is not null" syntax
mnadareski Jan 25, 2026
12232fa
Fix some errant formatting
mnadareski Jan 25, 2026
2d31caa
Add editorconfig, fix issues
mnadareski Jan 25, 2026
5a37906
Apparently didn't rename this
mnadareski Jan 26, 2026
58a72f4
Missed this one too
mnadareski Jan 26, 2026
b20af52
Add debug flag to interface definitions (fixes #60)
mnadareski Jan 26, 2026
73cb695
Flush before closing in microsoftcabinet.extraction (#61)
HeroponRikiBestest Jan 26, 2026
e809bb4
Clean up some formatting things
mnadareski Jan 27, 2026
50c266b
Apply reformat
HeroponRikiBestest Jan 27, 2026
b5ca082
Initial round of fixes
HeroponRikiBestest Jan 27, 2026
74e3bb4
Initial implementation
HeroponRikiBestest Jan 5, 2026
1479f85
Commit to the right branch this time
HeroponRikiBestest Jan 5, 2026
025e4d8
Use newtonsoft instead of System.Text.Json
HeroponRikiBestest Jan 6, 2026
b5d9bd1
Don't parse constant strings
HeroponRikiBestest Jan 6, 2026
f513fe4
Add comments for documentation
HeroponRikiBestest Jan 6, 2026
10e6f66
Add reader and wrapper tests
HeroponRikiBestest Jan 6, 2026
85713ae
Fix Naming
HeroponRikiBestest Jan 23, 2026
74ae0dc
Apply reformat
HeroponRikiBestest Jan 27, 2026
bd87c5c
Initial round of fixes
HeroponRikiBestest Jan 27, 2026
e54f12f
Merge remote-tracking branch 'origin/skusis-addition' into skusis-add…
HeroponRikiBestest Jan 27, 2026
1deb04f
Fix
HeroponRikiBestest Jan 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Convert Installshield Executable code to use proper reader/wrapper in…
…stead of living in PortableExecutable (#59)

* Figure out how to access OverlayAddress in wrapper or reader (ideally the latter) for a non-PE reader/wrapper

* Code works

* Remove TODOs

* First round of fixes.

* use constants

* remove comment
  • Loading branch information
HeroponRikiBestest committed Jan 27, 2026
commit 57765c4dc17039e448f61647d81ea1e99c25ece4
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

namespace SabreTools.Serialization.Test.Readers
{
public class InstallShieldExecutableFileTests
public class InstallShieldExecutableTests
{
[Fact]
public void NullArray_Null()
{
byte[]? data = null;
int offset = 0;
var deserializer = new InstallShieldExecutableFile();
var deserializer = new InstallShieldExecutable();

var actual = deserializer.Deserialize(data, offset);
Assert.Null(actual);
Expand All @@ -23,7 +23,7 @@ public void EmptyArray_Null()
{
byte[]? data = [];
int offset = 0;
var deserializer = new InstallShieldExecutableFile();
var deserializer = new InstallShieldExecutable();

var actual = deserializer.Deserialize(data, offset);
Assert.Null(actual);
Expand All @@ -34,7 +34,7 @@ public void InvalidArray_Null()
{
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
int offset = 0;
var deserializer = new InstallShieldExecutableFile();
var deserializer = new InstallShieldExecutable();

var actual = deserializer.Deserialize(data, offset);
Assert.Null(actual);
Expand All @@ -44,7 +44,7 @@ public void InvalidArray_Null()
public void NullStream_Null()
{
Stream? data = null;
var deserializer = new InstallShieldExecutableFile();
var deserializer = new InstallShieldExecutable();

var actual = deserializer.Deserialize(data);
Assert.Null(actual);
Expand All @@ -54,7 +54,7 @@ public void NullStream_Null()
public void EmptyStream_Null()
{
Stream? data = new MemoryStream([]);
var deserializer = new InstallShieldExecutableFile();
var deserializer = new InstallShieldExecutable();

var actual = deserializer.Deserialize(data);
Assert.Null(actual);
Expand All @@ -64,7 +64,7 @@ public void EmptyStream_Null()
public void InvalidStream_Null()
{
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
var deserializer = new InstallShieldExecutableFile();
var deserializer = new InstallShieldExecutable();

var actual = deserializer.Deserialize(data);
Assert.Null(actual);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace SabreTools.Data.Models.InstallShieldExecutable
{
public static class Constants
{
/// <summary>
/// The signature for one of two kinds of currently unsupported ISEXE formats.
/// </summary>
public const string ISSignatureString = "InstallShield";

/// <summary>
/// The signature for one of two kinds of currently unsupported ISEXE formats.
/// </summary>
public const string ISSetupSignatureString = "ISSetupStream";
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
namespace SabreTools.Data.Models.InstallShieldExecutable
{
/// <summary>
/// Set of attributes for each fileEntry in an InstallShield Executable
/// </summary>
public class FileEntry
{
/// <summary>
Expand All @@ -24,5 +27,11 @@ public class FileEntry
/// Length of the file. Stored in the installshield executable as a string.
/// </summary>
public ulong Length { get; set; }

/// <summary>
/// Offset of the file.
/// </summary>
/// <remarks>This is not stored in the installshield executable, but it needs to be stored here for extraction.</remarks>
public long Offset { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ namespace SabreTools.Data.Models.InstallShieldExecutable
/// documents the plain variant. Clearer naming and separation between the types is yet
/// to come.
/// </remarks>
/// TODO: Look into making the array a dictionary
/// There is no unified header or footer that indicates a file
/// table, so having either each file entry cache the data
/// or be associated with an offset may make it more useful.
Expand Down
98 changes: 98 additions & 0 deletions SabreTools.Serialization/Readers/InstallShieldExecutable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System.Collections.Generic;
using System.IO;
using SabreTools.Data.Models.InstallShieldExecutable;
using SabreTools.IO.Extensions;
using static SabreTools.Data.Models.InstallShieldExecutable.Constants;

namespace SabreTools.Serialization.Readers
{
public class InstallShieldExecutable : BaseBinaryReader<SFX>
{
public override SFX? Deserialize(Stream? data)
{
// If the data is invalid
if (data == null || !data.CanRead)
return null;

try
{
var sfx = new SFX();

// Cache the initial offset
long initialOffset = data.Position;

var sfxList = new List<FileEntry>();

while (data.Position < data.Length)
{
// Try to parse the entry
var fileEntry = ParseFileEntry(data, initialOffset);
if (fileEntry == null)
break;

// Get the length, and make sure it won't EOF
long length = (long)fileEntry.Length;
if (length > data.Length - data.Position)
break;

data.SeekIfPossible(length, SeekOrigin.Current);
sfxList.Add(fileEntry);
}

if (sfxList.Count == 0)
return null;

sfx.Entries = [.. sfxList];
return sfx;
}
catch
{
// Ignore the actual error
return null;
}
}

/// <summary>
/// Parse a Stream into a FileEntry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled FileEntry on success, null on error</returns>
public static FileEntry? ParseFileEntry(Stream data, long initialOffset)
{
string? name = data.ReadNullTerminatedAnsiString();
if (name == null)
return null;

// Both of these strings indicate that this is a different kind of encrypted and/or compressed format of
// ISEXE that is not yet supported, but will be in the future.
// They return early because no extraction can be performed, like how MsCab currently returns if a folder
// is LZX or Quantum.
if (name == ISSignatureString)
return null;

if (name == ISSetupSignatureString)
return null;

string? path = data.ReadNullTerminatedAnsiString();
if (path == null)
return null;

string? version = data.ReadNullTerminatedAnsiString();
if (version == null)
return null;

var lengthString = data.ReadNullTerminatedAnsiString();
if (lengthString == null || !ulong.TryParse(lengthString, out var lengthValue))
return null;

var obj = new FileEntry();
obj.Name = name;
obj.Path = path;
obj.Version = version;
obj.Length = lengthValue;
obj.Offset = data.Position - initialOffset;

return obj;
}
}
}
68 changes: 0 additions & 68 deletions SabreTools.Serialization/Readers/InstallShieldExecutableFile.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.IO;
using SabreTools.IO.Extensions;

namespace SabreTools.Serialization.Wrappers
{
public partial class InstallShieldExecutable : IExtractable
{
#region Extraction

/// <inheritdoc/>
public bool Extract(string outputDirectory, bool includeDebug)
{
const int chunkSize = 2048 * 1024;
try
{
for (int i = 0; i < Entries.Length; i++)
{
var entry = Entries[i];
_dataSource.SeekIfPossible(entry.Offset, SeekOrigin.Begin);

// Get the length, and make sure it won't EOF
long length = (long)entry.Length;
if (length > _dataSource.Length - _dataSource.Position)
break;

// Ensure directory separators are consistent
var filename = entry.Path.TrimEnd('\0');
if (Path.DirectorySeparatorChar == '\\')
filename = filename.Replace('/', '\\');
else if (Path.DirectorySeparatorChar == '/')
filename = filename.Replace('\\', '/');

// Ensure the full output directory exists
filename = Path.Combine(outputDirectory, filename);
var directoryName = Path.GetDirectoryName(filename);
if (directoryName != null && !Directory.Exists(directoryName))
Directory.CreateDirectory(directoryName);

// Write the output file
using var fs = File.Open(filename, FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
while (length > 0)
{
int bytesToRead = (int)Math.Min(length, chunkSize);

byte[] buffer = _dataSource.ReadBytes(bytesToRead);
fs.Write(buffer, 0, bytesToRead);
fs.Flush();

length -= bytesToRead;
}
}

return true;
}
catch (Exception ex)
{
if (includeDebug) Console.Error.WriteLine(ex);
return false;
}
}

#endregion
}
}
Loading