diff --git a/DICUI.Test/Utilities/DriveTest.cs b/DICUI.Test/Utilities/DriveTest.cs
index 8919999c..c853d420 100644
--- a/DICUI.Test/Utilities/DriveTest.cs
+++ b/DICUI.Test/Utilities/DriveTest.cs
@@ -9,7 +9,7 @@ namespace DICUI.Test.Utilities
public void DriveConstructorsTest()
{
Assert.True(Drive.Floppy('a').IsFloppy);
- Assert.False(Drive.Optical('d', "test").IsFloppy);
+ Assert.False(Drive.Optical('d', "test", true).IsFloppy);
}
}
}
diff --git a/DICUI.Test/Utilities/DumpEnvironmentTest.cs b/DICUI.Test/Utilities/DumpEnvironmentTest.cs
index c9fa135c..6100d524 100644
--- a/DICUI.Test/Utilities/DumpEnvironmentTest.cs
+++ b/DICUI.Test/Utilities/DumpEnvironmentTest.cs
@@ -18,7 +18,7 @@ namespace DICUI.Test
var env = new DumpEnvironment
{
DICParameters = new Parameters(parameters),
- Drive = isFloppy ? Drive.Floppy(letter) : Drive.Optical(letter, ""),
+ Drive = isFloppy ? Drive.Floppy(letter) : Drive.Optical(letter, "", true),
Type = mediaType,
};
diff --git a/DICUI/DICUI.csproj b/DICUI/DICUI.csproj
index 718845c5..d55c2f78 100644
--- a/DICUI/DICUI.csproj
+++ b/DICUI/DICUI.csproj
@@ -92,6 +92,9 @@
+
+ ..\packages\zlib.net.1.0.4.0\lib\zlib.net.dll
+
@@ -103,7 +106,18 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -188,15 +202,6 @@
Always
-
- Always
-
-
- Always
-
-
- Always
-
\ No newline at end of file
diff --git a/DICUI/Data/Constants.cs b/DICUI/Data/Constants.cs
index 25e6889c..22b8f53f 100644
--- a/DICUI/Data/Constants.cs
+++ b/DICUI/Data/Constants.cs
@@ -5,6 +5,7 @@
///
public static class UIElements
{
+ public const string DiscNotDetected = "Disc Not Detected";
public const string StartDumping = "Start Dumping";
public const string StopDumping = "Stop Dumping";
diff --git a/DICUI/External/BurnOut/ProtectionFind.cs b/DICUI/External/BurnOut/ProtectionFind.cs
index 6a479b32..b328ae13 100644
--- a/DICUI/External/BurnOut/ProtectionFind.cs
+++ b/DICUI/External/BurnOut/ProtectionFind.cs
@@ -21,7 +21,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
-using DICUI.External.iXcomp;
+using DICUI.External.Unshield;
using LibMSPackN;
namespace DICUI.External.BurnOut
@@ -72,7 +72,7 @@ namespace DICUI.External.BurnOut
protections[file] = mappings[Path.GetExtension(file)];
// Now check to see if the file contains any additional information
- string protectionname = ScanInFile(file).Replace("" + (char)0x00, "");
+ string protectionname = ScanInFile(file)?.Replace("" + (char)0x00, "");
if (!String.IsNullOrEmpty(protectionname))
protections[file] = protectionname;
}
@@ -96,14 +96,28 @@ namespace DICUI.External.BurnOut
///
private static string ScanInFile(string file)
{
+ // Get the extension for certain checks
string extension = Path.GetExtension(file).ToLower().TrimStart('.');
- #region EXE/DLL/ICD/DAT Content Checks
+ // Read the first 8 bytes to get the file type
+ string magic = "";
+ try
+ {
+ using (BinaryReader br = new BinaryReader(File.OpenRead(file)))
+ {
+ magic = new String(br.ReadChars(8));
+ }
+ }
+ catch
+ {
+ // We don't care what the issue was, we can't open the file
+ return null;
+ }
- if (extension == "exe" || extension == "ex_"
- || extension == "dll" || extension == "dl_"
- || extension == "dat"
- || extension == "icd")
+ #region Executable Content Checks
+
+ // Windows Executable and DLL
+ if (magic.StartsWith("MZ"))
{
try
{
@@ -206,8 +220,8 @@ namespace DICUI.External.BurnOut
if ((position = FileContent.IndexOf("" + (char)0xCA + (char)0xDD + (char)0xDD + (char)0xAC + (char)0x03)) > -1)
return "SecuROM " + GetSecuROM4and5Version(file, position);
- if (FileContent.Contains(".securom"))
- //if (FileContent.StartsWith(".securom" + (char)0xE0 + (char)0xC0))
+ if (FileContent.Contains(".securom")
+ || FileContent.StartsWith(".securom" + (char)0xE0 + (char)0xC0))
return "SecuROM " + GetSecuROM7Version(file);
if (FileContent.Contains("_and_play.dll" + (char)0x00 + "drm_pagui_doit"))
@@ -220,17 +234,15 @@ namespace DICUI.External.BurnOut
if ((position = FileContent.IndexOf("" + (char)0xEF + (char)0xBE + (char)0xAD + (char)0xDE)) > -1)
{
- position--; // TODO: Verify this subtract
if (FileContent.Substring(position + 5, 3) == "" + (char)0x00 + (char)0x00 + (char)0x00
&& FileContent.Substring(position + 16, 4) == "" + (char)0x00 + (char)0x10 + (char)0x00 + (char)0x00)
- return "SolidShield 1";
+ return "SolidShield 1 (SolidShield EXE Wrapper)";
else
{
string version = GetFileVersion(file);
string desc = FileVersionInfo.GetVersionInfo(file).FileDescription.ToLower();
if (!string.IsNullOrEmpty(version) && desc.Contains("solidshield"))
return "SolidShield Core.dll " + version;
- //return "SolidShield EXE Wrapper";
}
}
@@ -255,7 +267,7 @@ namespace DICUI.External.BurnOut
+ "o" + (char)0x00 + "n" + (char)0x00 + (char)0x00 + (char)0x00 + (char)0x00);
if (position > -1)
{
- position--;
+ position--; // TODO: Verify this subtract
return "SolidShield 2 + Tagès " + FileContent.Substring(position + 0x38, 1) + "." + FileContent.Substring(position + 0x38 + 4, 1) + "." + FileContent.Substring(position + 0x38 + 8, 1) + "." + FileContent.Substring(position + 0x38 + 12, 1);
}
else
@@ -342,8 +354,25 @@ namespace DICUI.External.BurnOut
#region Textfile Content Checks
- if (extension == "txt" || extension == "rtf" || extension == "doc" || extension == "docx")
+ if (magic.StartsWith("{\rtf") // Rich Text File
+ || magic.StartsWith("" + (char)0xd0 + (char)0xcf + (char)0x11 + (char)0xe0 + (char)0xa1 + (char)0xb1 + (char)0x1a + (char)0xe1) // Microsoft Office File (old)
+ || extension == "txt") // Generic textfile (no header)
{
+ try
+ {
+ StreamReader sr = File.OpenText(file);
+ string FileContent = sr.ReadToEnd().ToLower();
+ sr.Close();
+
+ // CD-Key
+ if (FileContent.Contains("a valid serial number is required")
+ || FileContent.Contains("serial number is located"))
+ return "CD-Key / Serial";
+ }
+ catch
+ {
+ // We don't care what the error was
+ }
// No-op
}
@@ -351,74 +380,90 @@ namespace DICUI.External.BurnOut
#region Archive Content Checks
- if (extension == "7z" || extension == "rar" || extension == "zip")
+ // 7-zip
+ if (magic.StartsWith("7z" + (char)0xbc + (char)0xaf + (char)0x27 + (char)0x1c))
{
// No-op
}
- else if (extension == "cab")
- {
- string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
- Directory.CreateDirectory(tempPath);
+ // InstallShield CAB
+ else if (magic.StartsWith("ISc"))
+ {
try
{
- // Read the first 4 bytes to get the archive type
- string magic = "";
- using (BinaryReader br = new BinaryReader(File.OpenRead(file)))
- {
- magic = new String(br.ReadChars(4));
- }
+ string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+ Directory.CreateDirectory(tempPath);
- // Microsoft CAB - "MSCF"
- if (magic.StartsWith("MSCF"))
+ UnshieldCabinet cabfile = UnshieldCabinet.Open(file);
+ for (int i = 0; i < cabfile.FileCount; i++)
{
- MSCabinet cabfile = new MSCabinet(file);
- foreach (var sub in cabfile.GetFiles())
+ string tempFileName = Path.Combine(tempPath, cabfile.FileName(i));
+ if (cabfile.FileSave(i, tempFileName))
{
- string tempfile = Path.Combine(tempPath, sub.Filename);
- sub.ExtractTo(tempfile);
- string protection = ScanInFile(tempfile);
- File.Delete(tempfile);
-
- if (!String.IsNullOrEmpty(protection))
- {
- return protection;
- }
- }
- }
- // InstallShield CAB - "ISc"
- else if (magic.StartsWith("ISc"))
- {
- IXComp.ListFiles(file, out int version);
- IXComp.ExtractAll(file, tempPath, version);
- var files = Directory.GetFiles(tempPath, "*", SearchOption.AllDirectories);
- files.Select(f => (new FileInfo(f).IsReadOnly = false));
- foreach (var sub in files)
- {
- string protection = ScanInFile(sub);
+ string protection = ScanInFile(tempFileName);
try
{
- File.Delete(sub);
+ File.Delete(tempFileName);
}
catch { }
if (!String.IsNullOrEmpty(protection))
+ {
+ try
+ {
+ Directory.Delete(tempPath, true);
+ }
+ catch { }
return protection;
+ }
}
}
}
- catch
+ catch { }
+ }
+
+ // Microsoft CAB
+ else if (magic.StartsWith("MSCF"))
+ {
+ try
{
- // We had access issues so we ignore
- }
- finally
- {
- try
+ string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+ Directory.CreateDirectory(tempPath);
+
+ MSCabinet cabfile = new MSCabinet(file);
+ foreach (var sub in cabfile.GetFiles())
{
- Directory.Delete(tempPath, true);
+ string tempfile = Path.Combine(tempPath, sub.Filename);
+ sub.ExtractTo(tempfile);
+ string protection = ScanInFile(tempfile);
+ File.Delete(tempfile);
+
+ if (!String.IsNullOrEmpty(protection))
+ {
+ try
+ {
+ Directory.Delete(tempPath, true);
+ }
+ catch { }
+ return protection;
+ }
}
- catch { }
}
+ catch { }
+ }
+
+ // PKZIP
+ else if (magic.StartsWith("PK" + (char)03 + (char)04)
+ || magic.StartsWith("PK" + (char)05 + (char)06)
+ || magic.StartsWith("PK" + (char)07 + (char)08))
+ {
+ // No-op
+ }
+ // RAR
+
+ else if (magic.StartsWith("Rar!"))
+ {
+ // No-op
}
#endregion
@@ -1192,10 +1237,13 @@ namespace DICUI.External.BurnOut
// dotFuscator - Not a protection
//mapping["DotfuscatorAttribute"] = "dotFuscator";
+ // EA CdKey Registration Module
+ mapping["ereg.ea-europe.com"] = "EA CdKey Registration Module";
+
// EXE Stealth
mapping["??[[__[[_" + (char)0x00 + "{{" + (char)0x0
+ (char)0x00 + "{{" + (char)0x00 + (char)0x00 + (char)0x00 + (char)0x00 + (char)0x0
- + (char)0x00 + (char)0x00 + (char)0x00 + (char)0x00 + "?;;??;;??"] = "EXE Stealth";
+ + (char)0x00 + (char)0x00 + (char)0x00 + (char)0x00 + "?;??;??"] = "EXE Stealth";
// Games for Windows - Live
mapping["xlive.dll"] = "Games for Windows - Live";
diff --git a/DICUI/External/Unshield/CabDescriptor.cs b/DICUI/External/Unshield/CabDescriptor.cs
new file mode 100644
index 00000000..34a65278
--- /dev/null
+++ b/DICUI/External/Unshield/CabDescriptor.cs
@@ -0,0 +1,15 @@
+namespace DICUI.External.Unshield
+{
+ public class CabDescriptor
+ {
+ public uint FileTableOffset; /* 0c */
+ public uint FileTableSize; /* 14 */
+ public uint FileTableSize2; /* 18 */
+ public uint DirectoryCount; /* 1c */
+ public uint FileCount; /* 28 */
+ public uint FileTableOffset2; /* 2c */
+
+ public uint[] FileGroupOffsets = new uint[Constants.MAX_FILE_GROUP_COUNT]; /* 0x3e */
+ public uint[] ComponentOffsets = new uint[Constants.MAX_COMPONENT_COUNT]; /* 0x15a */
+ }
+}
diff --git a/DICUI/External/Unshield/CommonHeader.cs b/DICUI/External/Unshield/CommonHeader.cs
new file mode 100644
index 00000000..37cff2c1
--- /dev/null
+++ b/DICUI/External/Unshield/CommonHeader.cs
@@ -0,0 +1,48 @@
+using System;
+
+namespace DICUI.External.Unshield
+{
+ public class CommonHeader
+ {
+ public uint Signature; // 00
+ public uint Version;
+ public uint VolumeInfo;
+ public uint CabDescriptorOffset;
+ public uint CabDescriptorSize; // 10
+
+ ///
+ /// Populate a CommonHeader from an input buffer
+ ///
+ public static bool ReadCommonHeader(ref byte[] buffer, int bufferPointer, CommonHeader common)
+ {
+ common.Signature = BitConverter.ToUInt32(buffer, bufferPointer); bufferPointer += 4;
+
+ if (common.Signature != Constants.CAB_SIGNATURE)
+ {
+ // unshield_error("Invalid file signature");
+
+ if (common.Signature == Constants.MSCF_SIGNATURE)
+ {
+ // unshield_warning("Found Microsoft Cabinet header. Use cabextract (http://www.kyz.uklinux.net/cabextract.php) to unpack this file.");
+ }
+
+ return false;
+ }
+
+ common.Version = BitConverter.ToUInt32(buffer, bufferPointer); bufferPointer += 4;
+ common.VolumeInfo = BitConverter.ToUInt32(buffer, bufferPointer); bufferPointer += 4;
+ common.CabDescriptorOffset = BitConverter.ToUInt32(buffer, bufferPointer); bufferPointer += 4;
+ common.CabDescriptorSize = BitConverter.ToUInt32(buffer, bufferPointer); bufferPointer += 4;
+
+ /*
+ unshield_trace("Common header: %08x %08x %08x %08x",
+ common->version,
+ ommon->volume_info,
+ common->cab_descriptor_offset,
+ common->cab_descriptor_size);
+ */
+
+ return true;
+ }
+ }
+}
diff --git a/DICUI/External/Unshield/FileDescriptor.cs b/DICUI/External/Unshield/FileDescriptor.cs
new file mode 100644
index 00000000..463da986
--- /dev/null
+++ b/DICUI/External/Unshield/FileDescriptor.cs
@@ -0,0 +1,17 @@
+namespace DICUI.External.Unshield
+{
+ public class FileDescriptor
+ {
+ public uint NameOffset;
+ public uint DirectoryIndex;
+ public ushort Flags;
+ public uint ExpandedSize;
+ public uint CompressedSize;
+ public uint DataOffset;
+ public byte[] Md5 = new byte[16];
+ public ushort Volume;
+ public uint LinkPrevious;
+ public uint LinkNext;
+ public byte LinkFlags;
+ }
+}
diff --git a/DICUI/External/Unshield/Header.cs b/DICUI/External/Unshield/Header.cs
new file mode 100644
index 00000000..1cebc9a1
--- /dev/null
+++ b/DICUI/External/Unshield/Header.cs
@@ -0,0 +1,240 @@
+using System;
+
+namespace DICUI.External.Unshield
+{
+ public class Header
+ {
+ public Header Next;
+ public int Index;
+ public byte[] Data;
+ public int DataPointer = 0;
+ public long Size;
+ public int MajorVersion;
+
+ // Shortcuts
+ public CommonHeader Common = new CommonHeader();
+ public CabDescriptor Cab = new CabDescriptor();
+ public uint[] FileTable;
+ public int FileTablePointer;
+ public FileDescriptor[] FileDescriptors;
+ public int FileDescriptorsPointer;
+
+ public int ComponentCount;
+ public UnshieldComponent[] Components;
+ public int ComponentsPointer;
+
+ public int FileGroupCount;
+ public UnshieldFileGroup[] FileGroups;
+ public int FileGroupsCounter;
+
+ public StringBuffer StringBuffer = new StringBuffer();
+
+ ///
+ /// Add a new StringBuffer to the existing list
+ ///
+ public StringBuffer AddStringBuffer()
+ {
+ StringBuffer result = new StringBuffer();
+ result.Next = this.StringBuffer;
+ this.StringBuffer = result;
+ return result;
+ }
+
+ ///
+ /// Populate the CabDescriptor from header data
+ ///
+ public bool GetCabDescriptor()
+ {
+ if (this.Common.CabDescriptorSize > 0)
+ {
+ int p = (int)(this.Common.CabDescriptorOffset);
+
+ p += 0xc;
+ this.Cab.FileTableOffset = BitConverter.ToUInt32(this.Data, p); p += 4;
+ p += 4;
+ this.Cab.FileTableSize = BitConverter.ToUInt32(this.Data, p); p += 4;
+ this.Cab.FileTableSize2 = BitConverter.ToUInt32(this.Data, p); p += 4;
+ this.Cab.DirectoryCount = BitConverter.ToUInt32(this.Data, p); p += 4;
+ p += 8;
+ this.Cab.FileCount = BitConverter.ToUInt32(this.Data, p); p += 4;
+ this.Cab.FileTableOffset2 = BitConverter.ToUInt32(this.Data, p); p += 4;
+
+ // assert((p - (header->data + header->common.cab_descriptor_offset)) == 0x30);
+
+ if (this.Cab.FileTableSize != this.Cab.FileTableSize2)
+ {
+ // unshield_warning("File table sizes do not match");
+ }
+
+ /*
+ unshield_trace("Cabinet descriptor: %08x %08x %08x %08x",
+ header->cab.file_table_offset,
+ header->cab.file_table_size,
+ header->cab.file_table_size2,
+ header->cab.file_table_offset2
+ );
+
+ unshield_trace("Directory count: %i", header->cab.directory_count);
+ unshield_trace("File count: %i", header->cab.file_count);
+ */
+
+ p += 0xe;
+
+ for (int i = 0; i < Constants.MAX_FILE_GROUP_COUNT; i++)
+ {
+ this.Cab.FileGroupOffsets[i] = BitConverter.ToUInt32(this.Data, p); p += 4;
+ }
+
+ for (int i = 0; i < Constants.MAX_COMPONENT_COUNT; i++)
+ {
+ this.Cab.ComponentOffsets[i] = this.Cab.FileGroupOffsets[i] = BitConverter.ToUInt32(this.Data, p); p += 4;
+ }
+
+ return true;
+ }
+ else
+ {
+ // unshield_error("No CAB descriptor available!");
+ return false;
+ }
+ }
+
+ ///
+ /// Populate the CommonHeader from header data
+ ///
+ public bool GetCommmonHeader()
+ {
+ return CommonHeader.ReadCommonHeader(ref this.Data, this.DataPointer, this.Common);
+ }
+
+ ///
+ /// Populate the component list from header data
+ ///
+ public bool GetComponents()
+ {
+ int count = 0;
+ int available = 16;
+
+ this.Components = new UnshieldComponent[available];
+
+ for (int i = 0; i < Constants.MAX_COMPONENT_COUNT; i++)
+ {
+ if (this.Cab.ComponentOffsets[i] > 0)
+ {
+ OffsetList list = new OffsetList();
+
+ list.NextOffset = this.Cab.ComponentOffsets[i];
+
+ while (list.NextOffset > 0)
+ {
+ int p = GetDataOffset(list.NextOffset);
+
+ list.NameOffset = BitConverter.ToUInt32(this.Data, p); p += 4;
+ list.DescriptorOffset = BitConverter.ToUInt32(this.Data, p); p += 4;
+ list.NextOffset = BitConverter.ToUInt32(this.Data, p); p += 4;
+
+ if (count == available)
+ {
+ available <<= 1;
+ Array.Resize(ref this.Components, available);
+ }
+
+ this.Components[count++] = UnshieldComponent.Create(this, list.DescriptorOffset);
+ }
+ }
+ }
+
+ this.ComponentCount = count;
+
+ return true;
+ }
+
+ ///
+ /// Get the real data offset
+ ///
+ public int GetDataOffset(uint offset)
+ {
+ if (offset > 0)
+ return (int)(this.Common.CabDescriptorOffset + offset);
+ else
+ return -1;
+ }
+
+ ///
+ /// Populate the file group list from header data
+ ///
+ public bool GetFileGroups()
+ {
+ int count = 0;
+ int available = 16;
+
+ this.FileGroups = new UnshieldFileGroup[available];
+
+ for (int i = 0; i < Constants.MAX_FILE_GROUP_COUNT; i++)
+ {
+ if (this.Cab.FileGroupOffsets[i] > 0)
+ {
+ OffsetList list = new OffsetList();
+
+ list.NextOffset = this.Cab.FileGroupOffsets[i];
+
+ while (list.NextOffset > 0)
+ {
+ int p = GetDataOffset(list.NextOffset);
+
+ list.NameOffset = BitConverter.ToUInt32(this.Data, p); p += 4;
+ list.DescriptorOffset = BitConverter.ToUInt32(this.Data, p); p += 4;
+ list.NextOffset = BitConverter.ToUInt32(this.Data, p); p += 4;
+
+ if (count == available)
+ {
+ available <<= 1;
+ Array.Resize(ref this.FileGroups, available);
+ }
+
+ this.FileGroups[count++] = UnshieldFileGroup.Create(this, list.DescriptorOffset);
+ }
+ }
+ }
+
+ this.FileGroupCount = count;
+
+ return true;
+ }
+
+ ///
+ /// Populate the file table from header data
+ ///
+ public bool GetFileTable()
+ {
+ int p = (int)(this.Common.CabDescriptorOffset +
+ this.Cab.FileTableOffset);
+ int count = (int)(this.Cab.DirectoryCount + this.Cab.FileCount);
+
+ this.FileTable = new uint[count];
+
+ for (int i = 0; i < count; i++)
+ {
+ this.FileTable[i] = BitConverter.ToUInt32(this.Data, p); p += 4;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Get the UInt32 at the given offset in the header data as a string
+ ///
+ public string GetString(uint offset)
+ {
+ return GetUTF8String(this.Data, GetDataOffset(offset));
+ }
+
+ ///
+ /// Convert a UInt32 read from a buffer to a string
+ ///
+ public string GetUTF8String(byte[] buffer, int bufferPointer)
+ {
+ return BitConverter.ToUInt32(buffer, bufferPointer).ToString("X8");
+ }
+ }
+}
diff --git a/DICUI/External/Unshield/LICENSE b/DICUI/External/Unshield/LICENSE
new file mode 100644
index 00000000..e23e0c08
--- /dev/null
+++ b/DICUI/External/Unshield/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2003 David Eriksson
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Addendum:
+
+The code in this part of the project has been adapted from the original source
+by Matt Nadareski for DICUI.
\ No newline at end of file
diff --git a/DICUI/External/Unshield/OffsetList.cs b/DICUI/External/Unshield/OffsetList.cs
new file mode 100644
index 00000000..8d68cc11
--- /dev/null
+++ b/DICUI/External/Unshield/OffsetList.cs
@@ -0,0 +1,9 @@
+namespace DICUI.External.Unshield
+{
+ public class OffsetList
+ {
+ public uint NameOffset;
+ public uint DescriptorOffset;
+ public uint NextOffset;
+ }
+}
diff --git a/DICUI/External/Unshield/StringBuffer.cs b/DICUI/External/Unshield/StringBuffer.cs
new file mode 100644
index 00000000..630d0555
--- /dev/null
+++ b/DICUI/External/Unshield/StringBuffer.cs
@@ -0,0 +1,8 @@
+namespace DICUI.External.Unshield
+{
+ public class StringBuffer
+ {
+ public StringBuffer Next;
+ public string String;
+ }
+}
diff --git a/DICUI/External/Unshield/UnshieldCabinet.cs b/DICUI/External/Unshield/UnshieldCabinet.cs
new file mode 100644
index 00000000..151b8a53
--- /dev/null
+++ b/DICUI/External/Unshield/UnshieldCabinet.cs
@@ -0,0 +1,1180 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text.RegularExpressions;
+using zlib;
+
+namespace DICUI.External.Unshield
+{
+ public static class Constants
+ {
+ #region cabfile.h
+
+ public const int OFFSET_COUNT = 0x47;
+ public const int CAB_SIGNATURE = 0x28635349;
+
+ public const int MSCF_SIGNATURE = 0x4643534d;
+
+ public const int COMMON_HEADER_SIZE = 20;
+ public const int VOLUME_HEADER_SIZE_V5 = 40;
+ public const int VOLUME_HEADER_SIZE_V6 = 64;
+
+ public const int MAX_FILE_GROUP_COUNT = 71;
+ public const int MAX_COMPONENT_COUNT = 71;
+
+ public const int FILE_SPLIT = 1;
+ public const int FILE_OBFUSCATED = 2;
+ public const int FILE_COMPRESSED = 4;
+ public const int FILE_INVALID = 8;
+
+ public const int LINK_NONE = 0;
+ public const int LINK_PREV = 1;
+ public const int LINK_NEXT = 2;
+ public const int LINK_BOTH = 3;
+
+ #endregion
+
+ #region file.c
+
+ public const int BUFFER_SIZE = 64 * 1024;
+
+ #endregion
+
+ #region internal.h
+
+ public const string HEADER_SUFFIX = "hdr";
+ public const string CABINET_SUFFIX = "cab";
+
+ #endregion
+
+ #region libunshield.h
+
+ public const int UNSHIELD_LOG_LEVEL_LOWEST = 0;
+
+ public const int UNSHIELD_LOG_LEVEL_ERROR = 1;
+ public const int UNSHIELD_LOG_LEVEL_WARNING = 2;
+ public const int UNSHIELD_LOG_LEVEL_TRACE = 3;
+
+ public const int UNSHIELD_LOG_LEVEL_HIGHEST = 4;
+
+ #endregion
+
+ #region zconf.h
+
+ public const int MAX_WBITS = 15;
+ public const int Z_BLOCK = 5;
+
+ #endregion
+ }
+
+ public class UnshieldCabinet
+ {
+ // Linked CAB headers
+ public Header HeaderList { get; set; }
+
+ // Internal CAB Counts
+ public int ComponentCount { get { return this.HeaderList?.ComponentCount ?? 0; } }
+ public int DirectoryCount { get { return (int)(this.HeaderList?.Cab?.DirectoryCount ?? 0); } } // XXX: multi-volume support...
+ public int FileCount { get { return (int)(this.HeaderList?.Cab?.FileCount ?? 0); } } // XXX: multi-volume support...
+ public int FileGroupCount { get { return this.HeaderList?.FileGroupCount ?? 0; } }
+
+ // Unicode compatibility
+ public bool IsUnicode { get { return (this.HeaderList == null ? false : this.HeaderList?.MajorVersion >= 17); } }
+
+ // Base filename path for related CAB files
+ private string filenamePattern;
+
+ #region Open Cabinet
+
+ ///
+ /// Open a file as an InstallShield CAB
+ ///
+ public static UnshieldCabinet Open(string filename)
+ {
+ return OpenForceVersion(filename, -1);
+ }
+
+ ///
+ /// Open a file as an InstallShield CAB, forcing a version
+ ///
+ public static UnshieldCabinet OpenForceVersion(string filename, int version)
+ {
+ UnshieldCabinet unshield = new UnshieldCabinet();
+ if (!unshield.CreateFilenamePattern(filename))
+ {
+ // unshield_error("Failed to create filename pattern");
+ return null;
+ }
+
+ if (!unshield.ReadHeaders(version))
+ {
+ // unshield_error("Failed to read header files");
+ return null;
+ }
+
+ return unshield;
+ }
+
+ #endregion
+
+ #region Name From Index
+
+ ///
+ /// Get the component name at an index
+ ///
+ public string ComponentName(int index)
+ {
+ if (index >= 0 && index < this.HeaderList.ComponentCount)
+ return this.HeaderList.Components[index].Name;
+ else
+ return null;
+ }
+
+ ///
+ /// Get the directory name at an index
+ ///
+ public string DirectoryName(int index)
+ {
+ if (index >= 0)
+ {
+ // XXX: multi-volume support...
+ Header header = this.HeaderList;
+
+ if (index < (int)header.Cab.DirectoryCount)
+ return header.GetUTF8String(header.Data,
+ (int)(header.Common.CabDescriptorOffset
+ + header.Cab.FileTableOffset
+ + header.FileTable[index]));
+ }
+
+ // unshield_warning("Failed to get directory name %i", index);
+ return null;
+ }
+
+ ///
+ /// Get the file name at an index
+ ///
+ public string FileName(int index)
+ {
+ FileDescriptor fd = this.GetFileDescriptor(index);
+
+ if (fd != null)
+ {
+ // XXX: multi-volume support...
+ Header header = this.HeaderList;
+
+ return header.GetUTF8String(header.Data,
+ (int)(header.Common.CabDescriptorOffset
+ + header.Cab.FileTableOffset
+ + fd.NameOffset));
+ }
+
+ // unshield_warning("Failed to get file descriptor %i", index);
+ return null;
+ }
+
+ ///
+ /// Get the file group name at an index
+ ///
+ public string FileGroupName(int index)
+ {
+ Header header = this.HeaderList;
+
+ if (index >= 0 && index < header.FileGroupCount)
+ return header.FileGroups[index].Name;
+ else
+ return null;
+ }
+
+ #endregion
+
+ #region File
+
+ ///
+ /// Returns if the file at a given index is marked as valid
+ ///
+ public bool FileIsValid(int index)
+ {
+ FileDescriptor fd;
+
+ if (index < 0 || index > this.FileCount)
+ return false;
+
+ if ((fd = this.GetFileDescriptor(index)) == null)
+ return false;
+
+ if ((fd.Flags & Constants.FILE_INVALID) != 0)
+ return false;
+
+ if (fd.NameOffset == default(uint))
+ return false;
+
+ if (fd.DataOffset == default(uint))
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Save the file at the given index to the filename specified
+ ///
+ public bool FileSave(int index, string filename)
+ {
+ FileStream output = null;
+ byte[] inputBuffer = new byte[Constants.BUFFER_SIZE + 1];
+ int inputBufferPointer = 0;
+ byte[] outputBuffer = new byte[Constants.BUFFER_SIZE];
+ int outputBufferPointer = 0;
+ uint bytesLeft;
+ ulong totalWritten = 0;
+ UnshieldReader reader = null;
+ FileDescriptor fileDescriptor;
+ MD5 md5 = MD5.Create();
+
+ md5.Initialize();
+
+ if ((fileDescriptor = this.GetFileDescriptor(index)) == null)
+ {
+ // unshield_error("Failed to get file descriptor for file %i", index);
+ return false;
+ }
+
+ if (((fileDescriptor.Flags & Constants.FILE_INVALID) != 0) || 0 == fileDescriptor.DataOffset)
+ {
+ // invalid file
+ return false;
+ }
+
+ if ((fileDescriptor.LinkFlags & Constants.LINK_PREV) != 0)
+ {
+ return this.FileSave((int)fileDescriptor.LinkPrevious, filename);
+ }
+
+ reader = UnshieldReader.Create(this, index, fileDescriptor);
+ if (reader == null)
+ {
+ // unshield_error("Failed to create data reader for file %i", index);
+ reader?.Dispose();
+ return false;
+ }
+
+ if (reader.VolumeFile.Length == (long)fileDescriptor.DataOffset)
+ {
+ // unshield_error("File %i is not inside the cabinet.", index);
+ reader?.Dispose();
+ return false;
+ }
+
+ if (!String.IsNullOrWhiteSpace(filename))
+ {
+ output = File.OpenWrite(filename);
+ if (output == null)
+ {
+ // unshield_error("Failed to open output file '%s'", filename);
+ reader?.Dispose();
+ output?.Close();
+ return false;
+ }
+ }
+
+ if ((fileDescriptor.Flags & Constants.FILE_COMPRESSED) != 0)
+ bytesLeft = fileDescriptor.CompressedSize;
+ else
+ bytesLeft = fileDescriptor.ExpandedSize;
+
+ // unshield_trace("Bytes to read: %i", bytes_left);
+
+ while (bytesLeft > 0)
+ {
+ ulong bytesToWrite = Constants.BUFFER_SIZE;
+ int result;
+
+ if ((fileDescriptor.Flags & Constants.FILE_COMPRESSED) != 0)
+ {
+ ulong readBytes;
+ byte[] bytesToRead = new byte[sizeof(ushort)];
+ int bytesToReadPointer = 0;
+
+ if (!reader.Read(ref bytesToRead, ref bytesToReadPointer, bytesToRead.Length))
+ {
+ // unshield_error("Failed to read %i bytes of file %i (%s) from input cabinet file %i", izeof(bytes_to_read), index, unshield_file_name(unshield, index), file_descriptor->volume);
+ reader?.Dispose();
+ output?.Close();
+ return false;
+ }
+
+ // bytesToRead = letoh16(bytesToRead); // TODO: No-op?
+ if (BitConverter.ToUInt16(bytesToRead, 0) == 0)
+ {
+ // unshield_error("bytes_to_read can't be zero");
+ // unshield_error("HINT: Try unshield_file_save_old() or -O command line parameter!");
+ reader?.Dispose();
+ output?.Close();
+ return false;
+ }
+
+ if (!reader.Read(ref inputBuffer, ref inputBufferPointer, (int)BitConverter.ToUInt16(bytesToRead, 0)))
+ {
+ // unshield_error("Failed to read %i bytes of file %i (%s) from input cabinet file %i", ytes_to_read, index, unshield_file_name(unshield, index), file_descriptor->volume);
+ reader?.Dispose();
+ output?.Close();
+ return false;
+ }
+
+ // add a null byte to make inflate happy
+ inputBuffer[BitConverter.ToUInt16(bytesToRead, 0)] = 0;
+ readBytes = (ulong)(BitConverter.ToUInt16(bytesToRead, 0) + 1);
+ result = Uncompress(ref outputBuffer, ref bytesToWrite, ref inputBuffer, ref readBytes);
+
+ if (result != zlibConst.Z_OK)
+ {
+ // unshield_error("Decompression failed with code %i. bytes_to_read=%i, volume_bytes_left=%i, volume=%i, read_bytes=%i", result, bytes_to_read, reader->volume_bytes_left, file_descriptor->volume, read_bytes)
+ if (result == zlibConst.Z_DATA_ERROR)
+ {
+ // unshield_error("HINT: Try unshield_file_save_old() or -O command line parameter!");
+ }
+ reader?.Dispose();
+ output?.Close();
+ return false;
+ }
+
+ // unshield_trace("read_bytes = %i", read_bytes);
+
+ bytesLeft -= 2;
+ bytesLeft -= BitConverter.ToUInt16(bytesToRead, 0);
+ }
+ else
+ {
+ bytesToWrite = Math.Min(bytesLeft, Constants.BUFFER_SIZE);
+
+ if (!reader.Read(ref outputBuffer, ref outputBufferPointer, (int)bytesToWrite))
+ {
+ // unshield_error("Failed to read %i bytes from input cabinet file %i", bytes_to_write, file_descriptor->volume);
+ reader?.Dispose();
+ output?.Close();
+ return false;
+ }
+
+ bytesLeft -= (uint)bytesToWrite;
+ }
+
+ md5.TransformBlock(outputBuffer, 0, (int)bytesToWrite, outputBuffer, 0);
+
+ if (output != null)
+ {
+ output.Write(outputBuffer, 0, (int)bytesToWrite);
+ }
+
+ totalWritten += bytesToWrite;
+ }
+
+ if (fileDescriptor.ExpandedSize != totalWritten)
+ {
+ // unshield_error("Expanded size expected to be %i, but was %i", file_descriptor->expanded_size, total_written);
+ reader?.Dispose();
+ output?.Close();
+ return false;
+ }
+
+ if (this.HeaderList.MajorVersion >= 6)
+ {
+ md5.TransformFinalBlock(outputBuffer, 0, 0);
+ byte[] md5result = new byte[16];
+ md5result = md5.Hash;
+
+ if (!md5result.SequenceEqual(fileDescriptor.Md5))
+ {
+ // unshield_error("MD5 checksum failure for file %i (%s)", index, unshield_file_name(unshield, index));
+ reader?.Dispose();
+ output?.Close();
+ return false;
+ }
+ }
+
+ reader?.Dispose();
+ output?.Close();
+ return true;
+ }
+
+ ///
+ /// Save the file at the given index to the filename specified (old version)
+ ///
+ public bool FileSaveOld(int index, string filename)
+ {
+ // XXX: Thou Shalt Not Cut & Paste
+ FileStream output = null;
+ long inputBufferSize = Constants.BUFFER_SIZE;
+ byte[] inputBuffer = new byte[Constants.BUFFER_SIZE];
+ int inputBufferPointer = 0;
+ byte[] outputBuffer = new byte[Constants.BUFFER_SIZE];
+ int outputBufferPointer = 0;
+ uint bytesLeft;
+ ulong totalWritten = 0;
+ UnshieldReader reader = null;
+ FileDescriptor fileDescriptor;
+
+ if ((fileDescriptor = this.GetFileDescriptor(index)) == null)
+ {
+ // unshield_error("Failed to get file descriptor for file %i", index);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ if (((fileDescriptor.Flags & Constants.FILE_INVALID) != 0) || fileDescriptor.DataOffset == 0)
+ {
+ // invalid file
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ if ((fileDescriptor.LinkFlags & Constants.LINK_PREV) != 0)
+ {
+ reader?.Dispose();
+ output.Close();
+ return FileSaveRaw((int)fileDescriptor.LinkPrevious, filename);
+ }
+
+ reader = UnshieldReader.Create(this, index, fileDescriptor);
+ if (reader == null)
+ {
+ // unshield_error("Failed to create data reader for file %i", index);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ if (reader.VolumeFile.Length == (long)(fileDescriptor.DataOffset))
+ {
+ // unshield_error("File %i is not inside the cabinet.", index);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ if (!String.IsNullOrWhiteSpace(filename))
+ {
+ output = File.OpenWrite(filename);
+ if (output == null)
+ {
+ // unshield_error("Failed to open output file '%s'", filename);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+ }
+
+ if ((fileDescriptor.Flags & Constants.FILE_COMPRESSED) != 0)
+ bytesLeft = fileDescriptor.CompressedSize;
+ else
+ bytesLeft = fileDescriptor.ExpandedSize;
+
+ // unshield_trace("Bytes to read: %i", bytes_left);
+
+ while (bytesLeft > 0)
+ {
+ ulong bytesToWrite = 0;
+ int result;
+
+ if (reader.VolumeBytesLeft == 0 && !reader.OpenVolume(reader.Volume + 1))
+ {
+ // unshield_error("Failed to open volume %i to read %i more bytes", reader->volume + 1, bytes_left);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ if ((fileDescriptor.Flags & Constants.FILE_COMPRESSED) != 0)
+ {
+ byte[] END_OF_CHUNK = { 0x00, 0x00, 0xff, 0xff };
+ int eocPointer = 0;
+ ulong readBytes;
+ long inputSize = reader.VolumeBytesLeft;
+ byte[] chunkBuffer;
+ int chunkBufferPointer;
+
+ while (inputSize > inputBufferSize)
+ {
+ inputBufferSize *= 2;
+ // unshield_trace("increased input_buffer_size to 0x%x", input_buffer_size);
+
+ Array.Resize(ref inputBuffer, (int)inputBufferSize);
+ // assert(input_buffer)
+ }
+
+ if (!reader.Read(ref inputBuffer, ref inputBufferPointer, (int)inputSize))
+ {
+ // unshield_error("Failed to read 0x%x bytes of file %i (%s) from input cabinet file %i", input_size, index, unshield_file_name(unshield, index), file_descriptor->volume);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ bytesLeft -= (uint)inputSize;
+
+ chunkBuffer = inputBuffer;
+ for (chunkBufferPointer = inputBufferPointer; inputSize > 0;)
+ {
+ long chunkSize;
+ int match = FindBytes(ref chunkBuffer, ref chunkBufferPointer, inputSize, ref END_OF_CHUNK, ref eocPointer, END_OF_CHUNK.Length);
+ if (match == -1)
+ {
+ // unshield_error("Could not find end of chunk for file %i (%s) from input cabinet file %i", index, unshield_file_name(unshield, index), file_descriptor->volume);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ chunkSize = match - chunkBufferPointer;
+
+ /*
+ Detect when the chunk actually contains the end of chunk marker.
+
+ Needed by Qtime.smk from "The Feeble Files - spanish version".
+
+ The first bit of a compressed block is always zero, so we apply this
+ workaround if it's a one.
+
+ A possibly more proper fix for this would be to have
+ unshield_uncompress_old eat compressed data and discard chunk
+ markers inbetween.
+ */
+ while ((chunkSize + END_OF_CHUNK.Length) < inputSize &&
+ (chunkBuffer[chunkSize + END_OF_CHUNK.Length] & 1) != 0)
+ {
+ // unshield_warning("It seems like we have an end of chunk marker inside of a chunk.");
+ chunkSize += END_OF_CHUNK.Length;
+ int tempChunkPointer = (int)(chunkBufferPointer + chunkSize);
+ match = FindBytes(ref chunkBuffer, ref tempChunkPointer, inputSize - chunkSize, ref END_OF_CHUNK, ref eocPointer, END_OF_CHUNK.Length);
+ if (match == -1)
+ {
+ // unshield_error("Could not find end of chunk for file %i (%s) from input cabinet file %i", index, unshield_file_name(unshield, index), file_descriptor->volume);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+ chunkSize = match - chunkBufferPointer;
+ }
+
+ // unshield_trace("chunk_size = 0x%x", chunk_size);
+
+ // add a null byte to make inflate happy
+ chunkBuffer[chunkSize] = 0;
+
+ bytesToWrite = Constants.BUFFER_SIZE;
+ readBytes = (ulong)chunkSize;
+ result = UncompressOld(ref outputBuffer, ref bytesToWrite, ref chunkBuffer, ref readBytes);
+
+ if (result != zlibConst.Z_OK)
+ {
+ // unshield_error("Decompression failed with code %i. input_size=%i, volume_bytes_left=%i, volume=%i, read_bytes=%i", result, input_size, reader->volume_bytes_left, file_descriptor->volume, read_bytes);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ // unshield_trace("read_bytes = 0x%x", read_bytes);
+
+ chunkBufferPointer += (int)chunkSize;
+ chunkBufferPointer += END_OF_CHUNK.Length;
+
+ inputSize -= chunkSize;
+ inputSize -= END_OF_CHUNK.Length;
+
+ if (output != null)
+ {
+ output.Write(outputBuffer, 0, (int)bytesToWrite);
+ }
+
+ totalWritten += bytesToWrite;
+ }
+ }
+ else
+ {
+ bytesToWrite = Math.Min(bytesLeft, Constants.BUFFER_SIZE);
+
+ if (!reader.Read(ref outputBuffer, ref outputBufferPointer, (int)bytesToWrite))
+ {
+ // unshield_error("Failed to read %i bytes from input cabinet file %i", bytes_to_write, file_descriptor->volume);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ bytesLeft -= (uint)bytesToWrite;
+
+ if (output != null)
+ {
+ output.Write(outputBuffer, 0, (int)bytesToWrite);
+ }
+
+ totalWritten += bytesToWrite;
+ }
+ }
+
+ if (fileDescriptor.ExpandedSize != totalWritten)
+ {
+ // unshield_error("Expanded size expected to be %i, but was %i", file_descriptor->expanded_size, total_written);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ reader?.Dispose();
+ output.Close();
+ return true;
+ }
+
+ ///
+ /// Save the file at the given index to the filename specified as raw
+ ///
+ public bool FileSaveRaw(int index, string filename)
+ {
+ // XXX: Thou Shalt Not Cut & Paste
+ FileStream output = null;
+ byte[] inputBuffer = new byte[Constants.BUFFER_SIZE];
+ byte[] outputBuffer = new byte[Constants.BUFFER_SIZE];
+ int outputBufferPointer = 0;
+ uint bytesLeft;
+ UnshieldReader reader = null;
+ FileDescriptor fileDescriptor;
+
+ if ((fileDescriptor = this.GetFileDescriptor(index)) == null)
+ {
+ // unshield_error("Failed to get file descriptor for file %i", index);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ if (((fileDescriptor.Flags & Constants.FILE_INVALID) != 0) || fileDescriptor.DataOffset == 0)
+ {
+ // invalid file
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ if ((fileDescriptor.LinkFlags & Constants.LINK_PREV) != 0)
+ {
+ reader?.Dispose();
+ output.Close();
+ return FileSaveRaw((int)fileDescriptor.LinkPrevious, filename);
+ }
+
+ reader = UnshieldReader.Create(this, index, fileDescriptor);
+ if (reader == null)
+ {
+ // unshield_error("Failed to create data reader for file %i", index);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ if (reader.VolumeFile.Length == (long)(fileDescriptor.DataOffset))
+ {
+ // unshield_error("File %i is not inside the cabinet.", index);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ if (!String.IsNullOrWhiteSpace(filename))
+ {
+ output = File.OpenWrite(filename);
+ if (output == null)
+ {
+ // unshield_error("Failed to open output file '%s'", filename);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+ }
+
+ if ((fileDescriptor.Flags & Constants.FILE_COMPRESSED) != 0)
+ bytesLeft = fileDescriptor.CompressedSize;
+ else
+ bytesLeft = fileDescriptor.ExpandedSize;
+
+ // unshield_trace("Bytes to read: %i", bytes_left);
+
+ while (bytesLeft > 0)
+ {
+ ulong bytesToWrite = Math.Min(bytesLeft, Constants.BUFFER_SIZE);
+
+ if (!reader.Read(ref outputBuffer, ref outputBufferPointer, (int)bytesToWrite))
+ {
+ // unshield_error("Failed to read %i bytes from input cabinet file %i", bytes_to_write, file_descriptor->volume);
+ reader?.Dispose();
+ output.Close();
+ return false;
+ }
+
+ bytesLeft -= (uint)bytesToWrite;
+
+ output.Write(outputBuffer, 0, (int)bytesToWrite);
+ }
+
+ reader?.Dispose();
+ output.Close();
+ return true;
+ }
+
+ ///
+ /// Get the directory index for the given file index
+ ///
+ public int FileDirectory(int index)
+ {
+ FileDescriptor fd = this.GetFileDescriptor(index);
+ if (fd != null)
+ return (int)fd.DirectoryIndex;
+ else
+ return -1;
+ }
+
+ ///
+ /// Get the reported expanded file size for a given index
+ ///
+ public int FileSize(int index)
+ {
+ FileDescriptor fd = this.GetFileDescriptor(index);
+ if (fd != null)
+ return (int)fd.ExpandedSize;
+ else
+ return 0;
+ }
+
+ #endregion
+
+ #region File Group
+
+ ///
+ /// Retrieve a file group based on index
+ ///
+ public UnshieldFileGroup FileGroupGet(int index)
+ {
+ Header header = this.HeaderList;
+
+ if (index >= 0 && index < header.FileGroupCount)
+ return header.FileGroups[index];
+ else
+ return null;
+ }
+
+ ///
+ /// Retrieve a file group based on name
+ ///
+ public UnshieldFileGroup FileGroupFind(string name)
+ {
+ Header header = this.HeaderList;
+
+ for (int i = 0; i < header.FileGroupCount; i++)
+ {
+ if (header.FileGroups[i].Name == name)
+ return header.FileGroups[i];
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ #region Uncompression
+
+ ///
+ /// Uncompress a source byte array to a destination
+ ///
+ public static int Uncompress(ref byte[] dest, ref ulong destLen, ref byte[] source, ref ulong sourceLen)
+ {
+ ZStream stream = new ZStream();
+ int err;
+
+ stream.next_in = source;
+ stream.avail_in = (int)sourceLen;
+
+ stream.next_out = dest;
+ stream.avail_out = (int)destLen;
+
+ //stream.zalloc = (alloc_func)0;
+ //stream.zfree = (free_func)0;
+
+ // make second parameter negative to disable checksum verification
+ err = stream.inflateInit(-Constants.MAX_WBITS);
+ if (err != zlibConst.Z_OK) return err;
+
+ err = stream.inflate(zlibConst.Z_FINISH);
+ if (err != zlibConst.Z_STREAM_END)
+ {
+ stream.inflateEnd();
+ return err;
+ }
+
+ destLen = (ulong)stream.total_out;
+ sourceLen = (ulong)stream.total_in;
+
+ err = stream.inflateEnd();
+ return err;
+ }
+
+ ///
+ /// Uncompress a source byte array to a destination (old version)
+ ///
+ public static int UncompressOld(ref byte[] dest, ref ulong destLen, ref byte[] source, ref ulong sourceLen)
+ {
+ ZStream stream = new ZStream();
+ int err;
+
+ stream.next_in = source;
+ stream.avail_in = (int)sourceLen;
+
+ stream.next_out = dest;
+ stream.avail_out = (int)destLen;
+
+ //stream.zalloc = (alloc_func)0;
+ //stream.zfree = (free_func)0;
+
+ destLen = 0;
+ sourceLen = 0;
+
+ // make second parameter negative to disable checksum verification
+ err = stream.inflateInit(-Constants.MAX_WBITS);
+ if (err != zlibConst.Z_OK)
+ return err;
+
+ while (stream.avail_in > 1)
+ {
+ err = stream.inflate(Constants.Z_BLOCK);
+ if (err != zlibConst.Z_OK)
+ {
+ stream.inflateEnd();
+ return err;
+ }
+ }
+
+ destLen = (ulong)stream.total_out;
+ sourceLen = (ulong)stream.total_in;
+
+ err = stream.inflateEnd();
+ return err;
+ }
+
+ #endregion
+
+ #region Helpers
+
+ ///
+ /// Open a cabinet file for reading
+ ///
+ public FileStream OpenFileForReading(int index, string suffix)
+ {
+ if (!String.IsNullOrWhiteSpace(this.filenamePattern))
+ {
+ string filename = this.filenamePattern + index + "." + suffix;
+ if (File.Exists(filename))
+ return File.OpenRead(filename);
+ return null;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Get the start index of a pattern in a byte array
+ ///
+ private int FindBytes(ref byte[] buffer, ref int bufferPointer, long bufferSize,
+ ref byte[] pattern, ref int patternPointer, long patternSize)
+ {
+ int p = bufferPointer;
+ long bufferLeft = bufferSize;
+ while((p = Array.IndexOf(buffer, pattern[0], p, (int)bufferLeft)) != -1)
+ {
+ if (patternSize > bufferLeft)
+ break;
+
+ if (BitConverter.ToString(buffer, p, (int)patternSize) != BitConverter.ToString(pattern, patternPointer, (int)patternSize))
+ return p;
+
+ ++p;
+ --bufferLeft;
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Create the generic filename pattern to look for from the input filename
+ ///
+ private bool CreateFilenamePattern(string filename)
+ {
+ if (!String.IsNullOrWhiteSpace(filename))
+ {
+ this.filenamePattern = Path.Combine(
+ Path.GetDirectoryName(filename),
+ Path.GetFileNameWithoutExtension(filename));
+ this.filenamePattern = new Regex(@"\d+$").Replace(this.filenamePattern, string.Empty);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Get the file descriptor at an index
+ ///
+ private FileDescriptor GetFileDescriptor(int index)
+ {
+ // XXX: multi-volume support...
+ Header header = this.HeaderList;
+
+ if (index < 0 || index >= (int)header.Cab.FileCount)
+ {
+ // unshield_error("Invalid index");
+ return null;
+ }
+
+ if (header.FileDescriptors == null)
+ header.FileDescriptors = new FileDescriptor[header.Cab.FileCount];
+
+ if (header.FileDescriptors[index] == null)
+ header.FileDescriptors[index] = this.ReadFileDescriptor(index);
+
+ return header.FileDescriptors[index];
+ }
+
+ ///
+ /// Read the file descriptor from the header data based on an index
+ ///
+ private FileDescriptor ReadFileDescriptor(int index)
+ {
+ // XXX: multi-volume support...
+ Header header = this.HeaderList;
+ byte[] p = null;
+ int pPointer = 0;
+ byte[] savedP = null;
+ int savedPPointer = 0;
+ FileDescriptor fd = new FileDescriptor();
+
+ switch (header.MajorVersion)
+ {
+ case 0:
+ case 5:
+ savedP = p = header.Data;
+ savedPPointer = pPointer = (int)(header.Common.CabDescriptorOffset
+ + header.Cab.FileTableOffset
+ + header.FileTable[header.Cab.DirectoryCount + index]);
+
+ // unshield_trace("File descriptor offset %i: %08x", index, p - header->data);
+
+ fd.Volume = (ushort)header.Index;
+
+ fd.NameOffset = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+ fd.DirectoryIndex = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+
+ fd.Flags = BitConverter.ToUInt16(p, pPointer); pPointer += 2;
+
+ fd.ExpandedSize = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+ fd.CompressedSize = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+ pPointer += 0x14;
+ fd.DataOffset = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+
+ /*
+ unshield_trace("Name offset: %08x", fd->name_offset);
+ unshield_trace("Directory index: %08x", fd->directory_index);
+ unshield_trace("Flags: %04x", fd->flags);
+ unshield_trace("Expanded size: %08x", fd->expanded_size);
+ unshield_trace("Compressed size: %08x", fd->compressed_size);
+ unshield_trace("Data offset: %08x", fd->data_offset);
+ */
+
+ if (header.MajorVersion == 5)
+ {
+ Array.Copy(p, pPointer, fd.Md5, 0, 0x10);
+ // assert((p - saved_p) == 0x3a);
+ }
+
+ break;
+
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ default:
+ savedP = p = header.Data;
+ savedPPointer = pPointer = (int)(header.Common.CabDescriptorOffset
+ + header.Cab.FileTableOffset
+ + header.Cab.FileTableOffset2
+ + index * 0x57);
+
+ // unshield_trace("File descriptor offset: %08x", p - header->data);
+
+ fd.Flags = BitConverter.ToUInt16(p, pPointer); pPointer += 2;
+ fd.ExpandedSize = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+ pPointer += 4;
+ fd.CompressedSize = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+ pPointer += 4;
+ fd.DataOffset = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+ pPointer += 4;
+ Array.Copy(p, pPointer, fd.Md5, 0, 0x10); pPointer += 0x10;
+ pPointer += 0x10;
+ fd.NameOffset = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+ fd.DirectoryIndex = BitConverter.ToUInt16(p, pPointer); pPointer += 2;
+
+ // assert((p - saved_p) == 0x40);
+
+ pPointer += 0xc;
+ fd.LinkPrevious = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+ fd.LinkNext = BitConverter.ToUInt32(p, pPointer); pPointer += 4;
+ fd.LinkFlags = p[pPointer]; pPointer++;
+
+ /*
+ if (fd->link_flags != LINK_NONE)
+ {
+ unshield_trace("Link: previous=%i, next=%i, flags=%i",
+ fd->link_previous, fd->link_next, fd->link_flags);
+ }
+ */
+
+ fd.Volume = BitConverter.ToUInt16(p, pPointer); pPointer += 2;
+
+ // assert((p - saved_p) == 0x57);
+ break;
+ }
+
+ if ((fd.Flags & Constants.FILE_COMPRESSED) == 0
+ && fd.CompressedSize != fd.ExpandedSize)
+ {
+ // unshield_warning("File is not compressed but compressed size is %08x and expanded size is %08x",
+ // fd->compressed_size, fd->expanded_size);
+ }
+
+ return fd;
+ }
+
+ ///
+ /// Read headers from the current file, optionally with a given version
+ ///
+ private bool ReadHeaders(int version)
+ {
+ bool iterate = true;
+ Header previous = null;
+
+ if (this.HeaderList != null)
+ {
+ // unshield_warning("Already have a header list");
+ return true;
+ }
+
+ for (int i = 1; iterate; i++)
+ {
+ FileStream file = OpenFileForReading(i, Constants.HEADER_SUFFIX);
+
+ if (file != null)
+ {
+ // unshield_trace("Reading header from .hdr file %i.", i);
+ iterate = false;
+ }
+ else
+ {
+ // unshield_trace("Could not open .hdr file %i. Reading header from .cab file %i instead.", i, i);
+ file = OpenFileForReading(i, Constants.CABINET_SUFFIX);
+ }
+
+ if (file != null)
+ {
+ long bytesRead;
+ Header header = new Header();
+ header.Index = i;
+
+ header.Size = file.Length;
+ if (header.Size < 4)
+ {
+ // unshield_error("Header file %i too small", i);
+ break;
+ }
+
+ header.Data = new byte[header.Size];
+ bytesRead = file.Read(header.Data, 0, (int)header.Size);
+ file.Close();
+
+ if (bytesRead != header.Size)
+ {
+ // unshield_error("Failed to read from header file %i. Expected = %i, read = %i", i, header->size, bytes_read);
+ break;
+ }
+
+ if (!header.GetCommmonHeader())
+ {
+ // unshield_error("Failed to read common header from header file %i", i);
+ break;
+ }
+
+ if (version != -1)
+ {
+ header.MajorVersion = version;
+ }
+ else if ((header.Common.Version >> 24) == 1)
+ {
+ header.MajorVersion = (int)((header.Common.Version >> 12) & 0xf);
+ }
+ else if ((header.Common.Version >> 24) == 2
+ || (header.Common.Version >> 24) == 4)
+ {
+ header.MajorVersion = (int)(header.Common.Version & 0xffff);
+ if (header.MajorVersion != 0)
+ header.MajorVersion = header.MajorVersion / 100;
+ }
+
+ /*
+ if (header.MajorVersion < 5)
+ header.MajorVersion = 5;
+
+ unshield_trace("Version 0x%08x handled as major version %i",
+ header->common.version,
+ header->major_version);
+ */
+
+ if (!header.GetCabDescriptor())
+ {
+ // unshield_error("Failed to read CAB descriptor from header file %i", i);
+ break;
+ }
+
+ if (!header.GetFileTable())
+ {
+ // unshield_error("Failed to read file table from header file %i", i);
+ break;
+ }
+
+ if (!header.GetComponents())
+ {
+ // unshield_error("Failed to read components from header file %i", i);
+ break;
+ }
+
+ if (!header.GetFileGroups())
+ {
+ // unshield_error("Failed to read file groups from header file %i", i);
+ break;
+ }
+
+ if (previous != null)
+ previous.Next = header;
+ else
+ previous = this.HeaderList = header;
+ }
+ else
+ break;
+ }
+
+ return (this.HeaderList != null);
+ }
+
+ #endregion
+ }
+}
diff --git a/DICUI/External/Unshield/UnshieldComponent.cs b/DICUI/External/Unshield/UnshieldComponent.cs
new file mode 100644
index 00000000..be0815e1
--- /dev/null
+++ b/DICUI/External/Unshield/UnshieldComponent.cs
@@ -0,0 +1,61 @@
+using System;
+
+namespace DICUI.External.Unshield
+{
+ public class UnshieldComponent
+ {
+ public string Name;
+ public uint FileGroupCount;
+ public string[] FileGroupNames;
+ public int FileGroupNamesPointer = 0;
+
+ ///
+ /// Create a new UnshieldComponent from a header and data offset
+ ///
+ public static UnshieldComponent Create(Header header, uint offset)
+ {
+ UnshieldComponent self = new UnshieldComponent();
+ int bufferPointer = header.GetDataOffset(offset);
+ uint fileGroupTableOffset;
+
+ self.Name = header.GetString((uint)bufferPointer); bufferPointer += 4;
+
+ switch (header.MajorVersion)
+ {
+ case 0:
+ case 5:
+ bufferPointer += 0x6c;
+ break;
+
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ default:
+ bufferPointer += 0x6b;
+ break;
+ }
+
+ self.FileGroupCount = BitConverter.ToUInt16(header.Data, bufferPointer); bufferPointer += 2;
+ if (self.FileGroupCount > Constants.MAX_FILE_GROUP_COUNT)
+ return default(UnshieldComponent);
+
+ self.FileGroupNames = new string[self.FileGroupCount];
+
+ fileGroupTableOffset = BitConverter.ToUInt32(header.Data, bufferPointer); bufferPointer += 4;
+
+ bufferPointer = header.GetDataOffset(fileGroupTableOffset);
+
+ for (int i = 0; i < self.FileGroupCount; i++)
+ {
+ self.FileGroupNames[i] = header.GetString((uint)bufferPointer); bufferPointer += 4; // TODO: Verify GetString
+ }
+
+ return self;
+ }
+ }
+}
diff --git a/DICUI/External/Unshield/UnshieldFileGroup.cs b/DICUI/External/Unshield/UnshieldFileGroup.cs
new file mode 100644
index 00000000..d5e2ced5
--- /dev/null
+++ b/DICUI/External/Unshield/UnshieldFileGroup.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace DICUI.External.Unshield
+{
+ public class UnshieldFileGroup
+ {
+ public string Name;
+ public uint FirstFile;
+ public uint LastFile;
+
+ ///
+ /// Create a new UnshieldFileGroup from a header and data offset
+ ///
+ public static UnshieldFileGroup Create(Header header, uint offset)
+ {
+ UnshieldFileGroup self = new UnshieldFileGroup();
+ int pPointer = header.GetDataOffset(offset);
+
+ // unshield_trace("File group descriptor offset: %08x", offset);
+
+ self.Name = header.GetString(BitConverter.ToUInt32(header.Data, pPointer)); pPointer += 4;
+
+ if (header.MajorVersion <= 5)
+ pPointer += 0x48;
+ else
+ pPointer += 0x12;
+
+ self.FirstFile = BitConverter.ToUInt32(header.Data, pPointer); pPointer += 4;
+ self.LastFile = BitConverter.ToUInt32(header.Data, pPointer); pPointer += 4;
+
+ // unshield_trace("File group %08x first file = %i, last file = %i", offset, self->first_file, self->last_file);
+
+ return self;
+ }
+ }
+}
diff --git a/DICUI/External/Unshield/UnshieldReader.cs b/DICUI/External/Unshield/UnshieldReader.cs
new file mode 100644
index 00000000..dddd5501
--- /dev/null
+++ b/DICUI/External/Unshield/UnshieldReader.cs
@@ -0,0 +1,327 @@
+using System;
+using System.IO;
+
+namespace DICUI.External.Unshield
+{
+ public class UnshieldReader
+ {
+ public UnshieldCabinet Unshield;
+ public uint Index;
+ public FileDescriptor FileDescriptor;
+ public int Volume;
+ public FileStream VolumeFile;
+ public VolumeHeader VolumeHeader;
+ public uint VolumeBytesLeft;
+ public uint ObfuscationOffset;
+
+ ///
+ /// Create a new UnshieldReader from an existing cabinet, index, and file descriptor
+ ///
+ public static UnshieldReader Create(UnshieldCabinet unshield, int index, FileDescriptor fileDescriptor)
+ {
+ UnshieldReader reader = new UnshieldReader();
+ if (reader == null)
+ return null;
+
+ reader.Unshield = unshield;
+ reader.Index = (uint)index;
+ reader.FileDescriptor = fileDescriptor;
+
+ for (; ; )
+ {
+ if (!reader.OpenVolume(fileDescriptor.Volume))
+ {
+ // unshield_error("Failed to open volume %i", file_descriptor->volume);
+ return null;
+ }
+
+ // Start with the correct volume for IS5 cabinets
+ if (reader.Unshield.HeaderList.MajorVersion <= 5 &&
+ index > (int)reader.VolumeHeader.LastFileIndex)
+ {
+ // unshield_trace("Trying next volume...");
+ fileDescriptor.Volume++;
+ continue;
+ }
+
+ break;
+ }
+
+ return reader;
+ }
+
+ ///
+ /// Dispose of the current object
+ ///
+ public void Dispose()
+ {
+ VolumeFile?.Close();
+ }
+
+ ///
+ /// Open the volume at the inputted index
+ ///
+ public bool OpenVolume(int volume)
+ {
+ bool success = false;
+ uint dataOffset = 0;
+ uint volumeBytesLeftCompressed;
+ uint volumeBytesLeftExpanded;
+ CommonHeader commonHeader = new CommonHeader();
+
+ // unshield_trace("Open volume %i", volume);
+
+ this.VolumeFile?.Close();
+
+ this.VolumeFile = this.Unshield.OpenFileForReading(volume, Constants.CABINET_SUFFIX);
+ if (this.VolumeFile == null)
+ {
+ // unshield_error("Failed to open input cabinet file %i", volume);
+ return success;
+ }
+
+ {
+ byte[] tmp = new byte[Constants.COMMON_HEADER_SIZE];
+ int p = 0;
+
+ if (Constants.COMMON_HEADER_SIZE !=
+ this.VolumeFile.Read(tmp, 0, Constants.COMMON_HEADER_SIZE))
+ return success;
+
+ if (!CommonHeader.ReadCommonHeader(ref tmp, p, commonHeader))
+ return success;
+ }
+
+ this.VolumeHeader = new VolumeHeader();
+
+ switch (this.Unshield.HeaderList.MajorVersion)
+ {
+ case 0:
+ case 5:
+ {
+ byte[] fiveHeader = new byte[Constants.VOLUME_HEADER_SIZE_V5];
+ int p = 0;
+
+ if (Constants.VOLUME_HEADER_SIZE_V5 !=
+ this.VolumeFile.Read(fiveHeader, 0, Constants.VOLUME_HEADER_SIZE_V5))
+ return success;
+
+ this.VolumeHeader.DataOffset = BitConverter.ToUInt32(fiveHeader, p); p += 4;
+
+ /*
+ if (READ_UINT32(p))
+ unshield_trace("Unknown = %08x", READ_UINT32(p));
+ */
+
+ /* unknown */
+ p += 4;
+ this.VolumeHeader.FirstFileIndex = BitConverter.ToUInt32(fiveHeader, p); p += 4;
+ this.VolumeHeader.LastFileIndex = BitConverter.ToUInt32(fiveHeader, p); p += 4;
+ this.VolumeHeader.FirstFileOffset = BitConverter.ToUInt32(fiveHeader, p); p += 4;
+ this.VolumeHeader.FirstFileSizeExpanded = BitConverter.ToUInt32(fiveHeader, p); p += 4;
+ this.VolumeHeader.FirstFileSizeCompressed = BitConverter.ToUInt32(fiveHeader, p); p += 4;
+ this.VolumeHeader.LastFileOffset = BitConverter.ToUInt32(fiveHeader, p); p += 4;
+ this.VolumeHeader.LastFileSizeExpanded = BitConverter.ToUInt32(fiveHeader, p); p += 4;
+ this.VolumeHeader.LastFileSizeCompressed = BitConverter.ToUInt32(fiveHeader, p); p += 4;
+
+ if (this.VolumeHeader.LastFileOffset == 0)
+ this.VolumeHeader.LastFileOffset = Int32.MaxValue;
+ }
+ break;
+
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ default:
+ {
+ byte[] sixHeader = new byte[Constants.VOLUME_HEADER_SIZE_V6];
+ int p = 0;
+
+ if (Constants.VOLUME_HEADER_SIZE_V6 !=
+ this.VolumeFile.Read(sixHeader, 0, Constants.VOLUME_HEADER_SIZE_V6))
+ return success;
+
+ this.VolumeHeader.DataOffset = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.DataOffsetHigh = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.FirstFileIndex = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.LastFileIndex = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.FirstFileOffset = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.FirstFileOffsetHigh = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.FirstFileSizeExpanded = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.FirstFileSizeExpandedHigh = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.FirstFileSizeCompressed = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.FirstFileSizeCompressedHigh = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.LastFileOffset = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.LastFileOffsetHigh = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.LastFileSizeExpanded = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.LastFileSizeExpandedHigh = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.LastFileSizeCompressed = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ this.VolumeHeader.LastFileSizeCompressedHigh = BitConverter.ToUInt32(sixHeader, p); p += 4;
+ }
+ break;
+ }
+
+ /*
+ unshield_trace("First file index = %i, last file index = %i",
+ reader->volume_header.first_file_index, reader->volume_header.last_file_index);
+ unshield_trace("First file offset = %08x, last file offset = %08x",
+ reader->volume_header.first_file_offset, reader->volume_header.last_file_offset);
+ */
+
+ // enable support for split archives for IS5
+ if (this.Unshield.HeaderList.MajorVersion == 5)
+ {
+ if (this.Index < (this.Unshield.HeaderList.Cab.FileCount - 1) &&
+ this.Index == this.VolumeHeader.LastFileIndex &&
+ this.VolumeHeader.LastFileSizeCompressed != this.FileDescriptor.CompressedSize)
+ {
+ // unshield_trace("IS5 split file last in volume");
+ this.FileDescriptor.Flags |= Constants.FILE_SPLIT;
+ }
+ else if (this.Index > 0 &&
+ this.Index == this.VolumeHeader.FirstFileIndex &&
+ this.VolumeHeader.FirstFileSizeCompressed != this.FileDescriptor.CompressedSize)
+ {
+ // unshield_trace("IS5 split file first in volume");
+ this.FileDescriptor.Flags |= Constants.FILE_SPLIT;
+ }
+ }
+
+ if ((this.FileDescriptor.Flags & Constants.FILE_SPLIT) != 0)
+ {
+ // unshield_trace(/*"Total bytes left = 0x08%x, "*/"previous data offset = 0x08%x", /*total_bytes_left, */ data_offset);
+
+ if (this.Index == this.VolumeHeader.LastFileIndex && this.VolumeHeader.LastFileOffset != 0x7FFFFFFF)
+ {
+ // can be first file too
+ // unshield_trace("Index %i is last file in cabinet file %i", reader->index, volume);
+
+ dataOffset = this.VolumeHeader.LastFileOffset;
+ volumeBytesLeftExpanded = this.VolumeHeader.LastFileSizeExpanded;
+ volumeBytesLeftCompressed = this.VolumeHeader.LastFileSizeCompressed;
+ }
+ else if (this.Index == this.VolumeHeader.FirstFileIndex)
+ {
+ // unshield_trace("Index %i is first file in cabinet file %i", reader->index, volume);
+
+ dataOffset = this.VolumeHeader.FirstFileOffset;
+ volumeBytesLeftExpanded = this.VolumeHeader.FirstFileSizeExpanded;
+ volumeBytesLeftCompressed = this.VolumeHeader.FirstFileSizeCompressed;
+ }
+ else
+ {
+ success = true;
+ return success;
+ }
+
+ // unshield_trace("Will read 0x%08x bytes from offset 0x%08x", volume_bytes_left_compressed, data_offset);
+ }
+ else
+ {
+ dataOffset = this.FileDescriptor.DataOffset;
+ volumeBytesLeftExpanded = this.FileDescriptor.ExpandedSize;
+ volumeBytesLeftCompressed = this.FileDescriptor.CompressedSize;
+ }
+
+ if ((this.FileDescriptor.Flags & Constants.FILE_COMPRESSED) != 0)
+ this.VolumeBytesLeft = volumeBytesLeftCompressed;
+ else
+ this.VolumeBytesLeft = volumeBytesLeftExpanded;
+
+ this.VolumeFile.Seek(dataOffset, SeekOrigin.Begin);
+
+ this.Volume = volume;
+ success = true;
+
+ return success;
+ }
+
+ ///
+ /// Deobfuscate a buffer
+ ///
+ public void Deobfuscate(ref byte[] buffer, ref int bufferPointer, int size)
+ {
+ this.Deobfuscate(ref buffer, ref bufferPointer, size, ref this.ObfuscationOffset);
+ }
+
+ ///
+ /// Read a certain number of bytes from the current volume
+ ///
+ public bool Read(ref byte[] buffer, ref int bufferPointer, int size)
+ {
+ bool success = false;
+ int p = bufferPointer;
+ int bytesLeft = size;
+
+ // unshield_trace("unshield_reader_read start: bytes_left = 0x%x, volume_bytes_left = 0x%x", bytes_left, reader->volume_bytes_left);
+
+ for (; ; )
+ {
+ // Read as much as possible from this volume
+ int bytesToRead = (int)Math.Min(bytesLeft, this.VolumeBytesLeft);
+
+ // unshield_trace("Trying to read 0x%x bytes from offset %08x in volume %i", bytes_to_read, ftell(reader->volume_file), reader->volume);
+ if (bytesToRead == 0)
+ {
+ // unshield_error("bytes_to_read can't be zero");
+ return success;
+ }
+
+ if (bytesToRead != this.VolumeFile.Read(buffer, p, bytesToRead))
+ {
+ // unshield_error("Failed to read 0x%08x bytes of file %i (%s) from volume %i. Current offset = 0x%08x", bytes_to_read, reader->index, unshield_file_name(reader->unshield, reader->index), reader->volume, ftell(reader->volume_file));
+ return success;
+ }
+
+ bytesLeft -= bytesToRead;
+ this.VolumeBytesLeft -= (uint)bytesToRead;
+
+ // unshield_trace("bytes_left = %i, volume_bytes_left = %i", bytes_left, reader->volume_bytes_left);
+
+ if (bytesLeft == 0)
+ break;
+
+ p += bytesToRead;
+
+ // Open next volume
+ if (!this.OpenVolume(this.Volume + 1))
+ {
+ // unshield_error("Failed to open volume %i to read %i more bytes", reader->volume + 1, bytes_to_read);
+ return success;
+ }
+ }
+
+ if ((this.FileDescriptor.Flags & Constants.FILE_OBFUSCATED) != 0)
+ this.Deobfuscate(ref buffer, ref bufferPointer, size);
+
+ success = true;
+ return success;
+ }
+
+ ///
+ /// Deobfuscate a buffer with a seed value
+ ///
+ /// Seed is 0 at file start
+ private void Deobfuscate(ref byte[] buffer, ref int bufferPointer, int size, ref uint seed)
+ {
+ uint tmpSeed = seed;
+
+ for (; size > 0; size--, bufferPointer++, tmpSeed++)
+ {
+ buffer[bufferPointer] = (byte)(ROR8(buffer[bufferPointer] ^ 0xd5, 2) - (tmpSeed % 0x47));
+ }
+
+ seed = tmpSeed;
+ }
+
+ ///
+ /// Rotate Right 8
+ ///
+ private int ROR8(int x, int n) { return (((x) >> ((int)(n))) | ((x) << (8 - (int)(n)))); }
+ }
+}
diff --git a/DICUI/External/Unshield/VolumeHeader.cs b/DICUI/External/Unshield/VolumeHeader.cs
new file mode 100644
index 00000000..525eb281
--- /dev/null
+++ b/DICUI/External/Unshield/VolumeHeader.cs
@@ -0,0 +1,22 @@
+namespace DICUI.External.Unshield
+{
+ public class VolumeHeader
+ {
+ public uint DataOffset;
+ public uint DataOffsetHigh;
+ public uint FirstFileIndex;
+ public uint LastFileIndex;
+ public uint FirstFileOffset;
+ public uint FirstFileOffsetHigh;
+ public uint FirstFileSizeExpanded;
+ public uint FirstFileSizeExpandedHigh;
+ public uint FirstFileSizeCompressed;
+ public uint FirstFileSizeCompressedHigh;
+ public uint LastFileOffset;
+ public uint LastFileOffsetHigh;
+ public uint LastFileSizeExpanded;
+ public uint LastFileSizeExpandedHigh;
+ public uint LastFileSizeCompressed;
+ public uint LastFileSizeCompressedHigh;
+ }
+}
diff --git a/DICUI/External/iXcomp/IXComp.cs b/DICUI/External/iXcomp/IXComp.cs
deleted file mode 100644
index a003d7f7..00000000
--- a/DICUI/External/iXcomp/IXComp.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-
-namespace DICUI.External.iXcomp
-{
- // TODO: Replace this with a C# implementation based on Unshield
- public class IXComp
- {
- ///
- /// List all files found within an InstallShield CAB file
- ///
- /// CAB file to check
- /// Output tool version
- /// List of files found in the CAB
- public static List ListFiles(string input, out int version)
- {
- // Version 6
- version = 6;
- Process p = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Programs", "i6comp.exe"),
- Arguments = "l -o -r -d \"" + input + "\"",
- CreateNoWindow = true,
- UseShellExecute = false,
- RedirectStandardOutput = true,
- },
- };
- p.Start();
- p.WaitForExit(1000);
- var i6output = p.StandardOutput.ReadToEnd().Replace("\r\n", "\n").Split('\n').Where(s => s.Length > 50).Select(s => s.Substring(50)).ToList();
-
- if (i6output.Count() > 0)
- return i6output;
-
- // Version 5
- version = 5;
- p = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- FileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Programs", "i5comp.exe"),
- Arguments = "l -o -r -d \"" + input + "\"",
- CreateNoWindow = true,
- UseShellExecute = false,
- RedirectStandardOutput = true,
- },
- };
- p.Start();
- p.WaitForExit(1000);
- var i5output = p.StandardOutput.ReadToEnd().Replace("\r\n", "\n").Split('\n').Where(s => s.Length > 47).Select(s => s.Substring(47)).ToList();
-
- if (i5output.Count() > 0)
- return i6output;
-
- version = -1;
- return new List();
- }
-
- ///
- /// Extract all files found within an InstallShield CAB file
- ///
- /// CAB file to check
- /// Output directory to extract to
- /// Tool version
- /// True if the files extracted succesfully, false otherwise
- public static bool ExtractAll(string cabfile, string outDir, int version)
- {
- string exe = null;
- switch(version)
- {
- case 6:
- exe = "i6comp.exe";
- break;
- case 5:
- exe = "i5comp.exe";
- break;
- }
-
- if (exe == null)
- return false;
-
- Process p = new Process
- {
- StartInfo = new ProcessStartInfo
- {
- WorkingDirectory = outDir,
- FileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Programs", exe),
- Arguments = "x -r -d \"" + cabfile + "\"",
- CreateNoWindow = true,
- UseShellExecute = false,
- RedirectStandardOutput = true,
- },
- };
- p.Start();
- p.WaitForExit();
- return true;
- }
- }
-}
diff --git a/DICUI/MainWindow.xaml.cs b/DICUI/MainWindow.xaml.cs
index 770c25bc..029e176f 100644
--- a/DICUI/MainWindow.xaml.cs
+++ b/DICUI/MainWindow.xaml.cs
@@ -169,6 +169,11 @@ namespace DICUI
EnsureDiscInformation();
}
+ private void ProgressUpdated(object sender, Result value)
+ {
+ StatusLabel.Content = value.Message;
+ }
+
private void MainWindowLocationChanged(object sender, EventArgs e)
{
if (_logWindow.IsVisible)
@@ -309,7 +314,8 @@ namespace DICUI
if (DriveLetterComboBox.Items.Count > 0)
{
- DriveLetterComboBox.SelectedIndex = 0;
+ int index = _drives.FindIndex(d => d.MarkedActive);
+ DriveLetterComboBox.SelectedIndex = (index != -1 ? index : 0);
StatusLabel.Content = "Valid media found! Choose your Media Type";
StartStopButton.IsEnabled = true;
CopyProtectScanButton.IsEnabled = true;
@@ -385,7 +391,9 @@ namespace DICUI
StatusLabel.Content = "Beginning dumping process";
ViewModels.LoggerViewModel.VerboseLogLn("Starting dumping process..");
- Result result = await _env.StartDumping();
+ var progress = new Progress();
+ progress.ProgressChanged += ProgressUpdated;
+ Result result = await _env.StartDumping(progress);
StatusLabel.Content = result ? "Dumping complete!" : result.Message;
StartStopButton.Content = UIElements.StartDumping;
@@ -481,6 +489,8 @@ namespace DICUI
var env = DetermineEnvironment();
if (env.Drive.Letter != default(char))
{
+ ViewModels.LoggerViewModel.VerboseLogLn("Scanning for copy protection in {0}", _env.Drive.Letter);
+
var tempContent = StatusLabel.Content;
StatusLabel.Content = "Scanning for copy protection... this might take a while!";
StartStopButton.IsEnabled = false;
@@ -488,7 +498,9 @@ namespace DICUI
CopyProtectScanButton.IsEnabled = false;
string protections = await Validators.RunProtectionScanOnPath(env.Drive.Letter + ":\\");
- MessageBox.Show(protections, "Detected Protection", MessageBoxButton.OK, MessageBoxImage.Information);
+ if (!ViewModels.LoggerViewModel.WindowVisible)
+ MessageBox.Show(protections, "Detected Protection", MessageBoxButton.OK, MessageBoxImage.Information);
+ ViewModels.LoggerViewModel.VerboseLog("Detected the following protections in {0}:\r\n\r\n{1}", env.Drive.Letter, protections);
StatusLabel.Content = tempContent;
StartStopButton.IsEnabled = true;
@@ -518,7 +530,7 @@ namespace DICUI
if (speed == -1)
return;
- ViewModels.LoggerViewModel.VerboseLogLn("Determined max drive speed for {0}: {0}.", _env.Drive.Letter, speed);
+ ViewModels.LoggerViewModel.VerboseLogLn("Determined max drive speed for {0}: {1}.", _env.Drive.Letter, speed);
// Choose the lower of the two speeds between the allowed speeds and the user-defined one
int chosenSpeed = Math.Min(
diff --git a/DICUI/Programs/ZD50149.DLL b/DICUI/Programs/ZD50149.DLL
deleted file mode 100644
index 79ae76dd..00000000
Binary files a/DICUI/Programs/ZD50149.DLL and /dev/null differ
diff --git a/DICUI/Programs/ZD51145.DLL b/DICUI/Programs/ZD51145.DLL
deleted file mode 100644
index 1c5c3bcf..00000000
Binary files a/DICUI/Programs/ZD51145.DLL and /dev/null differ
diff --git a/DICUI/Programs/ZD55131.DLL b/DICUI/Programs/ZD55131.DLL
deleted file mode 100644
index d6e92449..00000000
Binary files a/DICUI/Programs/ZD55131.DLL and /dev/null differ
diff --git a/DICUI/Programs/i3comp.exe b/DICUI/Programs/i3comp.exe
deleted file mode 100644
index 32ca4fc7..00000000
Binary files a/DICUI/Programs/i3comp.exe and /dev/null differ
diff --git a/DICUI/Programs/i5comp.exe b/DICUI/Programs/i5comp.exe
deleted file mode 100644
index 5f92cac8..00000000
Binary files a/DICUI/Programs/i5comp.exe and /dev/null differ
diff --git a/DICUI/Programs/i6comp.exe b/DICUI/Programs/i6comp.exe
deleted file mode 100644
index 1b1cb0b5..00000000
Binary files a/DICUI/Programs/i6comp.exe and /dev/null differ
diff --git a/DICUI/Utilities/Converters.cs b/DICUI/Utilities/Converters.cs
index e7d2fbc1..c18a7935 100644
--- a/DICUI/Utilities/Converters.cs
+++ b/DICUI/Utilities/Converters.cs
@@ -1,13 +1,9 @@
using System;
-using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
-using System.Windows.Data;
-using System.Reflection;
using IMAPI2;
using DICUI.Data;
-
namespace DICUI.Utilities
{
///
diff --git a/DICUI/Utilities/DumpEnvironment.cs b/DICUI/Utilities/DumpEnvironment.cs
index 9c871c23..aebca83e 100644
--- a/DICUI/Utilities/DumpEnvironment.cs
+++ b/DICUI/Utilities/DumpEnvironment.cs
@@ -20,16 +20,18 @@ namespace DICUI.Utilities
public char Letter { get; private set; }
public bool IsFloppy { get; private set; }
public string VolumeLabel { get; private set; }
+ public bool MarkedActive { get; private set; }
- private Drive(char letter, string volumeLabel, bool isFloppy)
+ private Drive(char letter, string volumeLabel, bool isFloppy, bool markedActive)
{
this.Letter = letter;
this.IsFloppy = isFloppy;
this.VolumeLabel = volumeLabel;
+ this.MarkedActive = markedActive;
}
- public static Drive Floppy(char letter) => new Drive(letter, null, true);
- public static Drive Optical(char letter, string volumeLabel) => new Drive(letter, volumeLabel, false);
+ public static Drive Floppy(char letter) => new Drive(letter, null, true, true);
+ public static Drive Optical(char letter, string volumeLabel, bool active) => new Drive(letter, volumeLabel, false, active);
}
///
@@ -136,9 +138,13 @@ namespace DICUI.Utilities
if (IsFloppy)
return -1;
+ // Make sure that the current drive is active
+ if (!Drive.MarkedActive)
+ return -1;
+
// Get the drive speed directly
- //int speed = Validators.GetDriveSpeed(Drive.Letter);
- //int speed = Validators.GetDriveSpeedEx(Drive.Letter, _currentMediaType);
+ //int speed = Validators.GetDriveSpeed(Drive);
+ //int speed = Validators.GetDriveSpeedEx(Drive, _currentMediaType);
// Get the drive speed from DIC, if possible
Process childProcess;
@@ -224,7 +230,7 @@ namespace DICUI.Utilities
///
/// Execute a complete dump workflow
///
- public async Task StartDumping()
+ public async Task StartDumping(IProgress progress)
{
Result result = IsValidForDump();
@@ -234,9 +240,11 @@ namespace DICUI.Utilities
// execute DIC
await Task.Run(() => ExecuteDiskImageCreator());
+ progress?.Report(Result.Success("DiscImageCreator has finished!"));
// execute additional tools
result = ExecuteAdditionalToolsAfterDIC();
+ progress?.Report(result);
// is something is wrong with additional tools report and return
// TODO: don't return, just keep generating output from DIC
@@ -247,7 +255,8 @@ namespace DICUI.Utilities
return;
}*/
- // verify dump output and save it
+ // Verify dump output and save it
+ progress?.Report(Result.Success("Gathering submission information..."));
result = VerifyAndSaveDumpOutput();
return result;
@@ -269,7 +278,7 @@ namespace DICUI.Utilities
if (Type == MediaType.Floppy)
Drive = Drive.Floppy(String.IsNullOrWhiteSpace(letter) ? new char() : letter[0]);
else
- Drive = Drive.Optical(String.IsNullOrWhiteSpace(letter) ? new char() : letter[0], "");
+ Drive = Drive.Optical(String.IsNullOrWhiteSpace(letter) ? new char() : letter[0], "", true);
OutputDirectory = Path.GetDirectoryName(path);
OutputFilename = Path.GetFileName(path);
}
@@ -1365,6 +1374,16 @@ namespace DICUI.Utilities
if (!File.Exists(DICPath))
return Result.Failure("Error! Could not find DiscImageCreator!");
+ // Validate that the user explicitly wants an inactive drive to be considered for dumping
+ if (!Drive.MarkedActive)
+ {
+ MessageBoxResult result = MessageBox.Show("The currently selected drive does not appear to contain a disc! Are you sure you want to continue?", "Missing Disc", MessageBoxButton.YesNo, MessageBoxImage.Exclamation);
+ if (result == MessageBoxResult.No || result == MessageBoxResult.Cancel || result == MessageBoxResult.None)
+ {
+ return Result.Failure("Dumping aborted!");
+ }
+ }
+
// If a complete dump already exists
if (FoundAllFiles())
{
diff --git a/DICUI/Utilities/Parameters.cs b/DICUI/Utilities/Parameters.cs
index 4d7c10ce..bc2cc808 100644
--- a/DICUI/Utilities/Parameters.cs
+++ b/DICUI/Utilities/Parameters.cs
@@ -200,7 +200,7 @@ namespace DICUI.Utilities
|| Command == DICCommand.XBOX)
{
if (Filename != null)
- parameters.Add("\"" + Filename + "\"");
+ parameters.Add("\"" + Filename.Trim('"') + "\"");
else
return null;
}
diff --git a/DICUI/Utilities/Validators.cs b/DICUI/Utilities/Validators.cs
index eee27280..6de4952b 100644
--- a/DICUI/Utilities/Validators.cs
+++ b/DICUI/Utilities/Validators.cs
@@ -419,8 +419,8 @@ namespace DICUI.Utilities
// Get the optical disc drives
List discDrives = DriveInfo.GetDrives()
- .Where(d => d.DriveType == DriveType.CDRom && d.IsReady)
- .Select(d => Drive.Optical(d.Name[0], d.VolumeLabel))
+ .Where(d => d.DriveType == DriveType.CDRom)
+ .Select(d => Drive.Optical(d.Name[0], (d.IsReady ? d.VolumeLabel : UIElements.DiscNotDetected), d.IsReady))
.ToList();
// Add the two lists together and order
@@ -516,11 +516,15 @@ namespace DICUI.Utilities
/// capabilities of the drives (according to QPXTool)
/// TransferRate appears to be the CURRENT transfer rate, not the maximum... basically making that flag useless
///
- public static int GetDriveSpeed(char driveLetter)
+ public static int GetDriveSpeed(Drive drive)
{
+ // If the current drive is not active or optical
+ if (drive.IsFloppy || !drive.MarkedActive)
+ return -1;
+
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\CIMV2",
- "SELECT * FROM Win32_CDROMDrive WHERE Id = '" + driveLetter + ":\'");
+ "SELECT * FROM Win32_CDROMDrive WHERE Id = '" + drive.Letter + ":\'");
var collection = searcher.Get();
double? transferRate = -1;
@@ -540,15 +544,19 @@ namespace DICUI.Utilities
return 0;
}
- public unsafe static int GetDriveSpeedEx(char driveLetter, MediaType? mediaType)
+ public unsafe static int GetDriveSpeedEx(Drive drive, MediaType? mediaType)
{
+ // If the current drive is not active or optical
+ if (drive.IsFloppy || !drive.MarkedActive)
+ return -1;
+
// Get the DeviceID from the current drive letter
string deviceId = null;
try
{
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\CIMV2",
- "SELECT * FROM Win32_CDROMDrive WHERE Id = '" + driveLetter + ":\'");
+ "SELECT * FROM Win32_CDROMDrive WHERE Id = '" + drive.Letter + ":\'");
var collection = searcher.Get();
foreach (ManagementObject queryObj in collection)
diff --git a/DICUI/packages.config b/DICUI/packages.config
index c31b69c3..2920ce8d 100644
--- a/DICUI/packages.config
+++ b/DICUI/packages.config
@@ -2,4 +2,5 @@
+
\ No newline at end of file