2016-10-03 21:16:59 -07:00
|
|
|
|
using System;
|
2020-06-10 22:37:19 -07:00
|
|
|
|
using System.IO;
|
2023-04-04 11:23:59 -04:00
|
|
|
|
using System.Xml.Serialization;
|
2020-12-07 14:29:45 -08:00
|
|
|
|
using SabreTools.Logging;
|
2023-04-04 18:31:19 -04:00
|
|
|
|
using SabreTools.Skippers.Tests;
|
2020-12-07 14:29:45 -08:00
|
|
|
|
|
2020-12-08 00:13:22 -08:00
|
|
|
|
namespace SabreTools.Skippers
|
2016-10-03 21:16:59 -07:00
|
|
|
|
{
|
2023-04-04 18:31:19 -04:00
|
|
|
|
[XmlType("rule")]
|
|
|
|
|
|
public class Rule
|
2019-02-08 21:01:54 -08:00
|
|
|
|
{
|
|
|
|
|
|
#region Fields
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Starting offset for applying rule
|
|
|
|
|
|
/// </summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
|
/// <remarks>Either numeric or the literal "EOF"</remarks>
|
2023-04-04 11:23:59 -04:00
|
|
|
|
[XmlAttribute("start_offset")]
|
2023-04-04 18:31:19 -04:00
|
|
|
|
public string? StartOffset
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _startOffset == null ? "EOF" : _startOffset.Value.ToString();
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (value == null || value.ToLowerInvariant() == "eof")
|
|
|
|
|
|
_startOffset = null;
|
|
|
|
|
|
else
|
|
|
|
|
|
_startOffset = Convert.ToInt64(value, fromBase: 16);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2019-02-08 21:01:54 -08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Ending offset for applying rule
|
|
|
|
|
|
/// </summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
|
/// <remarks>Either numeric or the literal "EOF"</remarks>
|
2023-04-04 11:23:59 -04:00
|
|
|
|
[XmlAttribute("end_offset")]
|
2023-04-04 18:31:19 -04:00
|
|
|
|
public string? EndOffset
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _endOffset == null ? "EOF" : _endOffset.Value.ToString();
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (value == null || value.ToLowerInvariant() == "eof")
|
|
|
|
|
|
_endOffset = null;
|
|
|
|
|
|
else
|
|
|
|
|
|
_endOffset = Convert.ToInt64(value, fromBase: 16);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2019-02-08 21:01:54 -08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Byte manipulation operation
|
|
|
|
|
|
/// </summary>
|
2023-04-04 11:23:59 -04:00
|
|
|
|
[XmlAttribute("operation")]
|
2019-02-08 21:01:54 -08:00
|
|
|
|
public HeaderSkipOperation Operation { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// List of matching tests in a rule
|
|
|
|
|
|
/// </summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
|
[XmlElement("and", typeof(AndTest))]
|
|
|
|
|
|
[XmlElement("data", typeof(DataTest))]
|
|
|
|
|
|
[XmlElement("file", typeof(FileTest))]
|
|
|
|
|
|
[XmlElement("or", typeof(OrTest))]
|
|
|
|
|
|
[XmlElement("xor", typeof(XorTest))]
|
|
|
|
|
|
public Test[]? Tests { get; set; }
|
2019-02-08 21:01:54 -08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Filename the skipper rule lives in
|
|
|
|
|
|
/// </summary>
|
2023-04-04 11:23:59 -04:00
|
|
|
|
[XmlIgnore]
|
2023-04-04 18:31:19 -04:00
|
|
|
|
public string? SourceFile { get; set; }
|
2019-02-08 21:01:54 -08:00
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2023-04-04 18:31:19 -04:00
|
|
|
|
#region Private instance variables
|
2020-10-07 15:42:30 -07:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
|
/// Starting offset for applying rule
|
2020-10-07 15:42:30 -07:00
|
|
|
|
/// </summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
|
/// <remarks>null is EOF</remarks>
|
|
|
|
|
|
private long? _startOffset = null;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Ending offset for applying rule
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>null is EOF</remarks>
|
|
|
|
|
|
private long? _endOffset = null;
|
2020-10-07 16:37:10 -07:00
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
2023-04-04 18:31:19 -04:00
|
|
|
|
#region Logging
|
2020-10-07 16:37:10 -07:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
|
/// Logging object
|
2020-10-07 16:37:10 -07:00
|
|
|
|
/// </summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
|
private readonly Logger logger;
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
public Rule()
|
2020-10-07 16:37:10 -07:00
|
|
|
|
{
|
2020-12-07 14:29:45 -08:00
|
|
|
|
logger = new Logger(this);
|
2020-10-07 16:37:10 -07:00
|
|
|
|
}
|
2020-10-07 15:42:30 -07:00
|
|
|
|
|
2020-07-30 22:32:16 -07:00
|
|
|
|
/// <summary>
|
2023-04-04 18:31:19 -04:00
|
|
|
|
/// Check if a Stream passes all tests in the Rule
|
2020-07-30 22:32:16 -07:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input">Stream to check</param>
|
|
|
|
|
|
/// <returns>True if all tests passed, false otherwise</returns>
|
|
|
|
|
|
public bool PassesAllTests(Stream input)
|
|
|
|
|
|
{
|
|
|
|
|
|
bool success = true;
|
2023-04-04 18:31:19 -04:00
|
|
|
|
|
|
|
|
|
|
// If there are no tests
|
|
|
|
|
|
if (Tests == null || Tests.Length == 0)
|
|
|
|
|
|
return success;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (Test test in Tests)
|
2020-07-30 22:32:16 -07:00
|
|
|
|
{
|
|
|
|
|
|
bool result = test.Passes(input);
|
|
|
|
|
|
success &= result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-02-08 21:01:54 -08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Transform an input file using the given rule
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input">Input file name</param>
|
|
|
|
|
|
/// <param name="output">Output file name</param>
|
|
|
|
|
|
/// <returns>True if the file was transformed properly, false otherwise</returns>
|
|
|
|
|
|
public bool TransformFile(string input, string output)
|
|
|
|
|
|
{
|
2023-04-04 18:31:19 -04:00
|
|
|
|
// If the input file doesn't exist
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(input) || !File.Exists(input))
|
|
|
|
|
|
{
|
|
|
|
|
|
logger.Error($"'{input}' doesn't exist and cannot be transformed!");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If we have an invalid output directory name
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(output))
|
2019-02-08 21:01:54 -08:00
|
|
|
|
{
|
2023-04-04 18:31:19 -04:00
|
|
|
|
logger.Error($"Output path was null or empty, cannot write transformed file!");
|
2019-02-08 21:01:54 -08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create the output directory if it doesn't already
|
2023-04-04 18:31:19 -04:00
|
|
|
|
string parentDirectory = Path.GetDirectoryName(output) ?? string.Empty;
|
|
|
|
|
|
Directory.CreateDirectory(parentDirectory);
|
2019-02-08 21:01:54 -08:00
|
|
|
|
|
2020-12-07 13:02:42 -08:00
|
|
|
|
//logger.User($"Attempting to apply rule to '{input}'");
|
2020-12-08 00:13:22 -08:00
|
|
|
|
bool success = TransformStream(File.OpenRead(input), File.Create(output));
|
2019-02-08 21:01:54 -08:00
|
|
|
|
|
|
|
|
|
|
// If the output file has size 0, delete it
|
|
|
|
|
|
if (new FileInfo(output).Length == 0)
|
|
|
|
|
|
{
|
2020-12-08 00:13:22 -08:00
|
|
|
|
File.Delete(output);
|
2019-02-08 21:01:54 -08:00
|
|
|
|
success = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Transform an input stream using the given rule
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="input">Input stream</param>
|
|
|
|
|
|
/// <param name="output">Output stream</param>
|
|
|
|
|
|
/// <param name="keepReadOpen">True if the underlying read stream should be kept open, false otherwise</param>
|
|
|
|
|
|
/// <param name="keepWriteOpen">True if the underlying write stream should be kept open, false otherwise</param>
|
|
|
|
|
|
/// <returns>True if the file was transformed properly, false otherwise</returns>
|
|
|
|
|
|
public bool TransformStream(Stream input, Stream output, bool keepReadOpen = false, bool keepWriteOpen = false)
|
|
|
|
|
|
{
|
|
|
|
|
|
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)
|
2023-04-04 18:31:19 -04:00
|
|
|
|
|| (Operation > HeaderSkipOperation.Bitswap && (_startOffset == null || _startOffset % 2 != 0)))
|
2019-02-08 21:01:54 -08:00
|
|
|
|
{
|
2020-12-07 14:29:45 -08:00
|
|
|
|
logger.Error("The stream did not have the correct size to be transformed!");
|
2019-02-08 21:01:54 -08:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Now read the proper part of the file and apply the rule
|
2023-04-04 18:31:19 -04:00
|
|
|
|
BinaryWriter? bw = null;
|
|
|
|
|
|
BinaryReader? br = null;
|
2019-02-08 21:01:54 -08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
2020-12-07 14:29:45 -08:00
|
|
|
|
logger.User("Applying found rule to input stream");
|
2019-02-08 21:01:54 -08:00
|
|
|
|
bw = new BinaryWriter(output);
|
|
|
|
|
|
br = new BinaryReader(input);
|
|
|
|
|
|
|
|
|
|
|
|
// Seek to the beginning offset
|
2023-04-04 18:31:19 -04:00
|
|
|
|
if (_startOffset == null)
|
2019-02-08 21:01:54 -08:00
|
|
|
|
success = false;
|
2020-06-10 22:37:19 -07:00
|
|
|
|
|
2023-04-04 18:31:19 -04:00
|
|
|
|
else if (Math.Abs((long)_startOffset) > input.Length)
|
2019-02-08 21:01:54 -08:00
|
|
|
|
success = false;
|
2020-06-10 22:37:19 -07:00
|
|
|
|
|
2023-04-04 18:31:19 -04:00
|
|
|
|
else if (_startOffset > 0)
|
|
|
|
|
|
input.Seek((long)_startOffset, SeekOrigin.Begin);
|
2020-06-10 22:37:19 -07:00
|
|
|
|
|
2023-04-04 18:31:19 -04:00
|
|
|
|
else if (_startOffset < 0)
|
|
|
|
|
|
input.Seek((long)_startOffset, SeekOrigin.End);
|
2019-02-08 21:01:54 -08:00
|
|
|
|
|
|
|
|
|
|
// Then read and apply the operation as you go
|
|
|
|
|
|
if (success)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] buffer = new byte[4];
|
|
|
|
|
|
int pos = 0;
|
2023-04-04 18:31:19 -04:00
|
|
|
|
while (input.Position < (_endOffset ?? input.Length)
|
2019-02-08 21:01:54 -08:00
|
|
|
|
&& 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;
|
2020-06-10 22:37:19 -07:00
|
|
|
|
|
2019-02-08 21:01:54 -08:00
|
|
|
|
case HeaderSkipOperation.Byteswap:
|
|
|
|
|
|
if (pos % 2 == 1)
|
|
|
|
|
|
buffer[pos - 1] = b;
|
2020-12-18 17:07:58 -08:00
|
|
|
|
else
|
2019-02-08 21:01:54 -08:00
|
|
|
|
buffer[pos + 1] = b;
|
2020-12-18 17:07:58 -08:00
|
|
|
|
|
2019-02-08 21:01:54 -08:00
|
|
|
|
break;
|
2020-06-10 22:37:19 -07:00
|
|
|
|
|
2019-02-08 21:01:54 -08:00
|
|
|
|
case HeaderSkipOperation.Wordswap:
|
|
|
|
|
|
buffer[3 - pos] = b;
|
|
|
|
|
|
break;
|
2020-06-10 22:37:19 -07:00
|
|
|
|
|
2019-02-08 21:01:54 -08:00
|
|
|
|
case HeaderSkipOperation.WordByteswap:
|
|
|
|
|
|
buffer[(pos + 2) % 4] = b;
|
|
|
|
|
|
break;
|
2020-06-10 22:37:19 -07:00
|
|
|
|
|
2019-02-08 21:01:54 -08:00
|
|
|
|
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];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2020-06-10 22:37:19 -07:00
|
|
|
|
|
2019-02-08 21:01:54 -08:00
|
|
|
|
// 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)
|
|
|
|
|
|
{
|
2020-12-07 14:29:45 -08:00
|
|
|
|
logger.Error(ex);
|
2019-02-08 21:01:54 -08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2016-10-03 21:16:59 -07:00
|
|
|
|
}
|