From cc4837c1d1ec5d8ba964ac35be5e8af2e26b3c0c Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Fri, 12 Sep 2025 09:09:40 -0400 Subject: [PATCH] More partial classes for reasonable things --- .../Wrappers/N3DS.Encryption.cs | 65 +++ SabreTools.Serialization/Wrappers/N3DS.cs | 63 +-- .../Wrappers/Nitro.Encryption.cs | 437 +++++++++++++++++ SabreTools.Serialization/Wrappers/Nitro.cs | 438 +----------------- .../Wrappers/WiseOverlayHeader.Extraction.cs | 313 +++++++++++++ .../Wrappers/WiseOverlayHeader.cs | 312 +------------ 6 files changed, 819 insertions(+), 809 deletions(-) create mode 100644 SabreTools.Serialization/Wrappers/N3DS.Encryption.cs create mode 100644 SabreTools.Serialization/Wrappers/Nitro.Encryption.cs create mode 100644 SabreTools.Serialization/Wrappers/WiseOverlayHeader.Extraction.cs diff --git a/SabreTools.Serialization/Wrappers/N3DS.Encryption.cs b/SabreTools.Serialization/Wrappers/N3DS.Encryption.cs new file mode 100644 index 00000000..e8a573e0 --- /dev/null +++ b/SabreTools.Serialization/Wrappers/N3DS.Encryption.cs @@ -0,0 +1,65 @@ +using System; +using static SabreTools.Models.N3DS.Constants; + +namespace SabreTools.Serialization.Wrappers +{ + public partial class N3DS + { + /// + /// Get the initial value for the ExeFS counter + /// + public byte[] ExeFSIV(int index) + { + if (Partitions == null) + return []; + if (index < 0 || index >= Partitions.Length) + return []; + + var header = Partitions[index]; + if (header == null || header.MagicID != NCCHMagicNumber) + return []; + + byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); + Array.Reverse(partitionIdBytes); + return [.. partitionIdBytes, .. ExefsCounter]; + } + + /// + /// Get the initial value for the plain counter + /// + public byte[] PlainIV(int index) + { + if (Partitions == null) + return []; + if (index < 0 || index >= Partitions.Length) + return []; + + var header = Partitions[index]; + if (header == null || header.MagicID != NCCHMagicNumber) + return []; + + byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); + Array.Reverse(partitionIdBytes); + return [.. partitionIdBytes, .. PlainCounter]; + } + + /// + /// Get the initial value for the RomFS counter + /// + public byte[] RomFSIV(int index) + { + if (Partitions == null) + return []; + if (index < 0 || index >= Partitions.Length) + return []; + + var header = Partitions[index]; + if (header == null || header.MagicID != NCCHMagicNumber) + return []; + + byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); + Array.Reverse(partitionIdBytes); + return [.. partitionIdBytes, .. RomfsCounter]; + } + } +} diff --git a/SabreTools.Serialization/Wrappers/N3DS.cs b/SabreTools.Serialization/Wrappers/N3DS.cs index 56c02b16..be845be6 100644 --- a/SabreTools.Serialization/Wrappers/N3DS.cs +++ b/SabreTools.Serialization/Wrappers/N3DS.cs @@ -5,7 +5,7 @@ using static SabreTools.Models.N3DS.Constants; namespace SabreTools.Serialization.Wrappers { - public class N3DS : WrapperBase + public partial class N3DS : WrapperBase { #region Descriptive Properties @@ -349,67 +349,6 @@ namespace SabreTools.Serialization.Wrappers #endregion - #region Encryption - - /// - /// Get the initial value for the ExeFS counter - /// - public byte[] ExeFSIV(int index) - { - if (Partitions == null) - return []; - if (index < 0 || index >= Partitions.Length) - return []; - - var header = Partitions[index]; - if (header == null || header.MagicID != NCCHMagicNumber) - return []; - - byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); - Array.Reverse(partitionIdBytes); - return [.. partitionIdBytes, .. ExefsCounter]; - } - - /// - /// Get the initial value for the plain counter - /// - public byte[] PlainIV(int index) - { - if (Partitions == null) - return []; - if (index < 0 || index >= Partitions.Length) - return []; - - var header = Partitions[index]; - if (header == null || header.MagicID != NCCHMagicNumber) - return []; - - byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); - Array.Reverse(partitionIdBytes); - return [.. partitionIdBytes, .. PlainCounter]; - } - - /// - /// Get the initial value for the RomFS counter - /// - public byte[] RomFSIV(int index) - { - if (Partitions == null) - return []; - if (index < 0 || index >= Partitions.Length) - return []; - - var header = Partitions[index]; - if (header == null || header.MagicID != NCCHMagicNumber) - return []; - - byte[] partitionIdBytes = BitConverter.GetBytes(header.PartitionId); - Array.Reverse(partitionIdBytes); - return [.. partitionIdBytes, .. RomfsCounter]; - } - - #endregion - #region Offsets /// diff --git a/SabreTools.Serialization/Wrappers/Nitro.Encryption.cs b/SabreTools.Serialization/Wrappers/Nitro.Encryption.cs new file mode 100644 index 00000000..2d4de23f --- /dev/null +++ b/SabreTools.Serialization/Wrappers/Nitro.Encryption.cs @@ -0,0 +1,437 @@ +using System; +using SabreTools.IO.Extensions; +using SabreTools.Models.Nitro; + +namespace SabreTools.Serialization.Wrappers +{ + public partial class Nitro + { + #region Encryption process variables + + private uint[] _cardHash = new uint[0x412]; + private uint[] _arg2 = new uint[3]; + + #endregion + + #region Encrypt + + /// + /// Encrypt secure area in the DS/DSi file + /// s + /// Blowfish table data as a byte array + /// Indicates if the operation should be forced + public void EncryptSecureArea(byte[] tableData, bool force) + { + // If we're forcing the operation, tell the user + if (force) + { + Console.WriteLine("File is not verified due to force flag being set."); + } + // If we're not forcing the operation, check to see if we should be proceeding + else + { + bool? isDecrypted = CheckIfDecrypted(out string? message); + if (message != null) + Console.WriteLine(message); + + if (isDecrypted == null) + { + Console.WriteLine("File has an empty secure area, cannot proceed"); + return; + } + else if (!isDecrypted.Value) + { + Console.WriteLine("File is already encrypted"); + return; + } + } + + EncryptARM9(tableData); + Console.WriteLine("File has been encrypted"); + } + + /// + /// Encrypt the secure ARM9 region of the file, if possible + /// + /// Blowfish table data as a byte array + private void EncryptARM9(byte[] tableData) + { + // If the secure area is invalid, nothing can be done + if (SecureArea == null) + return; + + // Point to the beginning of the secure area + int readOffset = 0; + + // Grab the first two blocks + uint p0 = SecureArea.ReadUInt32LittleEndian(ref readOffset); + uint p1 = SecureArea.ReadUInt32LittleEndian(ref readOffset); + + // Perform the initialization steps + Init1(tableData); + _arg2[1] <<= 1; + _arg2[2] >>= 1; + Init2(); + + // Ensure alignment + readOffset = 0x08; + int writeOffset = 0x08; + + // Loop throgh the main encryption step + uint size = 0x800 - 8; + while (size > 0) + { + p0 = SecureArea.ReadUInt32LittleEndian(ref readOffset); + p1 = SecureArea.ReadUInt32LittleEndian(ref readOffset); + + Encrypt(ref p1, ref p0); + + SecureArea.Write(ref writeOffset, p0); + SecureArea.Write(ref writeOffset, p1); + + size -= 8; + } + + // Replace the header explicitly + readOffset = 0; + writeOffset = 0; + + p0 = SecureArea.ReadUInt32LittleEndian(ref readOffset); + p1 = SecureArea.ReadUInt32LittleEndian(ref readOffset); + + if (p0 == 0xE7FFDEFF && p1 == 0xE7FFDEFF) + { + p0 = Constants.MAGIC30; + p1 = Constants.MAGIC34; + } + + Encrypt(ref p1, ref p0); + Init1(tableData); + Encrypt(ref p1, ref p0); + + SecureArea.Write(ref writeOffset, p0); + SecureArea.Write(ref writeOffset, p1); + } + + /// + /// Perform an encryption step + /// + /// First unsigned value to use in encryption + /// Second unsigned value to use in encryption + private void Encrypt(ref uint arg1, ref uint arg2) + { + uint a = arg1; + uint b = arg2; + for (int i = 0; i < 16; i++) + { + uint c = _cardHash[i] ^ a; + a = b ^ Lookup(c); + b = c; + } + + arg2 = a ^ _cardHash[16]; + arg1 = b ^ _cardHash[17]; + } + + #endregion + + #region Decrypt + + /// + /// Decrypt secure area in the DS/DSi file + /// s + /// Blowfish table data as a byte array + /// Indicates if the operation should be forced + public void DecryptSecureArea(byte[] tableData, bool force) + { + // If we're forcing the operation, tell the user + if (force) + { + Console.WriteLine("File is not verified due to force flag being set."); + } + // If we're not forcing the operation, check to see if we should be proceeding + else + { + bool? isDecrypted = CheckIfDecrypted(out string? message); + if (message != null) + Console.WriteLine(message); + + if (isDecrypted == null) + { + Console.WriteLine("File has an empty secure area, cannot proceed"); + return; + } + else if (isDecrypted.Value) + { + Console.WriteLine("File is already decrypted"); + return; + } + } + + DecryptARM9(tableData); + Console.WriteLine("File has been decrypted"); + } + + /// + /// Decrypt the secure ARM9 region of the file, if possible + /// + /// Blowfish table data as a byte array + private void DecryptARM9(byte[] tableData) + { + // If the secure area is invalid, nothing can be done + if (SecureArea == null) + return; + + // Point to the beginning of the secure area + int readOffset = 0; + int writeOffset = 0; + + // Grab the first two blocks + uint p0 = SecureArea.ReadUInt32LittleEndian(ref readOffset); + uint p1 = SecureArea.ReadUInt32LittleEndian(ref readOffset); + + // Perform the initialization steps + Init1(tableData); + Decrypt(ref p1, ref p0); + _arg2[1] <<= 1; + _arg2[2] >>= 1; + Init2(); + + // Set the proper flags + Decrypt(ref p1, ref p0); + if (p0 == Constants.MAGIC30 && p1 == Constants.MAGIC34) + { + p0 = 0xE7FFDEFF; + p1 = 0xE7FFDEFF; + } + + SecureArea.Write(ref writeOffset, p0); + SecureArea.Write(ref writeOffset, p1); + + // Ensure alignment + readOffset = 0x08; + writeOffset = 0x08; + + // Loop throgh the main encryption step + uint size = 0x800 - 8; + while (size > 0) + { + p0 = SecureArea.ReadUInt32LittleEndian(ref readOffset); + p1 = SecureArea.ReadUInt32LittleEndian(ref readOffset); + + Decrypt(ref p1, ref p0); + + SecureArea.Write(ref writeOffset, p0); + SecureArea.Write(ref writeOffset, p1); + + size -= 8; + } + } + + /// + /// Perform a decryption step + /// + /// First unsigned value to use in decryption + /// Second unsigned value to use in decryption + private void Decrypt(ref uint arg1, ref uint arg2) + { + uint a = arg1; + uint b = arg2; + for (int i = 17; i > 1; i--) + { + uint c = _cardHash[i] ^ a; + a = b ^ Lookup(c); + b = c; + } + + arg1 = b ^ _cardHash[0]; + arg2 = a ^ _cardHash[1]; + } + + #endregion + + #region Common + + /// + /// Determine if the current file is already decrypted or not (or has an empty secure area) + /// + /// Optional message with more information on the result + /// True if the file has known values for a decrypted file, null if it's empty, false otherwise + public bool? CheckIfDecrypted(out string? message) + { + // Return empty if the secure area is undefined + if (SecureArea == null) + { + message = "Secure area is undefined. Cannot be encrypted or decrypted."; + return null; + } + + int offset = 0; + uint firstValue = SecureArea.ReadUInt32LittleEndian(ref offset); + uint secondValue = SecureArea.ReadUInt32LittleEndian(ref offset); + + // Empty secure area standard + if (firstValue == 0x00000000 && secondValue == 0x00000000) + { + message = "Empty secure area found. Cannot be encrypted or decrypted."; + return null; + } + + // Improperly decrypted empty secure area (decrypt empty with woodsec) + else if ((firstValue == 0xE386C397 && secondValue == 0x82775B7E) + || (firstValue == 0xF98415B8 && secondValue == 0x698068FC) + || (firstValue == 0xA71329EE && secondValue == 0x2A1D4C38) + || (firstValue == 0xC44DCC48 && secondValue == 0x38B6F8CB) + || (firstValue == 0x3A9323B5 && secondValue == 0xC0387241)) + { + message = "Improperly decrypted empty secure area found. Should be encrypted to get proper value."; + return true; + } + + // Improperly encrypted empty secure area (encrypt empty with woodsec) + else if ((firstValue == 0x4BCE88BE && secondValue == 0xD3662DD1) + || (firstValue == 0x2543C534 && secondValue == 0xCC4BE38E)) + { + message = "Improperly encrypted empty secure area found. Should be decrypted to get proper value."; + return false; + } + + // Properly decrypted nonstandard value (mastering issue) + else if ((firstValue == 0xD0D48B67 && secondValue == 0x39392F23) // Dragon Quest 5 (EU) + || (firstValue == 0x014A191A && secondValue == 0xA5C470B9) // Dragon Quest 5 (USA) + || (firstValue == 0x7829BC8D && secondValue == 0x9968EF44) // Dragon Quest 5 (JP) + || (firstValue == 0xC4A15AB8 && secondValue == 0xD2E667C8) // Prince of Persia (EU) + || (firstValue == 0xD5E97D20 && secondValue == 0x21B2A159)) // Prince of Persia (USA) + { + message = "Decrypted secure area for known, nonstandard value found."; + return true; + } + + // Properly decrypted prototype value + else if (firstValue == 0xBA35F813 && secondValue == 0xB691AAE8) + { + message = "Decrypted secure area for prototype found."; + return true; + } + + // Strange, unlicenced values that can't determine decryption state + else if ((firstValue == 0xE1D830D8 && secondValue == 0xE3530000) // Aquela Ball (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xDC002A02 && secondValue == 0x2900E612) // Bahlz (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE1A03BA3 && secondValue == 0xE2011CFF) // Battle Ship (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE3A01001 && secondValue == 0xE1A02001) // Breakout!! DS (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE793200C && secondValue == 0xE4812004) // Bubble Fusion (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE583C0DC && secondValue == 0x0A00000B) // Carre Rouge (World) (Unl) (Datel Games n' Music) + || (firstValue == 0x0202453C && secondValue == 0x02060164) // ChainReaction (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xEBFFF218 && secondValue == 0xE31000FF) // Collection (World) (Unl) (Datel Games n' Music) + || (firstValue == 0x4A6CD003 && secondValue == 0x425B2301) // DiggerDS (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE3A00001 && secondValue == 0xEBFFFF8C) // Double Skill (World) (Unl) (Datel Games n' Music) + || (firstValue == 0x21043701 && secondValue == 0x45BA448C) // DSChess (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE59D0010 && secondValue == 0xE0833000) // Hexa-Virus (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE5C3A006 && secondValue == 0xE5C39007) // Invasion (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE1D920F4 && secondValue == 0xE06A3000) // JoggleDS (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE59F32EC && secondValue == 0xE5DD7011) // London Underground (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE08A3503 && secondValue == 0xE1D3C4B8) // NumberMinds (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE1A0C001 && secondValue == 0xE0031001) // Paddle Battle (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE1A03005 && secondValue == 0xE88D0180) // Pop the Balls (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE8BD4030 && secondValue == 0xE12FFF1E) // Solitaire DS (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE0A88006 && secondValue == 0xE1A00003) // Squash DS (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE51F3478 && secondValue == 0xEB004A02) // Super Snake DS (World) (Unl) (Datel Games n' Music) + || (firstValue == 0x1C200052 && secondValue == 0xFD12F013) // Tales of Dagur (World) (Unl) (Datel Games n' Music) + || (firstValue == 0x601F491E && secondValue == 0x041B880B) // Tetris & Touch (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE1A03843 && secondValue == 0xE0000293) // Tic Tac Toe (World) (Unl) (Datel Games n' Music) + || (firstValue == 0xE3530000 && secondValue == 0x13A03003) // Warrior Training (World) (Unl) (Datel Games n' Music) + || (firstValue == 0x02054A80 && secondValue == 0x02054B80)) // Zi (World) (Unl) (Datel Games n' Music) + { + message = "Unlicensed invalid value found. Unknown if encrypted or decrypted."; + return null; + } + + // Standard decryption values + message = null; + return firstValue == 0xE7FFDEFF && secondValue == 0xE7FFDEFF; + } + + /// + /// First common initialization step + /// + /// Blowfish table data as a byte array + private void Init1(byte[] tableData) + { + Buffer.BlockCopy(tableData, 0, _cardHash, 0, 4 * (1024 + 18)); + _arg2 = [GameCode, GameCode >> 1, GameCode << 1]; + Init2(); + Init2(); + } + + /// + /// Second common initialization step + /// + private void Init2() + { + Encrypt(ref _arg2[2], ref _arg2[1]); + Encrypt(ref _arg2[1], ref _arg2[0]); + + byte[] allBytes = [.. BitConverter.GetBytes(_arg2[0]), + .. BitConverter.GetBytes(_arg2[1]), + .. BitConverter.GetBytes(_arg2[2])]; + + UpdateHashtable(allBytes); + } + + /// + /// Lookup the value from the hashtable + /// + /// Value to lookup in the hashtable + /// Processed value through the hashtable + private uint Lookup(uint v) + { + uint a = (v >> 24) & 0xFF; + uint b = (v >> 16) & 0xFF; + uint c = (v >> 8) & 0xFF; + uint d = (v >> 0) & 0xFF; + + a = _cardHash[a + 18 + 0]; + b = _cardHash[b + 18 + 256]; + c = _cardHash[c + 18 + 512]; + d = _cardHash[d + 18 + 768]; + + return d + (c ^ (b + a)); + } + + /// + /// Update the hashtable + /// + /// Value to update the hashtable with + private void UpdateHashtable(byte[] arg1) + { + for (int j = 0; j < 18; j++) + { + uint r3 = 0; + for (int i = 0; i < 4; i++) + { + r3 <<= 8; + r3 |= arg1[(j * 4 + i) & 7]; + } + + _cardHash[j] ^= r3; + } + + uint tmp1 = 0; + uint tmp2 = 0; + for (int i = 0; i < 18; i += 2) + { + Encrypt(ref tmp1, ref tmp2); + _cardHash[i + 0] = tmp1; + _cardHash[i + 1] = tmp2; + } + for (int i = 0; i < 0x400; i += 2) + { + Encrypt(ref tmp1, ref tmp2); + _cardHash[i + 18 + 0] = tmp1; + _cardHash[i + 18 + 1] = tmp2; + } + } + + #endregion + } +} diff --git a/SabreTools.Serialization/Wrappers/Nitro.cs b/SabreTools.Serialization/Wrappers/Nitro.cs index d94432bb..aefdb830 100644 --- a/SabreTools.Serialization/Wrappers/Nitro.cs +++ b/SabreTools.Serialization/Wrappers/Nitro.cs @@ -1,11 +1,9 @@ -using System; -using System.IO; -using SabreTools.IO.Extensions; +using System.IO; using SabreTools.Models.Nitro; namespace SabreTools.Serialization.Wrappers { - public class Nitro : WrapperBase + public partial class Nitro : WrapperBase { #region Descriptive Properties @@ -27,13 +25,6 @@ namespace SabreTools.Serialization.Wrappers #endregion - #region Encryption process variables - - private uint[] _cardHash = new uint[0x412]; - private uint[] _arg2 = new uint[3]; - - #endregion - #region Constructors /// @@ -101,430 +92,5 @@ namespace SabreTools.Serialization.Wrappers } #endregion - - #region Encryption - - #region Encrypt - - /// - /// Encrypt secure area in the DS/DSi file - /// s - /// Blowfish table data as a byte array - /// Indicates if the operation should be forced - public void EncryptSecureArea(byte[] tableData, bool force) - { - // If we're forcing the operation, tell the user - if (force) - { - Console.WriteLine("File is not verified due to force flag being set."); - } - // If we're not forcing the operation, check to see if we should be proceeding - else - { - bool? isDecrypted = CheckIfDecrypted(out string? message); - if (message != null) - Console.WriteLine(message); - - if (isDecrypted == null) - { - Console.WriteLine("File has an empty secure area, cannot proceed"); - return; - } - else if (!isDecrypted.Value) - { - Console.WriteLine("File is already encrypted"); - return; - } - } - - EncryptARM9(tableData); - Console.WriteLine("File has been encrypted"); - } - - /// - /// Encrypt the secure ARM9 region of the file, if possible - /// - /// Blowfish table data as a byte array - private void EncryptARM9(byte[] tableData) - { - // If the secure area is invalid, nothing can be done - if (SecureArea == null) - return; - - // Point to the beginning of the secure area - int readOffset = 0; - - // Grab the first two blocks - uint p0 = SecureArea.ReadUInt32LittleEndian(ref readOffset); - uint p1 = SecureArea.ReadUInt32LittleEndian(ref readOffset); - - // Perform the initialization steps - Init1(tableData); - _arg2[1] <<= 1; - _arg2[2] >>= 1; - Init2(); - - // Ensure alignment - readOffset = 0x08; - int writeOffset = 0x08; - - // Loop throgh the main encryption step - uint size = 0x800 - 8; - while (size > 0) - { - p0 = SecureArea.ReadUInt32LittleEndian(ref readOffset); - p1 = SecureArea.ReadUInt32LittleEndian(ref readOffset); - - Encrypt(ref p1, ref p0); - - SecureArea.Write(ref writeOffset, p0); - SecureArea.Write(ref writeOffset, p1); - - size -= 8; - } - - // Replace the header explicitly - readOffset = 0; - writeOffset = 0; - - p0 = SecureArea.ReadUInt32LittleEndian(ref readOffset); - p1 = SecureArea.ReadUInt32LittleEndian(ref readOffset); - - if (p0 == 0xE7FFDEFF && p1 == 0xE7FFDEFF) - { - p0 = Constants.MAGIC30; - p1 = Constants.MAGIC34; - } - - Encrypt(ref p1, ref p0); - Init1(tableData); - Encrypt(ref p1, ref p0); - - SecureArea.Write(ref writeOffset, p0); - SecureArea.Write(ref writeOffset, p1); - } - - /// - /// Perform an encryption step - /// - /// First unsigned value to use in encryption - /// Second unsigned value to use in encryption - private void Encrypt(ref uint arg1, ref uint arg2) - { - uint a = arg1; - uint b = arg2; - for (int i = 0; i < 16; i++) - { - uint c = _cardHash[i] ^ a; - a = b ^ Lookup(c); - b = c; - } - - arg2 = a ^ _cardHash[16]; - arg1 = b ^ _cardHash[17]; - } - - #endregion - - #region Decrypt - - /// - /// Decrypt secure area in the DS/DSi file - /// s - /// Blowfish table data as a byte array - /// Indicates if the operation should be forced - public void DecryptSecureArea(byte[] tableData, bool force) - { - // If we're forcing the operation, tell the user - if (force) - { - Console.WriteLine("File is not verified due to force flag being set."); - } - // If we're not forcing the operation, check to see if we should be proceeding - else - { - bool? isDecrypted = CheckIfDecrypted(out string? message); - if (message != null) - Console.WriteLine(message); - - if (isDecrypted == null) - { - Console.WriteLine("File has an empty secure area, cannot proceed"); - return; - } - else if (isDecrypted.Value) - { - Console.WriteLine("File is already decrypted"); - return; - } - } - - DecryptARM9(tableData); - Console.WriteLine("File has been decrypted"); - } - - /// - /// Decrypt the secure ARM9 region of the file, if possible - /// - /// Blowfish table data as a byte array - private void DecryptARM9(byte[] tableData) - { - // If the secure area is invalid, nothing can be done - if (SecureArea == null) - return; - - // Point to the beginning of the secure area - int readOffset = 0; - int writeOffset = 0; - - // Grab the first two blocks - uint p0 = SecureArea.ReadUInt32LittleEndian(ref readOffset); - uint p1 = SecureArea.ReadUInt32LittleEndian(ref readOffset); - - // Perform the initialization steps - Init1(tableData); - Decrypt(ref p1, ref p0); - _arg2[1] <<= 1; - _arg2[2] >>= 1; - Init2(); - - // Set the proper flags - Decrypt(ref p1, ref p0); - if (p0 == Constants.MAGIC30 && p1 == Constants.MAGIC34) - { - p0 = 0xE7FFDEFF; - p1 = 0xE7FFDEFF; - } - - SecureArea.Write(ref writeOffset, p0); - SecureArea.Write(ref writeOffset, p1); - - // Ensure alignment - readOffset = 0x08; - writeOffset = 0x08; - - // Loop throgh the main encryption step - uint size = 0x800 - 8; - while (size > 0) - { - p0 = SecureArea.ReadUInt32LittleEndian(ref readOffset); - p1 = SecureArea.ReadUInt32LittleEndian(ref readOffset); - - Decrypt(ref p1, ref p0); - - SecureArea.Write(ref writeOffset, p0); - SecureArea.Write(ref writeOffset, p1); - - size -= 8; - } - } - - /// - /// Perform a decryption step - /// - /// First unsigned value to use in decryption - /// Second unsigned value to use in decryption - private void Decrypt(ref uint arg1, ref uint arg2) - { - uint a = arg1; - uint b = arg2; - for (int i = 17; i > 1; i--) - { - uint c = _cardHash[i] ^ a; - a = b ^ Lookup(c); - b = c; - } - - arg1 = b ^ _cardHash[0]; - arg2 = a ^ _cardHash[1]; - } - - #endregion - - #region Common - - /// - /// Determine if the current file is already decrypted or not (or has an empty secure area) - /// - /// Optional message with more information on the result - /// True if the file has known values for a decrypted file, null if it's empty, false otherwise - public bool? CheckIfDecrypted(out string? message) - { - // Return empty if the secure area is undefined - if (SecureArea == null) - { - message = "Secure area is undefined. Cannot be encrypted or decrypted."; - return null; - } - - int offset = 0; - uint firstValue = SecureArea.ReadUInt32LittleEndian(ref offset); - uint secondValue = SecureArea.ReadUInt32LittleEndian(ref offset); - - // Empty secure area standard - if (firstValue == 0x00000000 && secondValue == 0x00000000) - { - message = "Empty secure area found. Cannot be encrypted or decrypted."; - return null; - } - - // Improperly decrypted empty secure area (decrypt empty with woodsec) - else if ((firstValue == 0xE386C397 && secondValue == 0x82775B7E) - || (firstValue == 0xF98415B8 && secondValue == 0x698068FC) - || (firstValue == 0xA71329EE && secondValue == 0x2A1D4C38) - || (firstValue == 0xC44DCC48 && secondValue == 0x38B6F8CB) - || (firstValue == 0x3A9323B5 && secondValue == 0xC0387241)) - { - message = "Improperly decrypted empty secure area found. Should be encrypted to get proper value."; - return true; - } - - // Improperly encrypted empty secure area (encrypt empty with woodsec) - else if ((firstValue == 0x4BCE88BE && secondValue == 0xD3662DD1) - || (firstValue == 0x2543C534 && secondValue == 0xCC4BE38E)) - { - message = "Improperly encrypted empty secure area found. Should be decrypted to get proper value."; - return false; - } - - // Properly decrypted nonstandard value (mastering issue) - else if ((firstValue == 0xD0D48B67 && secondValue == 0x39392F23) // Dragon Quest 5 (EU) - || (firstValue == 0x014A191A && secondValue == 0xA5C470B9) // Dragon Quest 5 (USA) - || (firstValue == 0x7829BC8D && secondValue == 0x9968EF44) // Dragon Quest 5 (JP) - || (firstValue == 0xC4A15AB8 && secondValue == 0xD2E667C8) // Prince of Persia (EU) - || (firstValue == 0xD5E97D20 && secondValue == 0x21B2A159)) // Prince of Persia (USA) - { - message = "Decrypted secure area for known, nonstandard value found."; - return true; - } - - // Properly decrypted prototype value - else if (firstValue == 0xBA35F813 && secondValue == 0xB691AAE8) - { - message = "Decrypted secure area for prototype found."; - return true; - } - - // Strange, unlicenced values that can't determine decryption state - else if ((firstValue == 0xE1D830D8 && secondValue == 0xE3530000) // Aquela Ball (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xDC002A02 && secondValue == 0x2900E612) // Bahlz (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE1A03BA3 && secondValue == 0xE2011CFF) // Battle Ship (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE3A01001 && secondValue == 0xE1A02001) // Breakout!! DS (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE793200C && secondValue == 0xE4812004) // Bubble Fusion (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE583C0DC && secondValue == 0x0A00000B) // Carre Rouge (World) (Unl) (Datel Games n' Music) - || (firstValue == 0x0202453C && secondValue == 0x02060164) // ChainReaction (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xEBFFF218 && secondValue == 0xE31000FF) // Collection (World) (Unl) (Datel Games n' Music) - || (firstValue == 0x4A6CD003 && secondValue == 0x425B2301) // DiggerDS (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE3A00001 && secondValue == 0xEBFFFF8C) // Double Skill (World) (Unl) (Datel Games n' Music) - || (firstValue == 0x21043701 && secondValue == 0x45BA448C) // DSChess (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE59D0010 && secondValue == 0xE0833000) // Hexa-Virus (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE5C3A006 && secondValue == 0xE5C39007) // Invasion (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE1D920F4 && secondValue == 0xE06A3000) // JoggleDS (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE59F32EC && secondValue == 0xE5DD7011) // London Underground (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE08A3503 && secondValue == 0xE1D3C4B8) // NumberMinds (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE1A0C001 && secondValue == 0xE0031001) // Paddle Battle (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE1A03005 && secondValue == 0xE88D0180) // Pop the Balls (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE8BD4030 && secondValue == 0xE12FFF1E) // Solitaire DS (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE0A88006 && secondValue == 0xE1A00003) // Squash DS (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE51F3478 && secondValue == 0xEB004A02) // Super Snake DS (World) (Unl) (Datel Games n' Music) - || (firstValue == 0x1C200052 && secondValue == 0xFD12F013) // Tales of Dagur (World) (Unl) (Datel Games n' Music) - || (firstValue == 0x601F491E && secondValue == 0x041B880B) // Tetris & Touch (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE1A03843 && secondValue == 0xE0000293) // Tic Tac Toe (World) (Unl) (Datel Games n' Music) - || (firstValue == 0xE3530000 && secondValue == 0x13A03003) // Warrior Training (World) (Unl) (Datel Games n' Music) - || (firstValue == 0x02054A80 && secondValue == 0x02054B80)) // Zi (World) (Unl) (Datel Games n' Music) - { - message = "Unlicensed invalid value found. Unknown if encrypted or decrypted."; - return null; - } - - // Standard decryption values - message = null; - return firstValue == 0xE7FFDEFF && secondValue == 0xE7FFDEFF; - } - - /// - /// First common initialization step - /// - /// Blowfish table data as a byte array - private void Init1(byte[] tableData) - { - Buffer.BlockCopy(tableData, 0, _cardHash, 0, 4 * (1024 + 18)); - _arg2 = [GameCode, GameCode >> 1, GameCode << 1]; - Init2(); - Init2(); - } - - /// - /// Second common initialization step - /// - private void Init2() - { - Encrypt(ref _arg2[2], ref _arg2[1]); - Encrypt(ref _arg2[1], ref _arg2[0]); - - byte[] allBytes = [.. BitConverter.GetBytes(_arg2[0]), - .. BitConverter.GetBytes(_arg2[1]), - .. BitConverter.GetBytes(_arg2[2])]; - - UpdateHashtable(allBytes); - } - - /// - /// Lookup the value from the hashtable - /// - /// Value to lookup in the hashtable - /// Processed value through the hashtable - private uint Lookup(uint v) - { - uint a = (v >> 24) & 0xFF; - uint b = (v >> 16) & 0xFF; - uint c = (v >> 8) & 0xFF; - uint d = (v >> 0) & 0xFF; - - a = _cardHash[a + 18 + 0]; - b = _cardHash[b + 18 + 256]; - c = _cardHash[c + 18 + 512]; - d = _cardHash[d + 18 + 768]; - - return d + (c ^ (b + a)); - } - - /// - /// Update the hashtable - /// - /// Value to update the hashtable with - private void UpdateHashtable(byte[] arg1) - { - for (int j = 0; j < 18; j++) - { - uint r3 = 0; - for (int i = 0; i < 4; i++) - { - r3 <<= 8; - r3 |= arg1[(j * 4 + i) & 7]; - } - - _cardHash[j] ^= r3; - } - - uint tmp1 = 0; - uint tmp2 = 0; - for (int i = 0; i < 18; i += 2) - { - Encrypt(ref tmp1, ref tmp2); - _cardHash[i + 0] = tmp1; - _cardHash[i + 1] = tmp2; - } - for (int i = 0; i < 0x400; i += 2) - { - Encrypt(ref tmp1, ref tmp2); - _cardHash[i + 18 + 0] = tmp1; - _cardHash[i + 18 + 1] = tmp2; - } - } - - #endregion - - #endregion } } diff --git a/SabreTools.Serialization/Wrappers/WiseOverlayHeader.Extraction.cs b/SabreTools.Serialization/Wrappers/WiseOverlayHeader.Extraction.cs new file mode 100644 index 00000000..6747cc38 --- /dev/null +++ b/SabreTools.Serialization/Wrappers/WiseOverlayHeader.Extraction.cs @@ -0,0 +1,313 @@ +using System; +using System.IO; +using SabreTools.IO.Compression.Deflate; +using SabreTools.IO.Streams; +using SabreTools.Models.WiseInstaller.Actions; + +namespace SabreTools.Serialization.Wrappers +{ + public partial class WiseOverlayHeader + { + /// + /// Extract the predefined, static files defined in the header + /// + /// Output directory to write to + /// True to include debug data, false otherwise + /// True if the files extracted successfully, false otherwise + /// On success, this sets + public bool ExtractHeaderDefinedFiles(string outputDirectory, bool includeDebug) + { + lock (_dataSourceLock) + { + // Seek to the compressed data offset + _dataSource.Seek(CompressedDataOffset, SeekOrigin.Begin); + if (includeDebug) Console.WriteLine($"Beginning of header-defined files: {CompressedDataOffset}"); + + // Extract WiseColors.dib, if it exists + var expected = new DeflateInfo { InputSize = DibDeflatedSize, OutputSize = DibInflatedSize, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, "WiseColors.dib", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract WiseScript.bin + expected = new DeflateInfo { InputSize = WiseScriptDeflatedSize, OutputSize = WiseScriptInflatedSize, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, "WiseScript.bin", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract WISE0001.DLL, if it exists + expected = new DeflateInfo { InputSize = WiseDllDeflatedSize, OutputSize = -1, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "WISE0001.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract CTL3D32.DLL, if it exists + expected = new DeflateInfo { InputSize = Ctl3d32DeflatedSize, OutputSize = -1, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "CTL3D32.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract FILE0004, if it exists + expected = new DeflateInfo { InputSize = SomeData4DeflatedSize, OutputSize = -1, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE0004", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract Ocxreg32.EXE, if it exists + expected = new DeflateInfo { InputSize = RegToolDeflatedSize, OutputSize = -1, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "Ocxreg32.EXE", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract PROGRESS.DLL, if it exists + expected = new DeflateInfo { InputSize = ProgressDllDeflatedSize, OutputSize = -1, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "PROGRESS.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract FILE0007, if it exists + expected = new DeflateInfo { InputSize = SomeData7DeflatedSize, OutputSize = -1, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE0007", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract FILE0008, if it exists + expected = new DeflateInfo { InputSize = SomeData8DeflatedSize, OutputSize = -1, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE0008", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract FILE0009, if it exists + expected = new DeflateInfo { InputSize = SomeData9DeflatedSize, OutputSize = -1, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE0009", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract FILE000A, if it exists + expected = new DeflateInfo { InputSize = SomeData10DeflatedSize, OutputSize = -1, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE000A", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract install script, if it exists + expected = new DeflateInfo { InputSize = InstallScriptDeflatedSize, OutputSize = -1, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "INSTALL_SCRIPT", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + // Extract FILE000{n}.DAT, if it exists + expected = new DeflateInfo { InputSize = FinalFileDeflatedSize, OutputSize = FinalFileInflatedSize, Crc32 = 0 }; + if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE00XX.DAT", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) + return false; + + InstallerDataOffset = _dataSource.Position; + } + + return true; + } + + /// + /// Attempt to extract a file defined by a file header + /// + /// Deflate information + /// File index for automatic naming + /// Output directory to write to + /// True to include debug data, false otherwise + /// True if the file extracted successfully, false otherwise + /// Requires to be set + public ExtractionStatus ExtractFile(InstallFile obj, int index, string outputDirectory, bool includeDebug) + { + // Get expected values + var expected = new DeflateInfo + { + InputSize = obj.DeflateEnd - obj.DeflateStart, + OutputSize = obj.InflatedSize, + Crc32 = obj.Crc32, + }; + + // Perform path replacements + string filename = obj.DestinationPathname ?? $"WISE{index:X4}"; + filename = filename.Replace("%", string.Empty); + + lock (_dataSourceLock) + { + _dataSource.Seek(InstallerDataOffset + obj.DeflateStart, SeekOrigin.Begin); + return InflateWrapper.ExtractFile(_dataSource, + filename, + outputDirectory, + expected, + IsPKZIP, + includeDebug); + } + } + + /// + /// Attempt to extract a file defined by a file header + /// + /// Deflate information + /// Output directory to write to + /// True to include debug data, false otherwise + /// True if the file extracted successfully, false otherwise + /// Requires to be set + public ExtractionStatus ExtractFile(DisplayBillboard obj, string outputDirectory, bool includeDebug) + { + // Get the generated base name + string baseName = $"CustomBillboardSet_{obj.Flags:X4}-{obj.Operand_2}-{obj.Operand_3}"; + + // If there are no deflate objects + if (obj.DeflateInfo == null) + { + if (includeDebug) Console.WriteLine($"Skipping {baseName} because the deflate object array is null!"); + return ExtractionStatus.FAIL; + } + + // Loop through the values + for (int i = 0; i < obj.DeflateInfo.Length; i++) + { + // Get the deflate info object + var info = obj.DeflateInfo[i]; + + // Get expected values + var expected = new DeflateInfo + { + InputSize = info.DeflateEnd - info.DeflateStart, + OutputSize = info.InflatedSize, + Crc32 = 0, + }; + + // Perform path replacements + string filename = $"{baseName}{i:X4}"; + + lock (_dataSourceLock) + { + _dataSource.Seek(InstallerDataOffset + info.DeflateStart, SeekOrigin.Begin); + _ = InflateWrapper.ExtractFile(_dataSource, filename, outputDirectory, expected, IsPKZIP, includeDebug); + } + } + + // Always return good -- TODO: Fix this + return ExtractionStatus.GOOD; + } + + /// + /// Attempt to extract a file defined by a file header + /// + /// Deflate information + /// Output directory to write to + /// True to include debug data, false otherwise + /// True if the file extracted successfully, false otherwise + /// Requires to be set + public ExtractionStatus ExtractFile(CustomDialogSet obj, string outputDirectory, bool includeDebug) + { + // Get expected values + var expected = new DeflateInfo + { + InputSize = obj.DeflateEnd - obj.DeflateStart, + OutputSize = obj.InflatedSize, + Crc32 = 0, + }; + + // Perform path replacements + string filename = $"CustomDialogSet_{obj.DisplayVariable}-{obj.Name}"; + filename = filename.Replace("%", string.Empty); + + lock (_dataSourceLock) + { + _dataSource.Seek(InstallerDataOffset + obj.DeflateStart, SeekOrigin.Begin); + return InflateWrapper.ExtractFile(_dataSource, filename, outputDirectory, expected, IsPKZIP, includeDebug); + } + } + + /// + /// Open a potential WISE installer file and any additional files + /// + /// Input filename or base name to read from + /// True to include debug data, false otherwise + /// True if the file could be opened, false otherwise + public static bool OpenFile(string filename, bool includeDebug, out ReadOnlyCompositeStream? stream) + { + // If the file exists as-is + if (File.Exists(filename)) + { + var fileStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + stream = new ReadOnlyCompositeStream([fileStream]); + + // Debug statement + if (includeDebug) Console.WriteLine($"File {filename} was found and opened"); + + // Strip the extension and rebuild + string? directory = Path.GetDirectoryName(filename); + filename = Path.GetFileNameWithoutExtension(filename); + if (directory != null) + filename = Path.Combine(directory, filename); + } + + // If the base name was provided, try to open the associated exe + else if (File.Exists($"{filename}.EXE")) + { + var fileStream = File.Open($"{filename}.EXE", FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + stream = new ReadOnlyCompositeStream([fileStream]); + + // Debug statement + if (includeDebug) Console.WriteLine($"File {filename}.EXE was found and opened"); + } + else if (File.Exists($"{filename}.exe")) + { + var fileStream = File.Open($"{filename}.exe", FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + stream = new ReadOnlyCompositeStream([fileStream]); + + // Debug statement + if (includeDebug) Console.WriteLine($"File {filename}.exe was found and opened"); + } + + // Otherwise, the file cannot be opened + else + { + stream = null; + return false; + } + + // Get the pattern for file naming + string filePattern = string.Empty; + bool longDigits = false; + + byte fileno = 0; + bool foundStart = false; + for (; fileno < 3; fileno++) + { + if (File.Exists($"{filename}.W0{fileno}")) + { + foundStart = true; + filePattern = $"{filename}.W"; + longDigits = false; + break; + } + else if (File.Exists($"{filename}.w0{fileno}")) + { + foundStart = true; + filePattern = $"{filename}.w"; + longDigits = false; + break; + } + else if (File.Exists($"{filename}.00{fileno}")) + { + foundStart = true; + filePattern = $"{filename}."; + longDigits = true; + break; + } + } + + // If no starting part has been found + if (!foundStart) + return true; + + // Loop through and try to read all additional files + for (; ; fileno++) + { + string nextPart = longDigits ? $"{filePattern}{fileno:D3}" : $"{filePattern}{fileno:D2}"; + if (!File.Exists(nextPart)) + { + if (includeDebug) Console.WriteLine($"Part {nextPart} was not found"); + break; + } + + // Debug statement + if (includeDebug) Console.WriteLine($"Part {nextPart} was found and appended"); + + var fileStream = File.Open(nextPart, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + stream.AddStream(fileStream); + } + + return true; + } + } +} diff --git a/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs b/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs index 8f3c864d..27857b3b 100644 --- a/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs +++ b/SabreTools.Serialization/Wrappers/WiseOverlayHeader.cs @@ -1,13 +1,9 @@ -using System; using System.IO; -using SabreTools.IO.Compression.Deflate; -using SabreTools.IO.Streams; using SabreTools.Models.WiseInstaller; -using SabreTools.Models.WiseInstaller.Actions; namespace SabreTools.Serialization.Wrappers { - public class WiseOverlayHeader : WrapperBase + public partial class WiseOverlayHeader : WrapperBase { #region Descriptive Properties @@ -220,311 +216,5 @@ namespace SabreTools.Serialization.Wrappers } #endregion - - #region Extraction - - /// - /// Extract the predefined, static files defined in the header - /// - /// Output directory to write to - /// True to include debug data, false otherwise - /// True if the files extracted successfully, false otherwise - /// On success, this sets - public bool ExtractHeaderDefinedFiles(string outputDirectory, bool includeDebug) - { - lock (_dataSourceLock) - { - // Seek to the compressed data offset - _dataSource.Seek(CompressedDataOffset, SeekOrigin.Begin); - if (includeDebug) Console.WriteLine($"Beginning of header-defined files: {CompressedDataOffset}"); - - // Extract WiseColors.dib, if it exists - var expected = new DeflateInfo { InputSize = DibDeflatedSize, OutputSize = DibInflatedSize, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, "WiseColors.dib", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract WiseScript.bin - expected = new DeflateInfo { InputSize = WiseScriptDeflatedSize, OutputSize = WiseScriptInflatedSize, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, "WiseScript.bin", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract WISE0001.DLL, if it exists - expected = new DeflateInfo { InputSize = WiseDllDeflatedSize, OutputSize = -1, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "WISE0001.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract CTL3D32.DLL, if it exists - expected = new DeflateInfo { InputSize = Ctl3d32DeflatedSize, OutputSize = -1, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "CTL3D32.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract FILE0004, if it exists - expected = new DeflateInfo { InputSize = SomeData4DeflatedSize, OutputSize = -1, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE0004", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract Ocxreg32.EXE, if it exists - expected = new DeflateInfo { InputSize = RegToolDeflatedSize, OutputSize = -1, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "Ocxreg32.EXE", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract PROGRESS.DLL, if it exists - expected = new DeflateInfo { InputSize = ProgressDllDeflatedSize, OutputSize = -1, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "PROGRESS.DLL", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract FILE0007, if it exists - expected = new DeflateInfo { InputSize = SomeData7DeflatedSize, OutputSize = -1, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE0007", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract FILE0008, if it exists - expected = new DeflateInfo { InputSize = SomeData8DeflatedSize, OutputSize = -1, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE0008", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract FILE0009, if it exists - expected = new DeflateInfo { InputSize = SomeData9DeflatedSize, OutputSize = -1, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE0009", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract FILE000A, if it exists - expected = new DeflateInfo { InputSize = SomeData10DeflatedSize, OutputSize = -1, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE000A", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract install script, if it exists - expected = new DeflateInfo { InputSize = InstallScriptDeflatedSize, OutputSize = -1, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "INSTALL_SCRIPT", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - // Extract FILE000{n}.DAT, if it exists - expected = new DeflateInfo { InputSize = FinalFileDeflatedSize, OutputSize = FinalFileInflatedSize, Crc32 = 0 }; - if (InflateWrapper.ExtractFile(_dataSource, IsPKZIP ? null : "FILE00XX.DAT", outputDirectory, expected, IsPKZIP, includeDebug) == ExtractionStatus.FAIL) - return false; - - InstallerDataOffset = _dataSource.Position; - } - - return true; - } - - /// - /// Attempt to extract a file defined by a file header - /// - /// Deflate information - /// File index for automatic naming - /// Output directory to write to - /// True to include debug data, false otherwise - /// True if the file extracted successfully, false otherwise - /// Requires to be set - public ExtractionStatus ExtractFile(InstallFile obj, int index, string outputDirectory, bool includeDebug) - { - // Get expected values - var expected = new DeflateInfo - { - InputSize = obj.DeflateEnd - obj.DeflateStart, - OutputSize = obj.InflatedSize, - Crc32 = obj.Crc32, - }; - - // Perform path replacements - string filename = obj.DestinationPathname ?? $"WISE{index:X4}"; - filename = filename.Replace("%", string.Empty); - - lock (_dataSourceLock) - { - _dataSource.Seek(InstallerDataOffset + obj.DeflateStart, SeekOrigin.Begin); - return InflateWrapper.ExtractFile(_dataSource, - filename, - outputDirectory, - expected, - IsPKZIP, - includeDebug); - } - } - - /// - /// Attempt to extract a file defined by a file header - /// - /// Deflate information - /// Output directory to write to - /// True to include debug data, false otherwise - /// True if the file extracted successfully, false otherwise - /// Requires to be set - public ExtractionStatus ExtractFile(DisplayBillboard obj, string outputDirectory, bool includeDebug) - { - // Get the generated base name - string baseName = $"CustomBillboardSet_{obj.Flags:X4}-{obj.Operand_2}-{obj.Operand_3}"; - - // If there are no deflate objects - if (obj.DeflateInfo == null) - { - if (includeDebug) Console.WriteLine($"Skipping {baseName} because the deflate object array is null!"); - return ExtractionStatus.FAIL; - } - - // Loop through the values - for (int i = 0; i < obj.DeflateInfo.Length; i++) - { - // Get the deflate info object - var info = obj.DeflateInfo[i]; - - // Get expected values - var expected = new DeflateInfo - { - InputSize = info.DeflateEnd - info.DeflateStart, - OutputSize = info.InflatedSize, - Crc32 = 0, - }; - - // Perform path replacements - string filename = $"{baseName}{i:X4}"; - - lock (_dataSourceLock) - { - _dataSource.Seek(InstallerDataOffset + info.DeflateStart, SeekOrigin.Begin); - _ = InflateWrapper.ExtractFile(_dataSource, filename, outputDirectory, expected, IsPKZIP, includeDebug); - } - } - - // Always return good -- TODO: Fix this - return ExtractionStatus.GOOD; - } - - /// - /// Attempt to extract a file defined by a file header - /// - /// Deflate information - /// Output directory to write to - /// True to include debug data, false otherwise - /// True if the file extracted successfully, false otherwise - /// Requires to be set - public ExtractionStatus ExtractFile(CustomDialogSet obj, string outputDirectory, bool includeDebug) - { - // Get expected values - var expected = new DeflateInfo - { - InputSize = obj.DeflateEnd - obj.DeflateStart, - OutputSize = obj.InflatedSize, - Crc32 = 0, - }; - - // Perform path replacements - string filename = $"CustomDialogSet_{obj.DisplayVariable}-{obj.Name}"; - filename = filename.Replace("%", string.Empty); - - lock (_dataSourceLock) - { - _dataSource.Seek(InstallerDataOffset + obj.DeflateStart, SeekOrigin.Begin); - return InflateWrapper.ExtractFile(_dataSource, filename, outputDirectory, expected, IsPKZIP, includeDebug); - } - } - - /// - /// Open a potential WISE installer file and any additional files - /// - /// Input filename or base name to read from - /// True to include debug data, false otherwise - /// True if the file could be opened, false otherwise - public static bool OpenFile(string filename, bool includeDebug, out ReadOnlyCompositeStream? stream) - { - // If the file exists as-is - if (File.Exists(filename)) - { - var fileStream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - stream = new ReadOnlyCompositeStream([fileStream]); - - // Debug statement - if (includeDebug) Console.WriteLine($"File {filename} was found and opened"); - - // Strip the extension and rebuild - string? directory = Path.GetDirectoryName(filename); - filename = Path.GetFileNameWithoutExtension(filename); - if (directory != null) - filename = Path.Combine(directory, filename); - } - - // If the base name was provided, try to open the associated exe - else if (File.Exists($"{filename}.EXE")) - { - var fileStream = File.Open($"{filename}.EXE", FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - stream = new ReadOnlyCompositeStream([fileStream]); - - // Debug statement - if (includeDebug) Console.WriteLine($"File {filename}.EXE was found and opened"); - } - else if (File.Exists($"{filename}.exe")) - { - var fileStream = File.Open($"{filename}.exe", FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - stream = new ReadOnlyCompositeStream([fileStream]); - - // Debug statement - if (includeDebug) Console.WriteLine($"File {filename}.exe was found and opened"); - } - - // Otherwise, the file cannot be opened - else - { - stream = null; - return false; - } - - // Get the pattern for file naming - string filePattern = string.Empty; - bool longDigits = false; - - byte fileno = 0; - bool foundStart = false; - for (; fileno < 3; fileno++) - { - if (File.Exists($"{filename}.W0{fileno}")) - { - foundStart = true; - filePattern = $"{filename}.W"; - longDigits = false; - break; - } - else if (File.Exists($"{filename}.w0{fileno}")) - { - foundStart = true; - filePattern = $"{filename}.w"; - longDigits = false; - break; - } - else if (File.Exists($"{filename}.00{fileno}")) - { - foundStart = true; - filePattern = $"{filename}."; - longDigits = true; - break; - } - } - - // If no starting part has been found - if (!foundStart) - return true; - - // Loop through and try to read all additional files - for (; ; fileno++) - { - string nextPart = longDigits ? $"{filePattern}{fileno:D3}" : $"{filePattern}{fileno:D2}"; - if (!File.Exists(nextPart)) - { - if (includeDebug) Console.WriteLine($"Part {nextPart} was not found"); - break; - } - - // Debug statement - if (includeDebug) Console.WriteLine($"Part {nextPart} was found and appended"); - - var fileStream = File.Open(nextPart, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - stream.AddStream(fileStream); - } - - return true; - } - - #endregion } }