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
}
}