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