diff --git a/BurnOutSharp.Wrappers/PortableExecutable.cs b/BurnOutSharp.Wrappers/PortableExecutable.cs
index 5235b1ef..57f3e635 100644
--- a/BurnOutSharp.Wrappers/PortableExecutable.cs
+++ b/BurnOutSharp.Wrappers/PortableExecutable.cs
@@ -395,7 +395,7 @@ namespace BurnOutSharp.Wrappers
}
///
- /// Array of sanitized section names
+ /// Sanitized section names
///
public string[] SectionNames
{
@@ -424,6 +424,30 @@ namespace BurnOutSharp.Wrappers
}
}
+ ///
+ /// Stub executable data, if it exists
+ ///
+ public byte[] StubExecutableData
+ {
+ get
+ {
+ lock (_stubExecutableDataLock)
+ {
+ // If we already have cached data, just use that immediately
+ if (_stubExecutableData != null)
+ return _stubExecutableData;
+
+ // Populate the raw stub executable data based on the source
+ int endOfStubHeader = 0x40;
+ int lengthOfStubExecutableData = (int)_executable.Stub.Header.NewExeHeaderAddr - endOfStubHeader;
+ _stubExecutableData = ReadFromDataSource(endOfStubHeader, lengthOfStubExecutableData);
+
+ // Cache and return the stub executable data, even if null
+ return _stubExecutableData;
+ }
+ }
+ }
+
///
/// Dictionary of resource data
///
@@ -585,15 +609,20 @@ namespace BurnOutSharp.Wrappers
private byte[] _overlayData = null;
///
- /// Array of sanitized section names
+ /// Stub executable data, if it exists
+ ///
+ private byte[] _stubExecutableData = null;
+
+ ///
+ /// Sanitized section names
///
private string[] _sectionNames = null;
///
/// Cached raw section data
///
- private readonly Dictionary _rawSectionData = new Dictionary();
-
+ private byte[][] _sectionData = null;
+
///
/// Cached resource data
///
@@ -618,15 +647,20 @@ namespace BurnOutSharp.Wrappers
///
private readonly object _overlayDataLock = new object();
+ ///
+ /// Lock object for concurrent modifications on
+ ///
+ private readonly object _stubExecutableDataLock = new object();
+
///
/// Lock object for concurrent modifications on
///
private readonly object _sectionNamesLock = new object();
///
- /// Lock object for concurrent modifications on
+ /// Lock object for concurrent modifications on
///
- private readonly object _rawSectionsLock = new object();
+ private readonly object _sectionDataLock = new object();
///
/// Lock object for concurrent modifications on
@@ -635,15 +669,6 @@ namespace BurnOutSharp.Wrappers
#endregion
- #region Constants
-
- ///
- /// Special string representing the MS-DOS executable data "section"
- ///
- public const string MSDOSExecutableData = "[MS-DOS Executable Data]";
-
- #endregion
-
#region Constructors
///
@@ -699,235 +724,6 @@ namespace BurnOutSharp.Wrappers
// TODO: Cache all certificate objects
- ///
- /// Determine if a section is contained within the section table
- ///
- /// Name of the section to check for
- /// True to enable exact matching of names, false for starts-with
- /// True if the section is in the executable, false otherwise
- public bool ContainsSection(string sectionName, bool exact = false)
- {
- // Get all section names first
- if (SectionNames == null)
- return false;
-
- // If we're checking exactly, return only exact matches
- if (exact)
- return SectionNames.Any(n => n.Equals(sectionName));
-
- // Otherwise, check if section name starts with the value
- else
- return SectionNames.Any(n => n.StartsWith(sectionName));
- }
-
- ///
- /// Get the first section based on name, if possible
- ///
- /// Name of the section to check for
- /// True to enable exact matching of names, false for starts-with
- /// Section data on success, null on error
- public Models.PortableExecutable.SectionHeader GetFirstSection(string sectionName, bool exact = false)
- {
- // If we have no sections, we can't do anything
- if (_executable.SectionTable == null || !SectionTable.Any())
- return null;
-
- // TODO: Handle long section names with leading `/`
-
- // If we're checking exactly, return only exact matches
- if (exact)
- return SectionTable.FirstOrDefault(s => Encoding.UTF8.GetString(s.Name).Equals(sectionName));
-
- // Otherwise, check if section name starts with the value
- else
- return SectionTable.FirstOrDefault(s => Encoding.UTF8.GetString(s.Name).StartsWith(sectionName));
- }
-
- ///
- /// Get the last section based on name, if possible
- ///
- /// Name of the section to check for
- /// True to enable exact matching of names, false for starts-with
- /// Section data on success, null on error
- public Models.PortableExecutable.SectionHeader GetLastSection(string sectionName, bool exact = false)
- {
- // If we have no sections, we can't do anything
- if (SectionTable == null || !SectionTable.Any())
- return null;
-
- // TODO: Handle long section names with leading `/`
-
- // If we're checking exactly, return only exact matches (with nulls trimmed)
- if (exact)
- return SectionTable.LastOrDefault(s => Encoding.UTF8.GetString(s.Name).Equals(sectionName));
-
- // Otherwise, check if section name starts with the value
- else
- return SectionTable.LastOrDefault(s => Encoding.UTF8.GetString(s.Name).StartsWith(sectionName));
- }
-
- ///
- /// Get raw section data from the source file
- ///
- /// Name of the section to get raw data for
- /// Byte array representing the data, null on error
- public byte[] GetRawSection(string sectionName)
- {
- // If we have an invalid section name
- if (string.IsNullOrEmpty(sectionName))
- return null;
-
- // Validate the data source
- if (!DataSourceIsValid())
- return null;
-
- // We have a special case for the MS-DOS stub executable data
- if (sectionName == MSDOSExecutableData)
- {
- lock (_rawSectionsLock)
- {
- // If we already have cached data, just use that immediately
- if (_rawSectionData.ContainsKey(sectionName))
- return _rawSectionData[sectionName];
-
- // Populate the raw stub executable data based on the source
- int endOfStubHeader = 0x40;
- int lengthOfStubExecutableData = (int)_executable.Stub.Header.NewExeHeaderAddr - endOfStubHeader;
- byte[] sectionData = ReadFromDataSource(endOfStubHeader, lengthOfStubExecutableData);
-
- // Cache and return the stub executable data, even if null
- _rawSectionData[sectionName] = sectionData;
- return sectionData;
- }
- }
-
- // Get the section index from the array
- int sectionIndex = Array.IndexOf(SectionNames, sectionName);
- if (sectionIndex < 0)
- return null;
-
- // Get the section data from the table
- var section = _executable.SectionTable[sectionIndex];
- uint sectionAddress = section.VirtualAddress.ConvertVirtualAddress(_executable.SectionTable);
- if (sectionAddress == 0)
- return null;
-
- uint sectionSize = section.SizeOfRawData;
-
- // TODO: Use section name and index to combat duplicates
- lock (_rawSectionsLock)
- {
- // If we already have cached data, just use that immediately
- if (_rawSectionData.ContainsKey(sectionName))
- return _rawSectionData[sectionName];
-
- // Populate the raw section data based on the source
- byte[] sectionData = ReadFromDataSource((int)sectionAddress, (int)sectionSize);
-
- // Cache and return the section data, even if null
- _rawSectionData[sectionName] = sectionData;
- return sectionData;
- }
- }
-
- ///
- /// Find dialog box resources by title
- ///
- /// Dialog box title to check for
- /// Enumerable of matching resources
- public IEnumerable FindDialogByTitle(string title)
- {
- return ResourceData.Select(r => r.Value)
- .Select(r => r as Models.PortableExecutable.DialogBoxResource)
- .Where(d => d != null)
- .Where(d =>
- {
- return (d.DialogTemplate?.TitleResource?.Contains(title) ?? false)
- || (d.ExtendedDialogTemplate?.TitleResource?.Contains(title) ?? false);
- });
- }
-
- ///
- /// Find dialog box resources by contained item title
- ///
- /// Dialog box item title to check for
- /// Enumerable of matching resources
- public IEnumerable FindDialogBoxByItemTitle(string title)
- {
- return ResourceData.Select(r => r.Value)
- .Select(r => r as Models.PortableExecutable.DialogBoxResource)
- .Where(d => d != null)
- .Where(d =>
- {
- if (d.DialogItemTemplates != null)
- {
- return d.DialogItemTemplates
- .Where(dit => dit?.TitleResource != null)
- .Any(dit => dit.TitleResource.Contains(title));
- }
- else if (d.ExtendedDialogItemTemplates != null)
- {
- return d.ExtendedDialogItemTemplates
- .Where(edit => edit?.TitleResource != null)
- .Any(edit => edit.TitleResource.Contains(title));
- }
-
- return false;
- });
- }
-
- ///
- /// Find unparsed resources by type name
- ///
- /// Type name to check for
- /// Enumerable of matching resources
- public IEnumerable FindResourceByNamedType(string typeName)
- {
- return ResourceData.Where(kvp => kvp.Key.Contains(typeName))
- .Select(kvp => kvp.Value as byte[])
- .Where(b => b != null);
- }
-
- ///
- /// Find unparsed resources by string value
- ///
- /// String value to check for
- /// Enumerable of matching resources
- public IEnumerable FindGenericResource(string value)
- {
- return ResourceData.Select(r => r.Value)
- .Select(r => r as byte[])
- .Where(b => b != null)
- .Where(b =>
- {
- try
- {
- string arrayAsASCII = Encoding.ASCII.GetString(b);
- if (arrayAsASCII.Contains(value))
- return true;
- }
- catch { }
-
- try
- {
- string arrayAsUTF8 = Encoding.UTF8.GetString(b);
- if (arrayAsUTF8.Contains(value))
- return true;
- }
- catch { }
-
- try
- {
- string arrayAsUnicode = Encoding.Unicode.GetString(b);
- if (arrayAsUnicode.Contains(value))
- return true;
- }
- catch { }
-
- return true;
- });
- }
-
///
/// Get the version info string associated with a key, if possible
///
@@ -2511,6 +2307,108 @@ namespace BurnOutSharp.Wrappers
#endregion
+ #region Resource Data
+
+ ///
+ /// Find dialog box resources by title
+ ///
+ /// Dialog box title to check for
+ /// Enumerable of matching resources
+ public IEnumerable FindDialogByTitle(string title)
+ {
+ return ResourceData.Select(r => r.Value)
+ .Select(r => r as Models.PortableExecutable.DialogBoxResource)
+ .Where(d => d != null)
+ .Where(d =>
+ {
+ return (d.DialogTemplate?.TitleResource?.Contains(title) ?? false)
+ || (d.ExtendedDialogTemplate?.TitleResource?.Contains(title) ?? false);
+ });
+ }
+
+ ///
+ /// Find dialog box resources by contained item title
+ ///
+ /// Dialog box item title to check for
+ /// Enumerable of matching resources
+ public IEnumerable FindDialogBoxByItemTitle(string title)
+ {
+ return ResourceData.Select(r => r.Value)
+ .Select(r => r as Models.PortableExecutable.DialogBoxResource)
+ .Where(d => d != null)
+ .Where(d =>
+ {
+ if (d.DialogItemTemplates != null)
+ {
+ return d.DialogItemTemplates
+ .Where(dit => dit?.TitleResource != null)
+ .Any(dit => dit.TitleResource.Contains(title));
+ }
+ else if (d.ExtendedDialogItemTemplates != null)
+ {
+ return d.ExtendedDialogItemTemplates
+ .Where(edit => edit?.TitleResource != null)
+ .Any(edit => edit.TitleResource.Contains(title));
+ }
+
+ return false;
+ });
+ }
+
+ ///
+ /// Find unparsed resources by type name
+ ///
+ /// Type name to check for
+ /// Enumerable of matching resources
+ public IEnumerable FindResourceByNamedType(string typeName)
+ {
+ return ResourceData.Where(kvp => kvp.Key.Contains(typeName))
+ .Select(kvp => kvp.Value as byte[])
+ .Where(b => b != null);
+ }
+
+ ///
+ /// Find unparsed resources by string value
+ ///
+ /// String value to check for
+ /// Enumerable of matching resources
+ public IEnumerable FindGenericResource(string value)
+ {
+ return ResourceData.Select(r => r.Value)
+ .Select(r => r as byte[])
+ .Where(b => b != null)
+ .Where(b =>
+ {
+ try
+ {
+ string arrayAsASCII = Encoding.ASCII.GetString(b);
+ if (arrayAsASCII.Contains(value))
+ return true;
+ }
+ catch { }
+
+ try
+ {
+ string arrayAsUTF8 = Encoding.UTF8.GetString(b);
+ if (arrayAsUTF8.Contains(value))
+ return true;
+ }
+ catch { }
+
+ try
+ {
+ string arrayAsUnicode = Encoding.Unicode.GetString(b);
+ if (arrayAsUnicode.Contains(value))
+ return true;
+ }
+ catch { }
+
+ return true;
+ });
+ }
+
+ #endregion
+
#region Resource Parsing
///
@@ -2645,5 +2543,225 @@ namespace BurnOutSharp.Wrappers
}
#endregion
+
+ #region Sections
+
+ ///
+ /// Determine if a section is contained within the section table
+ ///
+ /// Name of the section to check for
+ /// True to enable exact matching of names, false for starts-with
+ /// True if the section is in the executable, false otherwise
+ public bool ContainsSection(string sectionName, bool exact = false)
+ {
+ // Get all section names first
+ if (SectionNames == null)
+ return false;
+
+ // If we're checking exactly, return only exact matches
+ if (exact)
+ return SectionNames.Any(n => n.Equals(sectionName));
+
+ // Otherwise, check if section name starts with the value
+ else
+ return SectionNames.Any(n => n.StartsWith(sectionName));
+ }
+
+ ///
+ /// Get the first section based on name, if possible
+ ///
+ /// Name of the section to check for
+ /// True to enable exact matching of names, false for starts-with
+ /// Section data on success, null on error
+ public Models.PortableExecutable.SectionHeader GetFirstSection(string name, bool exact = false)
+ {
+ // If we have no sections
+ if (_executable.SectionTable == null || !SectionTable.Any())
+ return null;
+
+ // If the section doesn't exist
+ if (!ContainsSection(name, exact))
+ return null;
+
+ // Get the first index of the section
+ int index = Array.IndexOf(SectionNames, name);
+ if (index == -1)
+ return null;
+
+ // Return the section
+ return SectionTable[index];
+ }
+
+ ///
+ /// Get the last section based on name, if possible
+ ///
+ /// Name of the section to check for
+ /// True to enable exact matching of names, false for starts-with
+ /// Section data on success, null on error
+ public Models.PortableExecutable.SectionHeader GetLastSection(string name, bool exact = false)
+ {
+ // If we have no sections
+ if (_executable.SectionTable == null || !SectionTable.Any())
+ return null;
+
+ // If the section doesn't exist
+ if (!ContainsSection(name, exact))
+ return null;
+
+ // Get the last index of the section
+ int index = Array.LastIndexOf(SectionNames, name);
+ if (index == -1)
+ return null;
+
+ // Return the section
+ return SectionTable[index];
+ }
+
+ ///
+ /// Get the section based on index, if possible
+ ///
+ /// Index of the section to check for
+ /// Section data on success, null on error
+ public Models.PortableExecutable.SectionHeader GetSection(int index)
+ {
+ // If we have no sections
+ if (_executable.SectionTable == null || !SectionTable.Any())
+ return null;
+
+ // If the section doesn't exist
+ if (index < 0 || index >= SectionTable.Length)
+ return null;
+
+ // Return the section
+ return SectionTable[index];
+ }
+
+ ///
+ /// Get the first section data based on name, if possible
+ ///
+ /// Name of the section to check for
+ /// True to enable exact matching of names, false for starts-with
+ /// Section data on success, null on error
+ public byte[] GetFirstSectionData(string name, bool exact = false)
+ {
+ // If we have no sections
+ if (_executable.SectionTable == null || !SectionTable.Any())
+ return null;
+
+ // If the section doesn't exist
+ if (!ContainsSection(name, exact))
+ return null;
+
+ // Get the first index of the section
+ int index = Array.IndexOf(SectionNames, name);
+ if (index == -1)
+ return null;
+
+ // Get the section data from the table
+ var section = _executable.SectionTable[index];
+ uint address = section.VirtualAddress.ConvertVirtualAddress(_executable.SectionTable);
+ if (address == 0)
+ return null;
+
+ // Set the section size
+ uint size = section.SizeOfRawData;
+ lock (_sectionDataLock)
+ {
+ // If we already have cached data, just use that immediately
+ if (_sectionData[index] != null)
+ return _sectionData[index];
+
+ // Populate the raw section data based on the source
+ byte[] sectionData = ReadFromDataSource((int)address, (int)size);
+
+ // Cache and return the section data, even if null
+ _sectionData[index] = sectionData;
+ return sectionData;
+ }
+ }
+
+ ///
+ /// Get the last section data based on name, if possible
+ ///
+ /// Name of the section to check for
+ /// True to enable exact matching of names, false for starts-with
+ /// Section data on success, null on error
+ public byte[] GetLastSectionData(string name, bool exact = false)
+ {
+ // If we have no sections
+ if (_executable.SectionTable == null || !SectionTable.Any())
+ return null;
+
+ // If the section doesn't exist
+ if (!ContainsSection(name, exact))
+ return null;
+
+ // Get the last index of the section
+ int index = Array.LastIndexOf(SectionNames, name);
+ if (index == -1)
+ return null;
+
+ // Get the section data from the table
+ var section = _executable.SectionTable[index];
+ uint address = section.VirtualAddress.ConvertVirtualAddress(_executable.SectionTable);
+ if (address == 0)
+ return null;
+
+ // Set the section size
+ uint size = section.SizeOfRawData;
+ lock (_sectionDataLock)
+ {
+ // If we already have cached data, just use that immediately
+ if (_sectionData[index] != null)
+ return _sectionData[index];
+
+ // Populate the raw section data based on the source
+ byte[] sectionData = ReadFromDataSource((int)address, (int)size);
+
+ // Cache and return the section data, even if null
+ _sectionData[index] = sectionData;
+ return sectionData;
+ }
+ }
+
+ ///
+ /// Get the section data based on index, if possible
+ ///
+ /// Index of the section to check for
+ /// Section data on success, null on error
+ public byte[] GetSectionData(int index)
+ {
+ // If we have no sections
+ if (_executable.SectionTable == null || !SectionTable.Any())
+ return null;
+
+ // If the section doesn't exist
+ if (index < 0 || index >= SectionTable.Length)
+ return null;
+
+ // Get the section data from the table
+ var section = _executable.SectionTable[index];
+ uint address = section.VirtualAddress.ConvertVirtualAddress(_executable.SectionTable);
+ if (address == 0)
+ return null;
+
+ // Set the section size
+ uint size = section.SizeOfRawData;
+ lock (_sectionDataLock)
+ {
+ // If we already have cached data, just use that immediately
+ if (_sectionData.ContainsKey(index))
+ return _sectionData[index];
+
+ // Populate the raw section data based on the source
+ byte[] sectionData = ReadFromDataSource((int)address, (int)size);
+
+ // Cache and return the section data, even if null
+ _sectionData[index] = sectionData;
+ return sectionData;
+ }
+ }
+
+ #endregion
}
}
\ No newline at end of file