diff --git a/SabreTools.Helper/Skippers/Skippers.cs b/SabreTools.Helper/Skippers/Skippers.cs index a0aa191e..70e19407 100644 --- a/SabreTools.Helper/Skippers/Skippers.cs +++ b/SabreTools.Helper/Skippers/Skippers.cs @@ -311,162 +311,181 @@ namespace SabreTools.Helper /// Get the SkipperRule associated with a given file /// /// Name of the file to be checked - /// Name of the skipper to be used + /// Name of the skipper to be used /// Logger object for file and console output /// The SkipperRule that matched the file - public static SkipperRule MatchesSkipper(string input, string skippername, Logger logger) + public static SkipperRule MatchesSkipper(string input, string skipperName, Logger logger) { - SkipperRule skipperRule = new SkipperRule(); - // If the file doesn't exist, return a blank skipper rule if (!File.Exists(input)) { logger.Error("The file '" + input + "' does not exist so it cannot be tested"); - return skipperRule; + return new SkipperRule(); } + return MatchesSkipper(File.OpenRead(input), skipperName, logger); + } + + /// + /// Get the SkipperRule associated with a given stream + /// + /// Name of the file to be checked + /// Name of the skipper to be used + /// 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 MatchesSkipper(Stream input, string skipperName, Logger logger, bool keepOpen = false) + { + SkipperRule skipperRule = new SkipperRule(); + // Loop through and find a Skipper that has the right name logger.Log("Beginning search for matching header skip rules"); foreach (Skipper skipper in List) { - if (String.IsNullOrEmpty(skippername) || (!String.IsNullOrEmpty(skipper.Name) && skippername.ToLowerInvariant() == skipper.Name.ToLowerInvariant())) + if (String.IsNullOrEmpty(skipperName) || (!String.IsNullOrEmpty(skipper.Name) && skipperName.ToLowerInvariant() == skipper.Name.ToLowerInvariant())) { // Loop through the rules until one is found that works - using (BinaryReader br = new BinaryReader(File.OpenRead(input))) + BinaryReader br = new BinaryReader(input); + + foreach (SkipperRule rule in skipper.Rules) { - foreach (SkipperRule rule in skipper.Rules) + // Always reset the stream back to the original place + br.BaseStream.Seek(0, SeekOrigin.Begin); + + // For each rule, make sure it passes each test + bool success = true; + foreach (SkipperTest test in rule.Tests) { - // Always reset the stream back to the original place - br.BaseStream.Seek(0, SeekOrigin.Begin); - - // For each rule, make sure it passes each test - bool success = true; - foreach (SkipperTest test in rule.Tests) + bool result = true; + switch (test.Type) { - bool result = true; - switch (test.Type) - { - case HeaderSkipTest.Data: - // First seek to the correct position - if (test.Offset == null) - { - br.BaseStream.Seek(0, SeekOrigin.End); - } - else if (test.Offset > 0 && test.Offset <= br.BaseStream.Length) - { - br.BaseStream.Seek((long)test.Offset, SeekOrigin.Begin); - } - else if (test.Offset < 0 && Math.Abs((long)test.Offset) <= br.BaseStream.Length) - { - br.BaseStream.Seek((long)test.Offset, SeekOrigin.End); - } + case HeaderSkipTest.Data: + // First seek to the correct position + if (test.Offset == null) + { + br.BaseStream.Seek(0, SeekOrigin.End); + } + else if (test.Offset > 0 && test.Offset <= br.BaseStream.Length) + { + br.BaseStream.Seek((long)test.Offset, SeekOrigin.Begin); + } + else if (test.Offset < 0 && Math.Abs((long)test.Offset) <= br.BaseStream.Length) + { + br.BaseStream.Seek((long)test.Offset, SeekOrigin.End); + } - // Then read and compare bytewise - result = true; - for (int i = 0; i < test.Value.Length; i++) + // Then read and compare bytewise + result = true; + for (int i = 0; i < test.Value.Length; i++) + { + try { - try - { - if (br.ReadByte() != test.Value[i]) - { - result = false; - break; - } - } - catch + if (br.ReadByte() != test.Value[i]) { result = false; break; } } - - // 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) - { - br.BaseStream.Seek(0, SeekOrigin.End); - } - else if (test.Offset > 0 && test.Offset <= br.BaseStream.Length) - { - br.BaseStream.Seek((long)test.Offset, SeekOrigin.Begin); - } - else if (test.Offset < 0 && Math.Abs((long)test.Offset) <= br.BaseStream.Length) - { - br.BaseStream.Seek((long)test.Offset, SeekOrigin.End); - } - - 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]) - ); - } - - // 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; + break; + } + } + + // 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) + { + br.BaseStream.Seek(0, SeekOrigin.End); + } + else if (test.Offset > 0 && test.Offset <= br.BaseStream.Length) + { + br.BaseStream.Seek((long)test.Offset, SeekOrigin.Begin); + } + else if (test.Offset < 0 && Math.Abs((long)test.Offset) <= br.BaseStream.Length) + { + br.BaseStream.Seek((long)test.Offset, SeekOrigin.End); + } + + 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]) + ); } - // 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 = br.BaseStream.Length; + // 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; + } - // 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.File: + // First get the file size from stream + long size = br.BaseStream.Length; - // Return if the expected and actual results match - success &= (result == test.Result); - 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; } + } - // If we still have a success, then return this rule - if (success) - { - logger.User(" Matching rule found!"); - return rule; - } + // If we're not keeping the stream open, dispose of the binary reader + if (!keepOpen) + { + br.Close(); + br.Dispose(); + } + + // If we still have a success, then return this rule + if (success) + { + logger.User(" Matching rule found!"); + return rule; } } } @@ -488,7 +507,7 @@ namespace SabreTools.Helper /// Output file name /// SkipperRule to apply to the file /// Logger object for file and console output - /// + /// True if the file was transformed properly, false otherwise public static bool TransformFile(string input, string output, SkipperRule rule, Logger logger) { bool success = true; @@ -506,112 +525,8 @@ namespace SabreTools.Helper Directory.CreateDirectory(System.IO.Path.GetDirectoryName(output)); } - // If the sizes are wrong for the values, fail - long extsize = new FileInfo(input).Length; - if ((rule.Operation > HeaderSkipOperation.Bitswap && (extsize % 2) != 0) - || (rule.Operation > HeaderSkipOperation.Byteswap && (extsize % 4) != 0) - || (rule.Operation > HeaderSkipOperation.Bitswap && (rule.StartOffset == null || rule.StartOffset % 2 == 0))) - { - logger.Error("The file did not have the correct size to be transformed!"); - return false; - } - - // Now read the proper part of the file and apply the rule - try - { - logger.User("Applying found rule to file '" + input + "'"); - using (BinaryWriter bw = new BinaryWriter(File.OpenWrite(output))) - using (BinaryReader br = new BinaryReader(File.OpenRead(input))) - { - // Seek to the beginning offset - if (rule.StartOffset == null) - { - success = false; - } - else if (Math.Abs((long)rule.StartOffset) > br.BaseStream.Length) - { - success = false; - } - else if (rule.StartOffset > 0) - { - br.BaseStream.Seek((long)rule.StartOffset, SeekOrigin.Begin); - } - else if (rule.StartOffset < 0) - { - br.BaseStream.Seek((long)rule.StartOffset, SeekOrigin.End); - } - - // Then read and apply the operation as you go - if (success) - { - byte[] buffer = new byte[4]; - int pos = 0; - while (br.BaseStream.Position < (rule.EndOffset != null ? rule.EndOffset : br.BaseStream.Length) - && br.BaseStream.Position < br.BaseStream.Length) - { - byte b = br.ReadByte(); - switch (rule.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) - { - logger.Error(ex.ToString()); - return false; - } + logger.User("Attempting to apply rule to '" + input + "'"); + success = TransformStream(File.OpenRead(input), File.OpenWrite(output), rule, logger); // If the output file has size 0, delete it if (new FileInfo(output).Length == 0) @@ -629,6 +544,146 @@ namespace SabreTools.Helper return success; } + /// + /// Transform an input stream using the given rule + /// + /// Input stream + /// Output stream + /// SkipperRule to apply to the stream + /// Logger object for file and console output + /// 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 static bool TransformStream(Stream input, Stream output, SkipperRule rule, Logger logger, bool keepReadOpen = false, bool keepWriteOpen = false) + { + bool success = true; + + // If the sizes are wrong for the values, fail + long extsize = input.Length; + if ((rule.Operation > HeaderSkipOperation.Bitswap && (extsize % 2) != 0) + || (rule.Operation > HeaderSkipOperation.Byteswap && (extsize % 4) != 0) + || (rule.Operation > HeaderSkipOperation.Bitswap && (rule.StartOffset == null || rule.StartOffset % 2 == 0))) + { + logger.Error("The stream did not have the correct size to be transformed!"); + return false; + } + + // Now read the proper part of the file and apply the rule + BinaryWriter bw = null; + BinaryReader br = null; + try + { + logger.User("Applying found rule to input stream"); + bw = new BinaryWriter(output); + br = new BinaryReader(input); + + // Seek to the beginning offset + if (rule.StartOffset == null) + { + success = false; + } + else if (Math.Abs((long)rule.StartOffset) > br.BaseStream.Length) + { + success = false; + } + else if (rule.StartOffset > 0) + { + br.BaseStream.Seek((long)rule.StartOffset, SeekOrigin.Begin); + } + else if (rule.StartOffset < 0) + { + br.BaseStream.Seek((long)rule.StartOffset, SeekOrigin.End); + } + + // Then read and apply the operation as you go + if (success) + { + byte[] buffer = new byte[4]; + int pos = 0; + while (br.BaseStream.Position < (rule.EndOffset != null ? rule.EndOffset : br.BaseStream.Length) + && br.BaseStream.Position < br.BaseStream.Length) + { + byte b = br.ReadByte(); + switch (rule.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) + { + logger.Error(ex.ToString()); + return false; + } + finally + { + // If we're not keeping the read stream open, dispose of the binary reader + if (!keepReadOpen) + { + br?.Close(); + br?.Dispose(); + } + + // If we're not keeping the write stream open, dispose of the binary reader + if (!keepWriteOpen) + { + bw?.Close(); + bw?.Dispose(); + } + } + + return success; + } + #endregion #region Header Skips (old)