diff --git a/SabreTools.Library/Skippers/Skipper.cs b/SabreTools.Library/Skippers/Skipper.cs index 7c60ca15..e1c652ac 100644 --- a/SabreTools.Library/Skippers/Skipper.cs +++ b/SabreTools.Library/Skippers/Skipper.cs @@ -19,528 +19,547 @@ using Stream = System.IO.Stream; namespace SabreTools.Library.Skippers { - public class Skipper - { - #region Fields + public class Skipper + { + #region Fields - public string Name; - public string Author; - public string Version; - public List Rules; - public string SourceFile; + /// + /// Skipper name + /// + public string Name { get; set; } - // Local paths - public static string LocalPath = Path.Combine(Globals.ExeDir, "Skippers") + Path.DirectorySeparatorChar; + /// + /// Author names + /// + public string Author { get; set; } - // Header skippers represented by a list of skipper objects - private static List _list; - public static List List - { - get - { - if (_list == null || _list.Count == 0) - { - PopulateSkippers(); - } - return _list; - } - } + /// + /// File version + /// + public string Version { get; set; } - #endregion + /// + /// Set of all rules in the skipper + /// + public List Rules { get; set; } - #region Constructors + /// + /// Filename the skipper lives in + /// + public string SourceFile { get; set; } - /// - /// Create an empty Skipper object - /// - public Skipper() - { - Name = ""; - Author = ""; - Version = ""; - Rules = new List(); - SourceFile = ""; - } + // Local paths + public static string LocalPath = Path.Combine(Globals.ExeDir, "Skippers") + Path.DirectorySeparatorChar; - /// - /// Create a Skipper object parsed from an input file - /// - /// Name of the file to parse - public Skipper(string filename) - { - Rules = new List(); - SourceFile = Path.GetFileNameWithoutExtension(filename); + // Header skippers represented by a list of skipper objects + private static List _list; + public static List List + { + get + { + if (_list == null || _list.Count == 0) + { + PopulateSkippers(); + } + return _list; + } + } - Logger logger = new Logger(); - XmlReader xtr = Utilities.GetXmlTextReader(filename); + #endregion - if (xtr == null) - { - return; - } + #region Constructors - bool valid = false; - xtr.MoveToContent(); - while (!xtr.EOF) - { - if (xtr.NodeType != XmlNodeType.Element) - { - xtr.Read(); - } + /// + /// Create an empty Skipper object + /// + public Skipper() + { + Name = ""; + Author = ""; + Version = ""; + Rules = new List(); + SourceFile = ""; + } - switch (xtr.Name.ToLowerInvariant()) - { - case "detector": - valid = true; - xtr.Read(); - break; - case "name": - Name = xtr.ReadElementContentAsString(); - break; - case "author": - Author = xtr.ReadElementContentAsString(); - break; - case "version": - Version = xtr.ReadElementContentAsString(); - break; - case "rule": - // Get the information from the rule first - SkipperRule rule = new SkipperRule - { - StartOffset = null, - EndOffset = null, - Operation = HeaderSkipOperation.None, - Tests = new List(), - SourceFile = Path.GetFileNameWithoutExtension(filename), - }; + /// + /// Create a Skipper object parsed from an input file + /// + /// Name of the file to parse + public Skipper(string filename) + { + Rules = new List(); + SourceFile = Path.GetFileNameWithoutExtension(filename); - if (xtr.GetAttribute("start_offset") != null) - { - string offset = xtr.GetAttribute("start_offset"); - if (offset.ToLowerInvariant() == "eof") - { - rule.StartOffset = null; - } - else - { - rule.StartOffset = Convert.ToInt64(offset, 16); - } - } - if (xtr.GetAttribute("end_offset") != null) - { - string offset = xtr.GetAttribute("end_offset"); - if (offset.ToLowerInvariant() == "eof") - { - rule.EndOffset = null; - } - else - { - rule.EndOffset = Convert.ToInt64(offset, 16); - } - } - if (xtr.GetAttribute("operation") != null) - { - string operation = xtr.GetAttribute("operation"); - switch (operation.ToLowerInvariant()) - { - case "bitswap": - rule.Operation = HeaderSkipOperation.Bitswap; - break; - case "byteswap": - rule.Operation = HeaderSkipOperation.Byteswap; - break; - case "wordswap": - rule.Operation = HeaderSkipOperation.Wordswap; - break; - } - } + Logger logger = new Logger(); + XmlReader xtr = Utilities.GetXmlTextReader(filename); - // Now read the individual tests into the Rule - XmlReader subreader = xtr.ReadSubtree(); + if (xtr == null) + { + return; + } - if (subreader != null) - { - while (!subreader.EOF) - { - if (subreader.NodeType != XmlNodeType.Element) - { - subreader.Read(); - } + bool valid = false; + xtr.MoveToContent(); + while (!xtr.EOF) + { + if (xtr.NodeType != XmlNodeType.Element) + { + xtr.Read(); + } - // Get the test type - SkipperTest test = new SkipperTest - { - Offset = 0, - Value = new byte[0], - Result = true, - Mask = new byte[0], - Size = 0, - Operator = HeaderSkipTestFileOperator.Equal, - }; - switch (subreader.Name.ToLowerInvariant()) - { - case "data": - test.Type = HeaderSkipTest.Data; - break; - case "or": - test.Type = HeaderSkipTest.Or; - break; - case "xor": - test.Type = HeaderSkipTest.Xor; - break; - case "and": - test.Type = HeaderSkipTest.And; - break; - case "file": - test.Type = HeaderSkipTest.File; - break; - default: - subreader.Read(); - break; - } + switch (xtr.Name.ToLowerInvariant()) + { + case "detector": + valid = true; + xtr.Read(); + break; + case "name": + Name = xtr.ReadElementContentAsString(); + break; + case "author": + Author = xtr.ReadElementContentAsString(); + break; + case "version": + Version = xtr.ReadElementContentAsString(); + break; + case "rule": + // Get the information from the rule first + SkipperRule rule = new SkipperRule + { + StartOffset = null, + EndOffset = null, + Operation = HeaderSkipOperation.None, + Tests = new List(), + SourceFile = Path.GetFileNameWithoutExtension(filename), + }; - // Now populate all the parts that we can - if (subreader.GetAttribute("offset") != null) - { - string offset = subreader.GetAttribute("offset"); - if (offset.ToLowerInvariant() == "eof") - { - test.Offset = null; - } - else - { - test.Offset = Convert.ToInt64(offset, 16); - } - } - if (subreader.GetAttribute("value") != null) - { - string value = subreader.GetAttribute("value"); + if (xtr.GetAttribute("start_offset") != null) + { + string offset = xtr.GetAttribute("start_offset"); + if (offset.ToLowerInvariant() == "eof") + { + rule.StartOffset = null; + } + else + { + rule.StartOffset = Convert.ToInt64(offset, 16); + } + } + if (xtr.GetAttribute("end_offset") != null) + { + string offset = xtr.GetAttribute("end_offset"); + if (offset.ToLowerInvariant() == "eof") + { + rule.EndOffset = null; + } + else + { + rule.EndOffset = Convert.ToInt64(offset, 16); + } + } + if (xtr.GetAttribute("operation") != null) + { + string operation = xtr.GetAttribute("operation"); + switch (operation.ToLowerInvariant()) + { + case "bitswap": + rule.Operation = HeaderSkipOperation.Bitswap; + break; + case "byteswap": + rule.Operation = HeaderSkipOperation.Byteswap; + break; + case "wordswap": + rule.Operation = HeaderSkipOperation.Wordswap; + break; + } + } - // http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array - test.Value = new byte[value.Length / 2]; - for (int index = 0; index < test.Value.Length; index++) - { - string byteValue = value.Substring(index * 2, 2); - test.Value[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); - } - } - if (subreader.GetAttribute("result") != null) - { - string result = subreader.GetAttribute("result"); - switch (result.ToLowerInvariant()) - { - case "false": - test.Result = false; - break; - case "true": - default: - test.Result = true; - break; - } - } - if (subreader.GetAttribute("mask") != null) - { - string mask = subreader.GetAttribute("mask"); + // Now read the individual tests into the Rule + XmlReader subreader = xtr.ReadSubtree(); - // http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array - test.Mask = new byte[mask.Length / 2]; - for (int index = 0; index < test.Mask.Length; index++) - { - string byteValue = mask.Substring(index * 2, 2); - test.Mask[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); - } - } - if (subreader.GetAttribute("size") != null) - { - string size = subreader.GetAttribute("size"); - if (size.ToLowerInvariant() == "po2") - { - test.Size = null; - } - else - { - test.Size = Convert.ToInt64(size, 16); - } - } - if (subreader.GetAttribute("operator") != null) - { - string oper = subreader.GetAttribute("operator"); - switch (oper.ToLowerInvariant()) - { - case "less": - test.Operator = HeaderSkipTestFileOperator.Less; - break; - case "greater": - test.Operator = HeaderSkipTestFileOperator.Greater; - break; - case "equal": - default: - test.Operator = HeaderSkipTestFileOperator.Equal; - break; - } - } + if (subreader != null) + { + while (!subreader.EOF) + { + if (subreader.NodeType != XmlNodeType.Element) + { + subreader.Read(); + } - // Add the created test to the rule - rule.Tests.Add(test); - subreader.Read(); - } - } + // Get the test type + SkipperTest test = new SkipperTest + { + Offset = 0, + Value = new byte[0], + Result = true, + Mask = new byte[0], + Size = 0, + Operator = HeaderSkipTestFileOperator.Equal, + }; + switch (subreader.Name.ToLowerInvariant()) + { + case "data": + test.Type = HeaderSkipTest.Data; + break; + case "or": + test.Type = HeaderSkipTest.Or; + break; + case "xor": + test.Type = HeaderSkipTest.Xor; + break; + case "and": + test.Type = HeaderSkipTest.And; + break; + case "file": + test.Type = HeaderSkipTest.File; + break; + default: + subreader.Read(); + break; + } - // Add the created rule to the skipper - Rules.Add(rule); - xtr.Skip(); - break; - default: - xtr.Read(); - break; - } - } + // Now populate all the parts that we can + if (subreader.GetAttribute("offset") != null) + { + string offset = subreader.GetAttribute("offset"); + if (offset.ToLowerInvariant() == "eof") + { + test.Offset = null; + } + else + { + test.Offset = Convert.ToInt64(offset, 16); + } + } + if (subreader.GetAttribute("value") != null) + { + string value = subreader.GetAttribute("value"); - // If we somehow have an invalid file, zero out the fields - if (!valid) - { - Name = null; - Author = null; - Version = null; - Rules = null; - SourceFile = null; - } - } + // http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array + test.Value = new byte[value.Length / 2]; + for (int index = 0; index < test.Value.Length; index++) + { + string byteValue = value.Substring(index * 2, 2); + test.Value[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + } + if (subreader.GetAttribute("result") != null) + { + string result = subreader.GetAttribute("result"); + switch (result.ToLowerInvariant()) + { + case "false": + test.Result = false; + break; + case "true": + default: + test.Result = true; + break; + } + } + if (subreader.GetAttribute("mask") != null) + { + string mask = subreader.GetAttribute("mask"); - #endregion + // http://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array + test.Mask = new byte[mask.Length / 2]; + for (int index = 0; index < test.Mask.Length; index++) + { + string byteValue = mask.Substring(index * 2, 2); + test.Mask[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + } + if (subreader.GetAttribute("size") != null) + { + string size = subreader.GetAttribute("size"); + if (size.ToLowerInvariant() == "po2") + { + test.Size = null; + } + else + { + test.Size = Convert.ToInt64(size, 16); + } + } + if (subreader.GetAttribute("operator") != null) + { + string oper = subreader.GetAttribute("operator"); + switch (oper.ToLowerInvariant()) + { + case "less": + test.Operator = HeaderSkipTestFileOperator.Less; + break; + case "greater": + test.Operator = HeaderSkipTestFileOperator.Greater; + break; + case "equal": + default: + test.Operator = HeaderSkipTestFileOperator.Equal; + break; + } + } - #region Static Methods + // Add the created test to the rule + rule.Tests.Add(test); + subreader.Read(); + } + } - /// - /// Populate the entire list of header Skippers - /// - /// - /// http://mamedev.emulab.it/clrmamepro/docs/xmlheaders.txt - /// http://www.emulab.it/forum/index.php?topic=127.0 - /// - private static void PopulateSkippers() - { - if (_list == null) - { - _list = new List(); - } + // Add the created rule to the skipper + Rules.Add(rule); + xtr.Skip(); + break; + default: + xtr.Read(); + break; + } + } - foreach (string skipperFile in Directory.EnumerateFiles(LocalPath, "*", SearchOption.AllDirectories)) - { - _list.Add(new Skipper(Path.GetFullPath(skipperFile))); - } - } + // If we somehow have an invalid file, zero out the fields + if (!valid) + { + Name = null; + Author = null; + Version = null; + Rules = null; + SourceFile = null; + } + } - /// - /// Get the SkipperRule associated with a given file - /// - /// Name of the file to be checked - /// Name of the skipper to be used, blank to find a matching skipper - /// Logger object for file and console output - /// The SkipperRule that matched the file - public static SkipperRule GetMatchingRule(string input, string skipperName) - { - // If the file doesn't exist, return a blank skipper rule - if (!File.Exists(input)) - { - Globals.Logger.Error("The file '{0}' does not exist so it cannot be tested", input); - return new SkipperRule(); - } + #endregion - return GetMatchingRule(Utilities.TryOpenRead(input), skipperName); - } + #region Static Methods - /// - /// Get the SkipperRule associated with a given stream - /// - /// Name of the file to be checked - /// Name of the skipper to be used, blank to find a matching skipper - /// Logger object for file and console output - /// True if the underlying stream should be kept open, false otherwise - /// The SkipperRule that matched the file - public static SkipperRule GetMatchingRule(Stream input, string skipperName, bool keepOpen = false) - { - SkipperRule skipperRule = new SkipperRule(); + /// + /// Populate the entire list of header Skippers + /// + /// + /// http://mamedev.emulab.it/clrmamepro/docs/xmlheaders.txt + /// http://www.emulab.it/forum/index.php?topic=127.0 + /// + private static void PopulateSkippers() + { + if (_list == null) + { + _list = new List(); + } - // If we have a null skipper name, we return since we're not matching skippers - if (skipperName == null) - { - return skipperRule; - } + foreach (string skipperFile in Directory.EnumerateFiles(LocalPath, "*", SearchOption.AllDirectories)) + { + _list.Add(new Skipper(Path.GetFullPath(skipperFile))); + } + } - // Loop through and find a Skipper that has the right name - Globals.Logger.Verbose("Beginning search for matching header skip rules"); - List tempList = new List(); - tempList.AddRange(List); + /// + /// Get the SkipperRule associated with a given file + /// + /// Name of the file to be checked + /// Name of the skipper to be used, blank to find a matching skipper + /// Logger object for file and console output + /// The SkipperRule that matched the file + public static SkipperRule GetMatchingRule(string input, string skipperName) + { + // If the file doesn't exist, return a blank skipper rule + if (!File.Exists(input)) + { + Globals.Logger.Error("The file '{0}' does not exist so it cannot be tested", input); + return new SkipperRule(); + } - foreach (Skipper skipper in tempList) - { - // If we're searching for the skipper OR we have a match to an inputted one - if (String.IsNullOrWhiteSpace(skipperName) - || (!String.IsNullOrWhiteSpace(skipper.Name) && skipperName.ToLowerInvariant() == skipper.Name.ToLowerInvariant()) - || (!String.IsNullOrWhiteSpace(skipper.Name) && skipperName.ToLowerInvariant() == skipper.SourceFile.ToLowerInvariant())) - { - // Loop through the rules until one is found that works - BinaryReader br = new BinaryReader(input); + return GetMatchingRule(Utilities.TryOpenRead(input), skipperName); + } - foreach (SkipperRule rule in skipper.Rules) - { - // Always reset the stream back to the original place - input.Seek(0, SeekOrigin.Begin); + /// + /// Get the SkipperRule associated with a given stream + /// + /// Name of the file to be checked + /// Name of the skipper to be used, blank to find a matching skipper + /// Logger object for file and console output + /// True if the underlying stream should be kept open, false otherwise + /// The SkipperRule that matched the file + public static SkipperRule GetMatchingRule(Stream input, string skipperName, bool keepOpen = false) + { + SkipperRule skipperRule = new SkipperRule(); - // For each rule, make sure it passes each test - bool success = true; - foreach (SkipperTest test in rule.Tests) - { - bool result = true; - switch (test.Type) - { - case HeaderSkipTest.Data: - // First seek to the correct position - if (test.Offset == null) - { - input.Seek(0, SeekOrigin.End); - } - else if (test.Offset > 0 && test.Offset <= input.Length) - { - input.Seek((long)test.Offset, SeekOrigin.Begin); - } - else if (test.Offset < 0 && Math.Abs((long)test.Offset) <= input.Length) - { - input.Seek((long)test.Offset, SeekOrigin.End); - } + // If we have a null skipper name, we return since we're not matching skippers + if (skipperName == null) + { + return skipperRule; + } - // Then read and compare bytewise - result = true; - for (int i = 0; i < test.Value.Length; i++) - { - try - { - if (br.ReadByte() != test.Value[i]) - { - result = false; - break; - } - } - catch - { - result = false; - break; - } - } + // Loop through and find a Skipper that has the right name + Globals.Logger.Verbose("Beginning search for matching header skip rules"); + List tempList = new List(); + tempList.AddRange(List); - // Return if the expected and actual results match - success &= (result == test.Result); - break; - case HeaderSkipTest.Or: - case HeaderSkipTest.Xor: - case HeaderSkipTest.And: - // First seek to the correct position - if (test.Offset == null) - { - input.Seek(0, SeekOrigin.End); - } - else if (test.Offset > 0 && test.Offset <= input.Length) - { - input.Seek((long)test.Offset, SeekOrigin.Begin); - } - else if (test.Offset < 0 && Math.Abs((long)test.Offset) <= input.Length) - { - input.Seek((long)test.Offset, SeekOrigin.End); - } + foreach (Skipper skipper in tempList) + { + // If we're searching for the skipper OR we have a match to an inputted one + if (String.IsNullOrWhiteSpace(skipperName) + || (!String.IsNullOrWhiteSpace(skipper.Name) && skipperName.ToLowerInvariant() == skipper.Name.ToLowerInvariant()) + || (!String.IsNullOrWhiteSpace(skipper.Name) && skipperName.ToLowerInvariant() == skipper.SourceFile.ToLowerInvariant())) + { + // Loop through the rules until one is found that works + BinaryReader br = new BinaryReader(input); - result = true; - try - { - // Then apply the mask if it exists - byte[] read = br.ReadBytes(test.Mask.Length); - byte[] masked = new byte[test.Mask.Length]; - for (int i = 0; i < read.Length; i++) - { - masked[i] = (byte)(test.Type == HeaderSkipTest.And ? read[i] & test.Mask[i] : - (test.Type == HeaderSkipTest.Or ? read[i] | test.Mask[i] : read[i] ^ test.Mask[i]) - ); - } + foreach (SkipperRule rule in skipper.Rules) + { + // Always reset the stream back to the original place + input.Seek(0, SeekOrigin.Begin); - // Finally, compare it against the value - for (int i = 0; i < test.Value.Length; i++) - { - if (masked[i] != test.Value[i]) - { - result = false; - break; - } - } - } - catch - { - result = false; - } + // For each rule, make sure it passes each test + bool success = true; + foreach (SkipperTest test in rule.Tests) + { + bool result = true; + switch (test.Type) + { + case HeaderSkipTest.Data: + // First seek to the correct position + if (test.Offset == null) + { + input.Seek(0, SeekOrigin.End); + } + else if (test.Offset > 0 && test.Offset <= input.Length) + { + input.Seek((long)test.Offset, SeekOrigin.Begin); + } + else if (test.Offset < 0 && Math.Abs((long)test.Offset) <= input.Length) + { + input.Seek((long)test.Offset, SeekOrigin.End); + } - // Return if the expected and actual results match - success &= (result == test.Result); - break; - case HeaderSkipTest.File: - // First get the file size from stream - long size = input.Length; + // Then read and compare bytewise + result = true; + for (int i = 0; i < test.Value.Length; i++) + { + try + { + if (br.ReadByte() != test.Value[i]) + { + result = false; + break; + } + } + catch + { + result = false; + break; + } + } - // If we have a null size, check that the size is a power of 2 - result = true; - if (test.Size == null) - { - // http://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2 - result = (((ulong)size & ((ulong)size - 1)) == 0); - } - else if (test.Operator == HeaderSkipTestFileOperator.Less) - { - result = (size < test.Size); - } - else if (test.Operator == HeaderSkipTestFileOperator.Greater) - { - result = (size > test.Size); - } - else if (test.Operator == HeaderSkipTestFileOperator.Equal) - { - result = (size == test.Size); - } + // Return if the expected and actual results match + success &= (result == test.Result); + break; + case HeaderSkipTest.Or: + case HeaderSkipTest.Xor: + case HeaderSkipTest.And: + // First seek to the correct position + if (test.Offset == null) + { + input.Seek(0, SeekOrigin.End); + } + else if (test.Offset > 0 && test.Offset <= input.Length) + { + input.Seek((long)test.Offset, SeekOrigin.Begin); + } + else if (test.Offset < 0 && Math.Abs((long)test.Offset) <= input.Length) + { + input.Seek((long)test.Offset, SeekOrigin.End); + } - // Return if the expected and actual results match - success &= (result == test.Result); - break; - } - } + result = true; + try + { + // Then apply the mask if it exists + byte[] read = br.ReadBytes(test.Mask.Length); + byte[] masked = new byte[test.Mask.Length]; + for (int i = 0; i < read.Length; i++) + { + masked[i] = (byte)(test.Type == HeaderSkipTest.And ? read[i] & test.Mask[i] : + (test.Type == HeaderSkipTest.Or ? read[i] | test.Mask[i] : read[i] ^ test.Mask[i]) + ); + } - // If we still have a success, then return this rule - if (success) - { - // If we're not keeping the stream open, dispose of the binary reader - if (!keepOpen) - { - input.Dispose(); - } + // Finally, compare it against the value + for (int i = 0; i < test.Value.Length; i++) + { + if (masked[i] != test.Value[i]) + { + result = false; + break; + } + } + } + catch + { + result = false; + } - Globals.Logger.User(" Matching rule found!"); - return rule; - } - } - } - } + // Return if the expected and actual results match + success &= (result == test.Result); + break; + case HeaderSkipTest.File: + // First get the file size from stream + long size = input.Length; - // If we're not keeping the stream open, dispose of the binary reader - if (!keepOpen) - { - input.Dispose(); - } + // If we have a null size, check that the size is a power of 2 + result = true; + if (test.Size == null) + { + // http://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2 + result = (((ulong)size & ((ulong)size - 1)) == 0); + } + else if (test.Operator == HeaderSkipTestFileOperator.Less) + { + result = (size < test.Size); + } + else if (test.Operator == HeaderSkipTestFileOperator.Greater) + { + result = (size > test.Size); + } + else if (test.Operator == HeaderSkipTestFileOperator.Equal) + { + result = (size == test.Size); + } - // If we have a blank rule, inform the user - if (skipperRule.Tests == null) - { - Globals.Logger.Verbose("No matching rule found!"); - } + // Return if the expected and actual results match + success &= (result == test.Result); + break; + } + } - return skipperRule; - } + // If we still have a success, then return this rule + if (success) + { + // If we're not keeping the stream open, dispose of the binary reader + if (!keepOpen) + { + input.Dispose(); + } - #endregion - } + Globals.Logger.User(" Matching rule found!"); + return rule; + } + } + } + } + + // If we're not keeping the stream open, dispose of the binary reader + if (!keepOpen) + { + input.Dispose(); + } + + // If we have a blank rule, inform the user + if (skipperRule.Tests == null) + { + Globals.Logger.Verbose("No matching rule found!"); + } + + return skipperRule; + } + + #endregion + } } diff --git a/SabreTools.Library/Skippers/SkipperRule.cs b/SabreTools.Library/Skippers/SkipperRule.cs index f626e840..087e3370 100644 --- a/SabreTools.Library/Skippers/SkipperRule.cs +++ b/SabreTools.Library/Skippers/SkipperRule.cs @@ -17,196 +17,245 @@ using Stream = System.IO.Stream; namespace SabreTools.Library.Skippers { - public class SkipperRule - { - // Public variables - public long? StartOffset; // null is EOF - public long? EndOffset; // null if EOF - public HeaderSkipOperation Operation; - public List Tests; - public string SourceFile; + public class SkipperRule + { + #region Fields - /// - /// Transform an input file using the given rule - /// - /// Input file name - /// Output file name - /// True if the file was transformed properly, false otherwise - public bool TransformFile(string input, string output) - { - bool success = true; + /// + /// Starting offset for applying rule + /// + public long? StartOffset { get; set; } // null is EOF - // If the input file doesn't exist, fail - if (!File.Exists(input)) - { - Globals.Logger.Error("I'm sorry but '{0}' doesn't exist!", input); - return false; - } + /// + /// Ending offset for applying rule + /// + public long? EndOffset { get; set; } // null if EOF - // Create the output directory if it doesn't already - Utilities.EnsureOutputDirectory(Path.GetDirectoryName(output)); + /// + /// Byte manipulation operation + /// + public HeaderSkipOperation Operation { get; set; } - Globals.Logger.User("Attempting to apply rule to '{0}'", input); - success = TransformStream(Utilities.TryOpenRead(input), Utilities.TryCreate(output)); + /// + /// List of matching tests in a rule + /// + public List Tests { get; set; } - // If the output file has size 0, delete it - if (new FileInfo(output).Length == 0) - { - Utilities.TryDeleteFile(output); - success = false; - } + /// + /// Filename the skipper rule lives in + /// + public string SourceFile { get; set; } - return success; - } + #endregion - /// - /// Transform an input stream using the given rule - /// - /// Input stream - /// Output stream - /// True if the underlying read stream should be kept open, false otherwise - /// True if the underlying write stream should be kept open, false otherwise - /// True if the file was transformed properly, false otherwise - public bool TransformStream(Stream input, Stream output, bool keepReadOpen = false, bool keepWriteOpen = false) - { - bool success = true; + /// + /// Transform an input file using the given rule + /// + /// Input file name + /// Output file name + /// True if the file was transformed properly, false otherwise + public bool TransformFile(string input, string output) + { + bool success = true; - // If the sizes are wrong for the values, fail - long extsize = input.Length; - if ((Operation > HeaderSkipOperation.Bitswap && (extsize % 2) != 0) - || (Operation > HeaderSkipOperation.Byteswap && (extsize % 4) != 0) - || (Operation > HeaderSkipOperation.Bitswap && (StartOffset == null || StartOffset % 2 == 0))) - { - Globals.Logger.Error("The stream did not have the correct size to be transformed!"); - return false; - } + // If the input file doesn't exist, fail + if (!File.Exists(input)) + { + Globals.Logger.Error("I'm sorry but '{0}' doesn't exist!", input); + return false; + } - // Now read the proper part of the file and apply the rule - BinaryWriter bw = null; - BinaryReader br = null; - try - { - Globals.Logger.User("Applying found rule to input stream"); - bw = new BinaryWriter(output); - br = new BinaryReader(input); + // Create the output directory if it doesn't already + Utilities.EnsureOutputDirectory(Path.GetDirectoryName(output)); - // Seek to the beginning offset - if (StartOffset == null) - { - success = false; - } - else if (Math.Abs((long)StartOffset) > input.Length) - { - success = false; - } - else if (StartOffset > 0) - { - input.Seek((long)StartOffset, SeekOrigin.Begin); - } - else if (StartOffset < 0) - { - input.Seek((long)StartOffset, SeekOrigin.End); - } + Globals.Logger.User("Attempting to apply rule to '{0}'", input); + success = TransformStream(Utilities.TryOpenRead(input), Utilities.TryCreate(output)); - // Then read and apply the operation as you go - if (success) - { - byte[] buffer = new byte[4]; - int pos = 0; - while (input.Position < (EndOffset ?? input.Length) - && input.Position < input.Length) - { - byte b = br.ReadByte(); - switch (Operation) - { - case HeaderSkipOperation.Bitswap: - // http://stackoverflow.com/questions/3587826/is-there-a-built-in-function-to-reverse-bit-order - uint r = b; - int s = 7; - for (b >>= 1; b != 0; b >>= 1) - { - r <<= 1; - r |= (byte)(b & 1); - s--; - } - r <<= s; - buffer[pos] = (byte)r; - break; - case HeaderSkipOperation.Byteswap: - if (pos % 2 == 1) - { - buffer[pos - 1] = b; - } - if (pos % 2 == 0) - { - buffer[pos + 1] = b; - } - break; - case HeaderSkipOperation.Wordswap: - buffer[3 - pos] = b; - break; - case HeaderSkipOperation.WordByteswap: - buffer[(pos + 2) % 4] = b; - break; - case HeaderSkipOperation.None: - default: - buffer[pos] = b; - break; - } + // If the output file has size 0, delete it + if (new FileInfo(output).Length == 0) + { + Utilities.TryDeleteFile(output); + success = false; + } - // Set the buffer position to default write to - pos = (pos + 1) % 4; + return success; + } - // If we filled a buffer, flush to the stream - if (pos == 0) - { - bw.Write(buffer); - bw.Flush(); - buffer = new byte[4]; - } - } - // If there's anything more in the buffer, write only the left bits - for (int i = 0; i < pos; i++) - { - bw.Write(buffer[i]); - } - } - } - catch (Exception ex) - { - Globals.Logger.Error(ex.ToString()); - return false; - } - finally - { - // If we're not keeping the read stream open, dispose of the binary reader - if (!keepReadOpen) - { - br?.Dispose(); - } + /// + /// Transform an input stream using the given rule + /// + /// Input stream + /// Output stream + /// True if the underlying read stream should be kept open, false otherwise + /// True if the underlying write stream should be kept open, false otherwise + /// True if the file was transformed properly, false otherwise + public bool TransformStream(Stream input, Stream output, bool keepReadOpen = false, bool keepWriteOpen = false) + { + bool success = true; - // If we're not keeping the write stream open, dispose of the binary reader - if (!keepWriteOpen) - { - bw?.Dispose(); - } - } + // If the sizes are wrong for the values, fail + long extsize = input.Length; + if ((Operation > HeaderSkipOperation.Bitswap && (extsize % 2) != 0) + || (Operation > HeaderSkipOperation.Byteswap && (extsize % 4) != 0) + || (Operation > HeaderSkipOperation.Bitswap && (StartOffset == null || StartOffset % 2 == 0))) + { + Globals.Logger.Error("The stream did not have the correct size to be transformed!"); + return false; + } - return success; - } - } + // Now read the proper part of the file and apply the rule + BinaryWriter bw = null; + BinaryReader br = null; + try + { + Globals.Logger.User("Applying found rule to input stream"); + bw = new BinaryWriter(output); + br = new BinaryReader(input); - /// - /// Intermediate class for storing Skipper Test information - /// - public struct SkipperTest - { - public HeaderSkipTest Type; - public long? Offset; // null is EOF - public byte[] Value; - public bool Result; - public byte[] Mask; - public long? Size; // null is PO2, "power of 2" filesize - public HeaderSkipTestFileOperator Operator; - } + // Seek to the beginning offset + if (StartOffset == null) + { + success = false; + } + else if (Math.Abs((long)StartOffset) > input.Length) + { + success = false; + } + else if (StartOffset > 0) + { + input.Seek((long)StartOffset, SeekOrigin.Begin); + } + else if (StartOffset < 0) + { + input.Seek((long)StartOffset, SeekOrigin.End); + } + + // Then read and apply the operation as you go + if (success) + { + byte[] buffer = new byte[4]; + int pos = 0; + while (input.Position < (EndOffset ?? input.Length) + && input.Position < input.Length) + { + byte b = br.ReadByte(); + switch (Operation) + { + case HeaderSkipOperation.Bitswap: + // http://stackoverflow.com/questions/3587826/is-there-a-built-in-function-to-reverse-bit-order + uint r = b; + int s = 7; + for (b >>= 1; b != 0; b >>= 1) + { + r <<= 1; + r |= (byte)(b & 1); + s--; + } + r <<= s; + buffer[pos] = (byte)r; + break; + case HeaderSkipOperation.Byteswap: + if (pos % 2 == 1) + { + buffer[pos - 1] = b; + } + if (pos % 2 == 0) + { + buffer[pos + 1] = b; + } + break; + case HeaderSkipOperation.Wordswap: + buffer[3 - pos] = b; + break; + case HeaderSkipOperation.WordByteswap: + buffer[(pos + 2) % 4] = b; + break; + case HeaderSkipOperation.None: + default: + buffer[pos] = b; + break; + } + + // Set the buffer position to default write to + pos = (pos + 1) % 4; + + // If we filled a buffer, flush to the stream + if (pos == 0) + { + bw.Write(buffer); + bw.Flush(); + buffer = new byte[4]; + } + } + // If there's anything more in the buffer, write only the left bits + for (int i = 0; i < pos; i++) + { + bw.Write(buffer[i]); + } + } + } + catch (Exception ex) + { + Globals.Logger.Error(ex.ToString()); + return false; + } + finally + { + // If we're not keeping the read stream open, dispose of the binary reader + if (!keepReadOpen) + { + br?.Dispose(); + } + + // If we're not keeping the write stream open, dispose of the binary reader + if (!keepWriteOpen) + { + bw?.Dispose(); + } + } + + return success; + } + } + + /// + /// Intermediate class for storing Skipper Test information + /// + public struct SkipperTest + { + /// + /// Type of test to be run + /// + public HeaderSkipTest Type { get; set; } + + /// + /// File offset to run the test + /// + public long? Offset { get; set; } // null is EOF + + /// + /// Static value to be checked at the offset + /// + public byte[] Value { get; set; } + + /// + /// Determines whether a pass or failure is expected + /// + public bool Result { get; set; } + + /// + /// Byte mask to be applied to the tested bytes + /// + public byte[] Mask { get; set; } + + /// + /// Expected size of the input byte array, used with the Operator + /// + public long? Size { get; set; } // null is PO2, "power of 2" filesize + + /// + /// Expected range value for the input byte array size, used with Size + /// + public HeaderSkipTestFileOperator Operator { get; set; } + } }