mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-09-22 06:45:11 +00:00
Add Compression package
This commit is contained in:
73
SabreTools.Serialization.Test/Deserializers/LZKWAJTests.cs
Normal file
73
SabreTools.Serialization.Test/Deserializers/LZKWAJTests.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.Serialization.Deserializers;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Serialization.Test.Deserializers
|
||||
{
|
||||
public class LZKWAJTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var deserializer = new LZKWAJ();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var deserializer = new LZKWAJ();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var deserializer = new LZKWAJ();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var deserializer = new LZKWAJ();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var deserializer = new LZKWAJ();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var deserializer = new LZKWAJ();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
SabreTools.Serialization.Test/Deserializers/LZQBasicTests.cs
Normal file
74
SabreTools.Serialization.Test/Deserializers/LZQBasicTests.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.Serialization.Deserializers;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Serialization.Test.Deserializers
|
||||
{
|
||||
public class LZQBasicTests
|
||||
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var deserializer = new LZQBasic();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var deserializer = new LZQBasic();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var deserializer = new LZQBasic();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var deserializer = new LZQBasic();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var deserializer = new LZQBasic();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var deserializer = new LZQBasic();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
73
SabreTools.Serialization.Test/Deserializers/LZSZDDTests.cs
Normal file
73
SabreTools.Serialization.Test/Deserializers/LZSZDDTests.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.Serialization.Deserializers;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Serialization.Test.Deserializers
|
||||
{
|
||||
public class LZSZDDTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var deserializer = new LZSZDD();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var deserializer = new LZSZDD();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var deserializer = new LZSZDD();
|
||||
|
||||
var actual = deserializer.Deserialize(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var deserializer = new LZSZDD();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var deserializer = new LZSZDD();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var deserializer = new LZSZDD();
|
||||
|
||||
var actual = deserializer.Deserialize(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="SabreTools.Hashing" Version="1.4.0" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.5.6" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.5.7" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
61
SabreTools.Serialization.Test/Wrappers/LZKWAJTests.cs
Normal file
61
SabreTools.Serialization.Test/Wrappers/LZKWAJTests.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Serialization.Test.Wrappers
|
||||
{
|
||||
public class LZKWAJTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var actual = LZKWAJ.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var actual = LZKWAJ.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var actual = LZKWAJ.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var actual = LZKWAJ.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var actual = LZKWAJ.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var actual = LZKWAJ.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
SabreTools.Serialization.Test/Wrappers/LZQBasicTests.cs
Normal file
61
SabreTools.Serialization.Test/Wrappers/LZQBasicTests.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Serialization.Test.Wrappers
|
||||
{
|
||||
public class LZQBasicTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var actual = LZQBasic.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var actual = LZQBasic.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var actual = LZQBasic.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var actual = LZQBasic.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var actual = LZQBasic.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var actual = LZQBasic.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
SabreTools.Serialization.Test/Wrappers/LZSZDDTests.cs
Normal file
61
SabreTools.Serialization.Test/Wrappers/LZSZDDTests.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SabreTools.Serialization.Wrappers;
|
||||
using Xunit;
|
||||
|
||||
namespace SabreTools.Serialization.Test.Wrappers
|
||||
{
|
||||
public class LZSZDDTests
|
||||
{
|
||||
[Fact]
|
||||
public void NullArray_Null()
|
||||
{
|
||||
byte[]? data = null;
|
||||
int offset = 0;
|
||||
var actual = LZSZDD.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyArray_Null()
|
||||
{
|
||||
byte[]? data = [];
|
||||
int offset = 0;
|
||||
var actual = LZSZDD.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidArray_Null()
|
||||
{
|
||||
byte[]? data = [.. Enumerable.Repeat<byte>(0xFF, 1024)];
|
||||
int offset = 0;
|
||||
var actual = LZSZDD.Create(data, offset);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NullStream_Null()
|
||||
{
|
||||
Stream? data = null;
|
||||
var actual = LZSZDD.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([]);
|
||||
var actual = LZSZDD.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStream_Null()
|
||||
{
|
||||
Stream? data = new MemoryStream([.. Enumerable.Repeat<byte>(0xFF, 1024)]);
|
||||
var actual = LZSZDD.Create(data);
|
||||
Assert.Null(actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
121
SabreTools.Serialization/Deserializers/LZKWAJ.cs
Normal file
121
SabreTools.Serialization/Deserializers/LZKWAJ.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.LZ;
|
||||
using static SabreTools.Models.LZ.Constants;
|
||||
|
||||
namespace SabreTools.Serialization.Deserializers
|
||||
{
|
||||
public class LZKWAJ : BaseBinaryDeserializer<KWAJFile>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override KWAJFile? Deserialize(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new file to fill
|
||||
var file = new KWAJFile();
|
||||
|
||||
#region File Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extended Header
|
||||
|
||||
if (header.HeaderFlags != 0)
|
||||
{
|
||||
var extensions = new KWAJHeaderExtensions();
|
||||
|
||||
#if NET20 || NET35
|
||||
if ((header.HeaderFlags & KWAJHeaderFlags.HasDecompressedLength) != 0)
|
||||
extensions.DecompressedLength = data.ReadUInt32();
|
||||
if ((header.HeaderFlags & KWAJHeaderFlags.HasUnknownFlag) != 0)
|
||||
extensions.UnknownPurpose = data.ReadUInt16();
|
||||
if ((header.HeaderFlags & KWAJHeaderFlags.HasPrefixedData) != 0)
|
||||
{
|
||||
extensions.UnknownDataLength = data.ReadUInt16();
|
||||
extensions.UnknownData = data.ReadBytes((int)extensions.UnknownDataLength);
|
||||
}
|
||||
if ((header.HeaderFlags & KWAJHeaderFlags.HasFileName) != 0)
|
||||
extensions.FileName = data.ReadNullTerminatedAnsiString();
|
||||
if ((header.HeaderFlags & KWAJHeaderFlags.HasFileExtension) != 0)
|
||||
extensions.FileExtension = data.ReadNullTerminatedAnsiString();
|
||||
if ((header.HeaderFlags & KWAJHeaderFlags.HasPrefixedData) != 0)
|
||||
{
|
||||
extensions.ArbitraryTextLength = data.ReadUInt16();
|
||||
extensions.ArbitraryText = data.ReadBytes((int)extensions.ArbitraryTextLength);
|
||||
}
|
||||
#else
|
||||
if (header.HeaderFlags.HasFlag(KWAJHeaderFlags.HasDecompressedLength))
|
||||
extensions.DecompressedLength = data.ReadUInt32();
|
||||
if (header.HeaderFlags.HasFlag(KWAJHeaderFlags.HasUnknownFlag))
|
||||
extensions.UnknownPurpose = data.ReadUInt16();
|
||||
if (header.HeaderFlags.HasFlag(KWAJHeaderFlags.HasPrefixedData))
|
||||
{
|
||||
extensions.UnknownDataLength = data.ReadUInt16();
|
||||
extensions.UnknownData = data.ReadBytes((int)extensions.UnknownDataLength);
|
||||
}
|
||||
if (header.HeaderFlags.HasFlag(KWAJHeaderFlags.HasFileName))
|
||||
extensions.FileName = data.ReadNullTerminatedAnsiString();
|
||||
if (header.HeaderFlags.HasFlag(KWAJHeaderFlags.HasFileExtension))
|
||||
extensions.FileExtension = data.ReadNullTerminatedAnsiString();
|
||||
if (header.HeaderFlags.HasFlag(KWAJHeaderFlags.HasPrefixedData))
|
||||
{
|
||||
extensions.ArbitraryTextLength = data.ReadUInt16();
|
||||
extensions.ArbitraryText = data.ReadBytes((int)extensions.ArbitraryTextLength);
|
||||
}
|
||||
#endif
|
||||
|
||||
file.HeaderExtensions = extensions;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled header on success, null on error</returns>
|
||||
private static KWAJHeader? ParseHeader(Stream data)
|
||||
{
|
||||
var header = new KWAJHeader();
|
||||
|
||||
header.Magic = data.ReadBytes(8);
|
||||
if (Encoding.ASCII.GetString(header.Magic) != Encoding.ASCII.GetString(KWAJSignatureBytes))
|
||||
return null;
|
||||
|
||||
header.CompressionType = (KWAJCompressionType)data.ReadUInt16();
|
||||
if (header.CompressionType > KWAJCompressionType.MSZIP)
|
||||
return null;
|
||||
|
||||
header.DataOffset = data.ReadUInt16();
|
||||
header.HeaderFlags = (KWAJHeaderFlags)data.ReadUInt16();
|
||||
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
SabreTools.Serialization/Deserializers/LZQBasic.cs
Normal file
65
SabreTools.Serialization/Deserializers/LZQBasic.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.LZ;
|
||||
using static SabreTools.Models.LZ.Constants;
|
||||
|
||||
namespace SabreTools.Serialization.Deserializers
|
||||
{
|
||||
public class LZQBasic : BaseBinaryDeserializer<QBasicFile>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override QBasicFile? Deserialize(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new file to fill
|
||||
var file = new QBasicFile();
|
||||
|
||||
#region File Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled header on success, null on error</returns>
|
||||
private static QBasicHeader? ParseHeader(Stream data)
|
||||
{
|
||||
var header = new QBasicHeader();
|
||||
|
||||
header.Magic = data.ReadBytes(8);
|
||||
if (Encoding.ASCII.GetString(header.Magic) != Encoding.ASCII.GetString(QBasicSignatureBytes))
|
||||
return null;
|
||||
|
||||
header.RealLength = data.ReadUInt32();
|
||||
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
SabreTools.Serialization/Deserializers/LZSZDD.cs
Normal file
70
SabreTools.Serialization/Deserializers/LZSZDD.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using SabreTools.IO.Extensions;
|
||||
using SabreTools.Models.LZ;
|
||||
using static SabreTools.Models.LZ.Constants;
|
||||
|
||||
namespace SabreTools.Serialization.Deserializers
|
||||
{
|
||||
public class LZSZDD : BaseBinaryDeserializer<SZDDFile>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override SZDDFile? Deserialize(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// Cache the current offset
|
||||
int initialOffset = (int)data.Position;
|
||||
|
||||
// Create a new file to fill
|
||||
var file = new SZDDFile();
|
||||
|
||||
#region File Header
|
||||
|
||||
// Try to parse the header
|
||||
var header = ParseHeader(data);
|
||||
if (header == null)
|
||||
return null;
|
||||
|
||||
// Set the header
|
||||
file.Header = header;
|
||||
|
||||
#endregion
|
||||
|
||||
return file;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore the actual error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a header
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <returns>Filled header on success, null on error</returns>
|
||||
private static SZDDHeader? ParseHeader(Stream data)
|
||||
{
|
||||
var header = new SZDDHeader();
|
||||
|
||||
header.Magic = data.ReadBytes(8);
|
||||
if (Encoding.ASCII.GetString(header.Magic) != Encoding.ASCII.GetString(SZDDSignatureBytes))
|
||||
return null;
|
||||
|
||||
header.CompressionType = (ExpandCompressionType)data.ReadByteValue();
|
||||
if (header.CompressionType != ExpandCompressionType.A)
|
||||
return null;
|
||||
|
||||
header.LastChar = (char)data.ReadByteValue();
|
||||
header.RealLength = data.ReadUInt32();
|
||||
|
||||
return header;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,9 @@ namespace SabreTools.Serialization
|
||||
Wrapper.InstallShieldCabinet item => item.PrettyPrint(),
|
||||
Wrapper.IRD item => item.PrettyPrint(),
|
||||
Wrapper.LinearExecutable item => item.PrettyPrint(),
|
||||
Wrapper.LZKWAJ item => item.PrettyPrint(),
|
||||
Wrapper.LZQBasic item => item.PrettyPrint(),
|
||||
Wrapper.LZSZDD item => item.PrettyPrint(),
|
||||
Wrapper.MicrosoftCabinet item => item.PrettyPrint(),
|
||||
Wrapper.MoPaQ item => item.PrettyPrint(),
|
||||
Wrapper.MSDOS item => item.PrettyPrint(),
|
||||
@@ -90,6 +93,9 @@ namespace SabreTools.Serialization
|
||||
Wrapper.InstallShieldCabinet item => item.ExportJSON(),
|
||||
Wrapper.IRD item => item.ExportJSON(),
|
||||
Wrapper.LinearExecutable item => item.ExportJSON(),
|
||||
Wrapper.LZKWAJ item => item.ExportJSON(),
|
||||
Wrapper.LZQBasic item => item.ExportJSON(),
|
||||
Wrapper.LZSZDD item => item.ExportJSON(),
|
||||
Wrapper.MicrosoftCabinet item => item.ExportJSON(),
|
||||
Wrapper.MoPaQ item => item.ExportJSON(),
|
||||
Wrapper.MSDOS item => item.ExportJSON(),
|
||||
@@ -229,6 +235,36 @@ namespace SabreTools.Serialization
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export the item information as pretty-printed text
|
||||
/// </summary>
|
||||
private static StringBuilder PrettyPrint(this Wrapper.LZKWAJ item)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
LZKWAJ.Print(builder, item.Model);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export the item information as pretty-printed text
|
||||
/// </summary>
|
||||
private static StringBuilder PrettyPrint(this Wrapper.LZQBasic item)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
LZQBasic.Print(builder, item.Model);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export the item information as pretty-printed text
|
||||
/// </summary>
|
||||
private static StringBuilder PrettyPrint(this Wrapper.LZSZDD item)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
LZSZDD.Print(builder, item.Model);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Export the item information as pretty-printed text
|
||||
/// </summary>
|
||||
|
||||
63
SabreTools.Serialization/Printers/LZKWAJ.cs
Normal file
63
SabreTools.Serialization/Printers/LZKWAJ.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Text;
|
||||
using SabreTools.Models.LZ;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
namespace SabreTools.Serialization.Printers
|
||||
{
|
||||
public class LZKWAJ : IPrinter<KWAJFile>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void PrintInformation(StringBuilder builder, KWAJFile model)
|
||||
=> Print(builder, model);
|
||||
|
||||
public static void Print(StringBuilder builder, KWAJFile file)
|
||||
{
|
||||
builder.AppendLine("LZ-compressed File, KWAJ Variant Information:");
|
||||
builder.AppendLine("-------------------------");
|
||||
builder.AppendLine();
|
||||
|
||||
Print(builder, file.Header);
|
||||
Print(builder, file.HeaderExtensions);
|
||||
}
|
||||
|
||||
private static void Print(StringBuilder builder, KWAJHeader? header)
|
||||
{
|
||||
builder.AppendLine(" Header Information:");
|
||||
builder.AppendLine(" -------------------------");
|
||||
if (header == null)
|
||||
{
|
||||
builder.AppendLine(" No header");
|
||||
builder.AppendLine();
|
||||
return;
|
||||
}
|
||||
|
||||
builder.AppendLine(header.Magic, " Magic number");
|
||||
builder.AppendLine($" Compression type: {header.CompressionType} (0x{header.CompressionType:X})");
|
||||
builder.AppendLine(header.DataOffset, " Data offset");
|
||||
builder.AppendLine($" Header flags: {header.HeaderFlags} (0x{header.HeaderFlags:X})");
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
private static void Print(StringBuilder builder, KWAJHeaderExtensions? header)
|
||||
{
|
||||
builder.AppendLine(" Header Extensions Information:");
|
||||
builder.AppendLine(" -------------------------");
|
||||
if (header == null)
|
||||
{
|
||||
builder.AppendLine(" No header extensions");
|
||||
builder.AppendLine();
|
||||
return;
|
||||
}
|
||||
|
||||
builder.AppendLine(header.DecompressedLength, " Decompressed length");
|
||||
builder.AppendLine(header.UnknownPurpose, " Unknown purpose");
|
||||
builder.AppendLine(header.UnknownDataLength, " Unknown data length");
|
||||
builder.AppendLine(header.UnknownData, " Unknown data");
|
||||
builder.AppendLine(header.FileName, " File name");
|
||||
builder.AppendLine(header.FileExtension, " File extension");
|
||||
builder.AppendLine(header.ArbitraryTextLength, " Arbitrary text length");
|
||||
builder.AppendLine(header.ArbitraryText, " Arbitrary text");
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
38
SabreTools.Serialization/Printers/LZQBasic.cs
Normal file
38
SabreTools.Serialization/Printers/LZQBasic.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Text;
|
||||
using SabreTools.Models.LZ;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
namespace SabreTools.Serialization.Printers
|
||||
{
|
||||
public class LZQBasic : IPrinter<QBasicFile>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void PrintInformation(StringBuilder builder, QBasicFile model)
|
||||
=> Print(builder, model);
|
||||
|
||||
public static void Print(StringBuilder builder, QBasicFile file)
|
||||
{
|
||||
builder.AppendLine("LZ-compressed File, QBasic Variant Information:");
|
||||
builder.AppendLine("-------------------------");
|
||||
builder.AppendLine();
|
||||
|
||||
Print(builder, file.Header);
|
||||
}
|
||||
|
||||
private static void Print(StringBuilder builder, QBasicHeader? header)
|
||||
{
|
||||
builder.AppendLine(" Header Information:");
|
||||
builder.AppendLine(" -------------------------");
|
||||
if (header == null)
|
||||
{
|
||||
builder.AppendLine(" No header");
|
||||
builder.AppendLine();
|
||||
return;
|
||||
}
|
||||
|
||||
builder.AppendLine(header.Magic, " Magic number");
|
||||
builder.AppendLine(header.RealLength, " Real length");
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
40
SabreTools.Serialization/Printers/LZSZDD.cs
Normal file
40
SabreTools.Serialization/Printers/LZSZDD.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Text;
|
||||
using SabreTools.Models.LZ;
|
||||
using SabreTools.Serialization.Interfaces;
|
||||
|
||||
namespace SabreTools.Serialization.Printers
|
||||
{
|
||||
public class LZSZDD : IPrinter<SZDDFile>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void PrintInformation(StringBuilder builder, SZDDFile model)
|
||||
=> Print(builder, model);
|
||||
|
||||
public static void Print(StringBuilder builder, SZDDFile file)
|
||||
{
|
||||
builder.AppendLine("LZ-compressed File, SZDD Variant Information:");
|
||||
builder.AppendLine("-------------------------");
|
||||
builder.AppendLine();
|
||||
|
||||
Print(builder, file.Header);
|
||||
}
|
||||
|
||||
private static void Print(StringBuilder builder, SZDDHeader? header)
|
||||
{
|
||||
builder.AppendLine(" Header Information:");
|
||||
builder.AppendLine(" -------------------------");
|
||||
if (header == null)
|
||||
{
|
||||
builder.AppendLine(" No header");
|
||||
builder.AppendLine();
|
||||
return;
|
||||
}
|
||||
|
||||
builder.AppendLine(header.Magic, " Magic number");
|
||||
builder.AppendLine($" Compression type: {header.CompressionType} (0x{header.CompressionType:X})");
|
||||
builder.AppendLine(header.LastChar, " Last char");
|
||||
builder.AppendLine(header.RealLength, " Real length");
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="SabreTools.ASN1" Version="1.5.0" />
|
||||
<PackageReference Include="SabreTools.Compression" Version="0.6.2" />
|
||||
<PackageReference Include="SabreTools.Hashing" Version="1.4.0" />
|
||||
<PackageReference Include="SabreTools.IO" Version="1.6.1" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.5.6" />
|
||||
<PackageReference Include="SabreTools.Models" Version="1.5.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
139
SabreTools.Serialization/Wrappers/LZKWAJ.cs
Normal file
139
SabreTools.Serialization/Wrappers/LZKWAJ.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
using System.IO;
|
||||
using SabreTools.Compression.SZDD;
|
||||
using SabreTools.Models.LZ;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
public class LZKWAJ : WrapperBase<KWAJFile>
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string DescriptionString => "LZ-compressed file, KWAJ variant";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LZKWAJ(KWAJFile? model, byte[]? data, int offset)
|
||||
: base(model, data, offset)
|
||||
{
|
||||
// All logic is handled by the base class
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LZKWAJ(KWAJFile? model, Stream? data)
|
||||
: base(model, data)
|
||||
{
|
||||
// All logic is handled by the base class
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an LZ (KWAJ variant) from a byte array and offset
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing the LZ (KWAJ variant)</param>
|
||||
/// <param name="offset">Offset within the array to parse</param>
|
||||
/// <returns>An LZ (KWAJ variant) wrapper on success, null on failure</returns>
|
||||
public static LZKWAJ? Create(byte[]? data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and use that
|
||||
var dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return Create(dataStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a LZ (KWAJ variant) from a Stream
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the LZ (KWAJ variant)</param>
|
||||
/// <returns>An LZ (KWAJ variant) wrapper on success, null on failure</returns>
|
||||
public static LZKWAJ? Create(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var file = Deserializers.LZKWAJ.DeserializeStream(data);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
return new LZKWAJ(file, data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Extract the contents to an output directory
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <returns>True if the contents extracted, false otherwise</returns>
|
||||
public bool Extract(string outputDirectory)
|
||||
{
|
||||
// Get the length of the compressed data
|
||||
long compressedSize = Length - Model.Header!.DataOffset;
|
||||
if (compressedSize < Model.Header.DataOffset)
|
||||
return false;
|
||||
|
||||
// Read in the data as an array
|
||||
byte[]? contents = ReadFromDataSource(Model.Header.DataOffset, (int)compressedSize);
|
||||
if (contents == null)
|
||||
return false;
|
||||
|
||||
// Get the decompressor
|
||||
var decompressor = Decompressor.CreateKWAJ(contents, Model.Header!.CompressionType);
|
||||
if (decompressor == null)
|
||||
return false;
|
||||
|
||||
// If we have an invalid output directory
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
return false;
|
||||
|
||||
// Create the full output path
|
||||
string filename = "tempfile";
|
||||
if (Model.HeaderExtensions?.FileName != null)
|
||||
filename = Model.HeaderExtensions.FileName;
|
||||
if (Model.HeaderExtensions?.FileExtension != null)
|
||||
filename += $".{Model.HeaderExtensions.FileExtension}";
|
||||
|
||||
filename = Path.Combine(outputDirectory, filename);
|
||||
|
||||
// Ensure the output directory is created
|
||||
var directoryName = Path.GetDirectoryName(filename);
|
||||
if (directoryName != null)
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
// Try to write the data
|
||||
try
|
||||
{
|
||||
// Open the output file for writing
|
||||
using Stream fs = File.OpenWrite(filename);
|
||||
decompressor.CopyTo(fs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
134
SabreTools.Serialization/Wrappers/LZQBasic.cs
Normal file
134
SabreTools.Serialization/Wrappers/LZQBasic.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System.IO;
|
||||
using SabreTools.Compression.SZDD;
|
||||
using SabreTools.Models.LZ;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
public class LZQBasic : WrapperBase<QBasicFile>
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string DescriptionString => "LZ-compressed file, QBasic variant";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LZQBasic(QBasicFile? model, byte[]? data, int offset)
|
||||
: base(model, data, offset)
|
||||
{
|
||||
// All logic is handled by the base class
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LZQBasic(QBasicFile? model, Stream? data)
|
||||
: base(model, data)
|
||||
{
|
||||
// All logic is handled by the base class
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an LZ (QBasic variant) from a byte array and offset
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing the LZ (QBasic variant)</param>
|
||||
/// <param name="offset">Offset within the array to parse</param>
|
||||
/// <returns>An LZ (QBasic variant) wrapper on success, null on failure</returns>
|
||||
public static LZQBasic? Create(byte[]? data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and use that
|
||||
var dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return Create(dataStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a LZ (QBasic variant) from a Stream
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the LZ (QBasic variant)</param>
|
||||
/// <returns>An LZ (QBasic variant) wrapper on success, null on failure</returns>
|
||||
public static LZQBasic? Create(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var file = Deserializers.LZQBasic.DeserializeStream(data);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
return new LZQBasic(file, data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Extract the contents to an output directory
|
||||
/// </summary>
|
||||
/// <param name="filename">Original filename to use as a base</param>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <returns>True if the contents extracted, false otherwise</returns>
|
||||
public bool Extract(string outputDirectory)
|
||||
{
|
||||
// Get the length of the compressed data
|
||||
long compressedSize = Length - 12;
|
||||
if (compressedSize < 12)
|
||||
return false;
|
||||
|
||||
// Read in the data as an array
|
||||
byte[]? contents = ReadFromDataSource(12, (int)compressedSize);
|
||||
if (contents == null)
|
||||
return false;
|
||||
|
||||
// Get the decompressor
|
||||
var decompressor = Decompressor.CreateQBasic(contents);
|
||||
if (decompressor == null)
|
||||
return false;
|
||||
|
||||
// If we have an invalid output directory
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
return false;
|
||||
|
||||
// Create the full output path
|
||||
string filename = Path.Combine(outputDirectory, "tempfile.bin");
|
||||
|
||||
// Ensure the output directory is created
|
||||
var directoryName = Path.GetDirectoryName(filename);
|
||||
if (directoryName != null)
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
// Try to write the data
|
||||
try
|
||||
{
|
||||
// Open the output file for writing
|
||||
using Stream fs = File.OpenWrite(filename);
|
||||
decompressor.CopyTo(fs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
166
SabreTools.Serialization/Wrappers/LZSZDD.cs
Normal file
166
SabreTools.Serialization/Wrappers/LZSZDD.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System.IO;
|
||||
using SabreTools.Compression.SZDD;
|
||||
using SabreTools.Models.LZ;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
{
|
||||
public class LZSZDD : WrapperBase<SZDDFile>
|
||||
{
|
||||
#region Descriptive Properties
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string DescriptionString => "LZ-compressed file, SZDD variant";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LZSZDD(SZDDFile? model, byte[]? data, int offset)
|
||||
: base(model, data, offset)
|
||||
{
|
||||
// All logic is handled by the base class
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LZSZDD(SZDDFile? model, Stream? data)
|
||||
: base(model, data)
|
||||
{
|
||||
// All logic is handled by the base class
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an LZ (SZDD variant) from a byte array and offset
|
||||
/// </summary>
|
||||
/// <param name="data">Byte array representing the LZ (SZDD variant)</param>
|
||||
/// <param name="offset">Offset within the array to parse</param>
|
||||
/// <returns>An LZ (SZDD variant) wrapper on success, null on failure</returns>
|
||||
public static LZSZDD? Create(byte[]? data, int offset)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || data.Length == 0)
|
||||
return null;
|
||||
|
||||
// If the offset is out of bounds
|
||||
if (offset < 0 || offset >= data.Length)
|
||||
return null;
|
||||
|
||||
// Create a memory stream and use that
|
||||
var dataStream = new MemoryStream(data, offset, data.Length - offset);
|
||||
return Create(dataStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a LZ (SZDD variant) from a Stream
|
||||
/// </summary>
|
||||
/// <param name="data">Stream representing the LZ (SZDD variant)</param>
|
||||
/// <returns>An LZ (SZDD variant) wrapper on success, null on failure</returns>
|
||||
public static LZSZDD? Create(Stream? data)
|
||||
{
|
||||
// If the data is invalid
|
||||
if (data == null || !data.CanRead)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var file = Deserializers.LZSZDD.DeserializeStream(data);
|
||||
if (file == null)
|
||||
return null;
|
||||
|
||||
return new LZSZDD(file, data);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Extract the contents to an output directory
|
||||
/// </summary>
|
||||
/// <param name="filename">Original filename to use as a base</param>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <returns>True if the contents extracted, false otherwise</returns>
|
||||
public bool Extract(string filename, string outputDirectory)
|
||||
{
|
||||
// Get the length of the compressed data
|
||||
long compressedSize = Length - 14;
|
||||
if (compressedSize < 14)
|
||||
return false;
|
||||
|
||||
// Read in the data as an array
|
||||
byte[]? contents = ReadFromDataSource(14, (int)compressedSize);
|
||||
if (contents == null)
|
||||
return false;
|
||||
|
||||
// Get the decompressor
|
||||
var decompressor = Decompressor.CreateSZDD(contents);
|
||||
if (decompressor == null)
|
||||
return false;
|
||||
|
||||
// Create the output file
|
||||
filename = GetExpandedName(filename).TrimEnd('\0');
|
||||
|
||||
// If we have an invalid output directory
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
return false;
|
||||
|
||||
// Create the full output path
|
||||
filename = Path.Combine(outputDirectory, filename);
|
||||
|
||||
// Ensure the output directory is created
|
||||
var directoryName = Path.GetDirectoryName(filename);
|
||||
if (directoryName != null)
|
||||
Directory.CreateDirectory(directoryName);
|
||||
|
||||
// Try to write the data
|
||||
try
|
||||
{
|
||||
// Open the output file for writing
|
||||
using Stream fs = File.OpenWrite(filename);
|
||||
decompressor.CopyTo(fs);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the full name of the input file
|
||||
/// </summary>
|
||||
private string GetExpandedName(string input)
|
||||
{
|
||||
// If the extension is missing
|
||||
string extension = Path.GetExtension(input).TrimStart('.');
|
||||
if (string.IsNullOrEmpty(extension))
|
||||
return Path.GetFileNameWithoutExtension(input);
|
||||
|
||||
// If the extension is a single character
|
||||
if (extension.Length == 1)
|
||||
{
|
||||
if (extension == "_" || extension == "$")
|
||||
return $"{Path.GetFileNameWithoutExtension(input)}.{char.ToLower(Model.Header!.LastChar)}";
|
||||
|
||||
return Path.GetFileNameWithoutExtension(input);
|
||||
}
|
||||
|
||||
// If the extension isn't formatted
|
||||
if (!extension.EndsWith("_"))
|
||||
return Path.GetFileNameWithoutExtension(input);
|
||||
|
||||
// Handle replacing characters
|
||||
char c = (char.IsUpper(input[0]) ? char.ToLower(Model.Header!.LastChar) : char.ToUpper(Model.Header!.LastChar));
|
||||
string text2 = extension.Substring(0, extension.Length - 1) + c;
|
||||
return Path.GetFileNameWithoutExtension(input) + "." + text2;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SabreTools.Compression.zlib;
|
||||
using SabreTools.Models.SGA;
|
||||
|
||||
namespace SabreTools.Serialization.Wrappers
|
||||
@@ -120,6 +122,148 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extraction
|
||||
|
||||
/// <summary>
|
||||
/// Extract all files from the SGA to an output directory
|
||||
/// </summary>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <returns>True if all files extracted, false otherwise</returns>
|
||||
public bool ExtractAll(string outputDirectory)
|
||||
{
|
||||
// Get the file count
|
||||
int fileCount = FileCount;
|
||||
if (fileCount == 0)
|
||||
return false;
|
||||
|
||||
// Loop through and extract all files to the output
|
||||
bool allExtracted = true;
|
||||
for (int i = 0; i < fileCount; i++)
|
||||
{
|
||||
allExtracted &= ExtractFile(i, outputDirectory);
|
||||
}
|
||||
|
||||
return allExtracted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract a file from the SGA to an output directory by index
|
||||
/// </summary>
|
||||
/// <param name="index">File index to extract</param>
|
||||
/// <param name="outputDirectory">Output directory to write to</param>
|
||||
/// <returns>True if the file extracted, false otherwise</returns>
|
||||
public bool ExtractFile(int index, string outputDirectory)
|
||||
{
|
||||
// Get the file count
|
||||
int fileCount = FileCount;
|
||||
if (fileCount == 0)
|
||||
return false;
|
||||
|
||||
// If the files index is invalid
|
||||
if (index < 0 || index >= fileCount)
|
||||
return false;
|
||||
|
||||
// Create the filename
|
||||
var filename = GetFileName(index);
|
||||
if (filename == null)
|
||||
return false;
|
||||
|
||||
// Loop through and get all parent directories
|
||||
var parentNames = new List<string> { filename };
|
||||
|
||||
// Get the parent directory
|
||||
string? folderName = GetParentName(index);
|
||||
if (folderName != null)
|
||||
parentNames.Add(folderName);
|
||||
|
||||
// TODO: Should the section name/alias be used in the path as well?
|
||||
|
||||
// Reverse and assemble the filename
|
||||
parentNames.Reverse();
|
||||
#if NET20 || NET35
|
||||
filename = parentNames[0];
|
||||
for (int i = 1; i < parentNames.Count; i++)
|
||||
{
|
||||
filename = Path.Combine(filename, parentNames[i]);
|
||||
}
|
||||
#else
|
||||
filename = Path.Combine([.. parentNames]);
|
||||
#endif
|
||||
|
||||
// Get and adjust the file offset
|
||||
long fileOffset = GetFileOffset(index);
|
||||
fileOffset += FileDataOffset;
|
||||
if (fileOffset < 0)
|
||||
return false;
|
||||
|
||||
// Get the file sizes
|
||||
long fileSize = GetCompressedSize(index);
|
||||
long outputFileSize = GetUncompressedSize(index);
|
||||
|
||||
// Read the compressed data directly
|
||||
var compressedData = ReadFromDataSource((int)fileOffset, (int)fileSize);
|
||||
if (compressedData == null)
|
||||
return false;
|
||||
|
||||
// If the compressed and uncompressed sizes match
|
||||
byte[] data;
|
||||
if (fileSize == outputFileSize)
|
||||
{
|
||||
data = compressedData;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inflate the data into the buffer
|
||||
var zstream = new ZLib.z_stream_s();
|
||||
data = new byte[outputFileSize];
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* payloadPtr = compressedData)
|
||||
fixed (byte* dataPtr = data)
|
||||
{
|
||||
zstream.next_in = payloadPtr;
|
||||
zstream.avail_in = (uint)compressedData.Length;
|
||||
zstream.total_in = (uint)compressedData.Length;
|
||||
zstream.next_out = dataPtr;
|
||||
zstream.avail_out = (uint)data.Length;
|
||||
zstream.total_out = 0;
|
||||
|
||||
ZLib.inflateInit_(zstream, ZLib.zlibVersion(), compressedData.Length);
|
||||
int zret = ZLib.inflate(zstream, 1);
|
||||
ZLib.inflateEnd(zstream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have an invalid output directory
|
||||
if (string.IsNullOrEmpty(outputDirectory))
|
||||
return false;
|
||||
|
||||
// Create the full output path
|
||||
filename = Path.Combine(outputDirectory, filename);
|
||||
|
||||
// Ensure the output directory is created
|
||||
var directoryName = Path.GetDirectoryName(filename);
|
||||
if (directoryName != null)
|
||||
System.IO.Directory.CreateDirectory(directoryName);
|
||||
|
||||
// Try to write the data
|
||||
try
|
||||
{
|
||||
// Open the output file for writing
|
||||
using Stream fs = System.IO.File.OpenWrite(filename);
|
||||
fs.Write(data, 0, data.Length);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region File
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -45,6 +45,24 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// </summary>
|
||||
public T Model { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Length of the underlying data
|
||||
/// </summary>
|
||||
public long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return _dataSource switch
|
||||
{
|
||||
DataSource.ByteArray => _byteArrayData!.Length - _byteArrayOffset,
|
||||
DataSource.Stream => _streamData!.Length,
|
||||
|
||||
// Everything else is invalid
|
||||
_ => -1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Instance Variables
|
||||
|
||||
@@ -31,8 +31,10 @@ namespace SabreTools.Serialization.Wrappers
|
||||
WrapperType.InstallShieldArchiveV3 => null,// TODO: Implement wrapper
|
||||
WrapperType.InstallShieldCAB => InstallShieldCabinet.Create(data),
|
||||
WrapperType.LDSCRYPT => null,// TODO: Implement wrapper
|
||||
WrapperType.LZKWAJ => LZKWAJ.Create(data),
|
||||
WrapperType.LZQBasic => LZQBasic.Create(data),
|
||||
WrapperType.LZSZDD => LZSZDD.Create(data),
|
||||
WrapperType.MicrosoftCAB => MicrosoftCabinet.Create(data),
|
||||
WrapperType.MicrosoftLZ => null,// TODO: Implement wrapper
|
||||
WrapperType.MoPaQ => MoPaQ.Create(data),
|
||||
WrapperType.N3DS => N3DS.Create(data),
|
||||
WrapperType.NCF => NCF.Create(data),
|
||||
@@ -330,6 +332,19 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region LZ
|
||||
|
||||
if (magic.StartsWith([0x4B, 0x57, 0x41, 0x4A, 0x88, 0xF0, 0x27, 0xD1]))
|
||||
return WrapperType.LZKWAJ;
|
||||
|
||||
if (magic.StartsWith([0x53, 0x5A, 0x20, 0x88, 0xF0, 0x27, 0x33, 0xD1]))
|
||||
return WrapperType.LZQBasic;
|
||||
|
||||
if (magic.StartsWith([0x53, 0x5A, 0x44, 0x44, 0x88, 0xF0, 0x27, 0x33]))
|
||||
return WrapperType.LZSZDD;
|
||||
|
||||
#endregion
|
||||
|
||||
#region MicrosoftCAB
|
||||
|
||||
if (magic.StartsWith([0x4d, 0x53, 0x43, 0x46]))
|
||||
@@ -339,13 +354,6 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
#region MicrosoftLZ
|
||||
|
||||
if (magic.StartsWith([0x53, 0x5a, 0x44, 0x44, 0x88, 0xf0, 0x27, 0x33]))
|
||||
return WrapperType.MicrosoftLZ;
|
||||
|
||||
#endregion
|
||||
|
||||
#region MoPaQ
|
||||
|
||||
if (magic.StartsWith([0x4d, 0x50, 0x51, 0x1a]))
|
||||
|
||||
@@ -91,17 +91,26 @@ namespace SabreTools.Serialization.Wrappers
|
||||
/// <remarks>Currently has no IWrapper implementation</remarks>
|
||||
LDSCRYPT,
|
||||
|
||||
/// <summary>
|
||||
/// LZ-compressed file, KWAJ variant
|
||||
/// </summary>
|
||||
LZKWAJ,
|
||||
|
||||
/// <summary>
|
||||
/// LZ-compressed file, QBasic variant
|
||||
/// </summary>
|
||||
LZQBasic,
|
||||
|
||||
/// <summary>
|
||||
/// LZ-compressed file, SZDD variant
|
||||
/// </summary>
|
||||
LZSZDD,
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft cabinet file
|
||||
/// </summary>
|
||||
MicrosoftCAB,
|
||||
|
||||
/// <summary>
|
||||
/// Microsoft LZ-compressed file
|
||||
/// </summary>
|
||||
/// <remarks>Currently has no IWrapper implementation</remarks>
|
||||
MicrosoftLZ,
|
||||
|
||||
/// <summary>
|
||||
/// MPQ game data archive
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user