27 Commits
0.4.3 ... 0.6.0

Author SHA1 Message Date
Matt Nadareski
16d8d9839c Bump version 2024-11-13 02:05:29 -05:00
Matt Nadareski
dfcbce8874 Add .NET 9 to target frameworks 2024-11-13 02:05:05 -05:00
Matt Nadareski
fa5084d067 Bump version 2024-10-01 13:43:37 -04:00
Matt Nadareski
d9e1748ec1 Update packages 2024-10-01 13:43:02 -04:00
Matt Nadareski
84a3e515c0 Update library versions 2024-09-25 10:52:34 -04:00
Matt Nadareski
e217f4109c Bump version 2024-04-26 21:42:53 -04:00
Matt Nadareski
882f2c5335 Update SabreTools.IO 2024-04-26 21:41:13 -04:00
Matt Nadareski
3f6c7dc0d6 Bump version 2024-04-24 10:11:31 -04:00
Matt Nadareski
bf413cbb85 Update SabreTools.Models 2024-04-24 10:08:00 -04:00
Matt Nadareski
7a403cf368 Make Linux publish script executable 2024-04-24 01:34:40 -04:00
Matt Nadareski
aec4611d14 Add publish scripts 2024-04-24 01:33:42 -04:00
Matt Nadareski
7052584cea Add incomplete compressions to table 2024-04-24 01:30:47 -04:00
Matt Nadareski
efe6c545b9 Add a couple of tags 2024-04-24 01:29:30 -04:00
Matt Nadareski
612a8b3c83 Use constants that were defined 2024-04-24 00:50:35 -04:00
Matt Nadareski
bc06cb5bdb Port extension code for zlib constant names from UnshieldSharp 2024-04-24 00:49:34 -04:00
Matt Nadareski
ae223a4589 Port Blast code from UnshieldSharp 2024-04-24 00:47:31 -04:00
Matt Nadareski
018fd01922 Port zlibConst from UnshieldSharp 2024-04-24 00:35:12 -04:00
Matt Nadareski
910b01b072 Update packages 2024-04-23 21:07:02 -04:00
Matt Nadareski
d239d9f09b Bump version 2024-04-18 12:08:56 -04:00
Matt Nadareski
0cf3e3e816 Update SabreTools.IO 2024-04-18 12:05:30 -04:00
Matt Nadareski
fe319b71f1 Bump version 2024-04-16 22:10:41 -04:00
Matt Nadareski
cd5bf99f21 File formatting changes 2024-04-16 22:09:11 -04:00
Matt Nadareski
f79b5353f7 Add empty test project 2024-04-16 22:06:54 -04:00
Matt Nadareski
5e184b03c5 Move project files to subfolder 2024-04-16 22:02:51 -04:00
Matt Nadareski
a1417d2f8a Minor syntax cleanup 2024-04-16 22:00:58 -04:00
Matt Nadareski
7580d49830 Migrate to ReadOnlyBitStream 2024-04-16 21:57:31 -04:00
Matt Nadareski
c04c7d438d Update SabreTools.IO 2024-04-16 21:55:02 -04:00
32 changed files with 927 additions and 328 deletions

View File

@@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore

View File

@@ -11,7 +11,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Build
run: dotnet build

View File

@@ -1,239 +0,0 @@
using System;
using System.IO;
using SabreTools.IO;
// TODO: Add more complete implementation to SabreTools.IO
namespace SabreTools.Compression
{
/// <summary>
/// Wrapper to allow reading bits from a source stream
/// </summary>
public class BitStream
{
/// <inheritdoc cref="Stream.Position"/>
public long Position => _source.Position;
/// <inheritdoc cref="Stream.Length"/>
public long Length => _source.Length;
/// <summary>
/// Original stream source
/// </summary>
private readonly Stream _source;
/// <summary>
/// Last read byte value from the stream
/// </summary>
private byte? _bitBuffer;
/// <summary>
/// Index in the byte of the current bit
/// </summary>
private int _bitIndex;
/// <summary>
/// Create a new BitStream from a source Stream
/// </summary>
public BitStream(Stream? source)
{
if (source == null || !source.CanRead || !source.CanSeek)
throw new ArgumentException(nameof(source));
_source = source;
_bitBuffer = null;
_bitIndex = 0;
}
/// <summary>
/// Discard the current cached byte
/// </summary>
public void Discard()
{
_bitBuffer = null;
_bitIndex = 0;
}
/// <summary>
/// Read a single bit, if possible
/// </summary>
/// <returns>The next bit encoded in a byte, null on error or end of stream</returns>
public byte? ReadBit()
{
// If we reached the end of the stream
if (_source.Position >= _source.Length)
return null;
// If we don't have a value cached
if (_bitBuffer == null)
{
// Read the next byte, if possible
_bitBuffer = ReadSourceByte();
if (_bitBuffer == null)
return null;
// Reset the bit index
_bitIndex = 0;
}
// Get the value by bit-shifting
int value = _bitBuffer.Value & 0x01;
_bitBuffer = (byte?)(_bitBuffer >> 1);
_bitIndex++;
// Reset the byte if we're at the end
if (_bitIndex >= 8)
Discard();
return (byte)value;
}
/// <summary>
/// Read a multiple bits in LSB, if possible
/// </summary>
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
public uint? ReadBitsLSB(int bits)
{
uint value = 0;
for (int i = 0; i < bits; i++)
{
// Read the next bit
byte? bitValue = ReadBit();
if (bitValue == null)
return null;
// Add the bit shifted by the current index
value += (uint)(bitValue.Value << i);
}
return value;
}
/// <summary>
/// Read a multiple bits in MSB, if possible
/// </summary>
/// <returns>The next bits encoded in a UInt32, null on error or end of stream</returns>
public uint? ReadBitsMSB(int bits)
{
uint value = 0;
for (int i = 0; i < bits; i++)
{
// Read the next bit
byte? bitValue = ReadBit();
if (bitValue == null)
return null;
// Add the bit shifted by the current index
value += (value << 1) + bitValue.Value;
}
return value;
}
/// <summary>
/// Read a byte, if possible
/// </summary>
/// <returns>The next byte, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public byte? ReadByte()
{
try
{
Discard();
return _source.ReadByteValue();
}
catch
{
return null;
}
}
/// <summary>
/// Read a UInt16, if possible
/// </summary>
/// <returns>The next UInt16, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public ushort? ReadUInt16()
{
try
{
Discard();
return _source.ReadUInt16();
}
catch
{
return null;
}
}
/// <summary>
/// Read a UInt32, if possible
/// </summary>
/// <returns>The next UInt32, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public uint? ReadUInt32()
{
try
{
Discard();
return _source.ReadUInt32();
}
catch
{
return null;
}
}
/// <summary>
/// Read a UInt64, if possible
/// </summary>
/// <returns>The next UInt64, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public ulong? ReadUInt64()
{
try
{
Discard();
return _source.ReadUInt64();
}
catch
{
return null;
}
}
/// <summary>
/// Read <paramref name="bytes"/> bytes, if possible
/// </summary>
/// <param name="bytes">Number of bytes to read</param>
/// <returns>The next <paramref name="bytes"/> bytes, null on error or end of stream</returns>
/// <remarks>Assumes the stream is byte-aligned</remarks>
public byte[]? ReadBytes(int bytes)
{
try
{
Discard();
return _source.ReadBytes(bytes);
}
catch
{
return null;
}
}
/// <summary>
/// Read a single byte from the underlying stream, if possible
/// </summary>
/// <returns>The next full byte from the stream, null on error or end of stream</returns>
private byte? ReadSourceByte()
{
try
{
return _source.ReadByteValue();
}
catch
{
return null;
}
}
}
}

View File

@@ -8,8 +8,11 @@ Find the link to the Nuget package [here](https://www.nuget.org/packages/SabreTo
| Compression Name | Decompress | Compress |
| --- | --- | --- |
| Blast | Yes | No |
| LZ | Yes | No |
| LZX | No | No |
| MSZIP | Yes* | No |
| Quantum | No | No |
**Note:** If something is marked with a `*` it means that it need testing.

View File

@@ -1,34 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0</TargetFrameworks>
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.4.3</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Clean compression implementations</Description>
<Copyright>Copyright (c) Matt Nadareski 2022-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Compression</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>compression decompression lz mszip</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.IO" Version="1.3.4" />
<PackageReference Include="SabreTools.Models" Version="1.4.2" />
</ItemGroup>
</Project>

View File

@@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Compression", "SabreTools.Compression.csproj", "{B26E863F-8509-48BB-BABA-4FF83DB28D2A}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SabreTools.Compression", "SabreTools.Compression\SabreTools.Compression.csproj", "{B26E863F-8509-48BB-BABA-4FF83DB28D2A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -18,5 +20,9 @@ Global
{B26E863F-8509-48BB-BABA-4FF83DB28D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B26E863F-8509-48BB-BABA-4FF83DB28D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B26E863F-8509-48BB-BABA-4FF83DB28D2A}.Release|Any CPU.Build.0 = Release|Any CPU
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FDF6EF41-1B5A-4F3B-A7DD-DEB810E55A30}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,270 @@
/* blast.c
* Copyright (C) 2003, 2012, 2013 Mark Adler
* For conditions of distribution and use, see copyright notice in blast.h
* version 1.3, 24 Aug 2013
*
* blast.c decompresses data compressed by the PKWare Compression Library.
* This function provides functionality similar to the explode() function of
* the PKWare library, hence the name "blast".
*
* This decompressor is based on the excellent format description provided by
* Ben Rudiak-Gould in comp.compression on August 13, 2001. Interestingly, the
* example Ben provided in the post is incorrect. The distance 110001 should
* instead be 111000. When corrected, the example byte stream becomes:
*
* 00 04 82 24 25 8f 80 7f
*
* which decompresses to "AIAIAIAIAIAIA" (without the quotes).
*/
/*
* Change history:
*
* 1.0 12 Feb 2003 - First version
* 1.1 16 Feb 2003 - Fixed distance check for > 4 GB uncompressed data
* 1.2 24 Oct 2012 - Add note about using binary mode in stdio
* - Fix comparisons of differently signed integers
* 1.3 24 Aug 2013 - Return unused input from blast()
* - Fix test code to correctly report unused input
* - Enable the provision of initial input to blast()
*/
using System;
using System.Collections.Generic;
using static SabreTools.Compression.Blast.Constants;
namespace SabreTools.Compression.Blast
{
public unsafe static class BlastDecoder
{
#region Huffman Encoding
/// <summary>
/// Literal code
/// </summary>
private static readonly Huffman litcode = new(MAXBITS + 1, 256);
/// <summary>
/// Length code
/// </summary>
private static readonly Huffman lencode = new(MAXBITS + 1, 16);
/// <summary>
/// Distance code
/// </summary>
private static readonly Huffman distcode = new(MAXBITS + 1, 64);
/// <summary>
/// Base for length codes
/// </summary>
private static readonly short[] baseLength =
[
3, 2, 4, 5, 6, 7, 8, 9, 10, 12, 16, 24, 40, 72, 136, 264
];
/// <summary>
/// Extra bits for length codes
/// </summary>
private static readonly byte[] extra =
[
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8
];
#endregion
/// <summary>
/// Static constructor
/// </summary>
static BlastDecoder()
{
// Repeated code lengths of literal codes
byte[] litlen =
[
11, 124, 8, 7, 28, 7, 188, 13, 76, 4, 10, 8, 12, 10, 12, 10, 8, 23, 8,
9, 7, 6, 7, 8, 7, 6, 55, 8, 23, 24, 12, 11, 7, 9, 11, 12, 6, 7, 22, 5,
7, 24, 6, 11, 9, 6, 7, 22, 7, 11, 38, 7, 9, 8, 25, 11, 8, 11, 9, 12,
8, 12, 5, 38, 5, 38, 5, 11, 7, 5, 6, 21, 6, 10, 53, 8, 7, 24, 10, 27,
44, 253, 253, 253, 252, 252, 252, 13, 12, 45, 12, 45, 12, 61, 12, 45,
44, 173
];
litcode.Initialize(litlen);
// Repeated code lengths of length codes 0..15
byte[] lenlen =
[
2, 35, 36, 53, 38, 23
];
lencode.Initialize(lenlen);
// Repeated code lengths of distance codes 0..63
byte[] distlen =
[
2, 20, 53, 230, 247, 151, 248
];
distcode.Initialize(distlen);
}
/// <summary>
/// blast() decompresses the PKWare Data Compression Library (DCL) compressed
/// format. It provides the same functionality as the explode() function in
/// that library. (Note: PKWare overused the "implode" verb, and the format
/// used by their library implode() function is completely different and
/// incompatible with the implode compression method supported by PKZIP.)
///
/// The binary mode for stdio functions should be used to assure that the
/// compressed data is not corrupted when read or written. For example:
/// fopen(..., "rb") and fopen(..., "wb").
/// </summary>
public static int Blast(byte[] inhow, List<byte> outhow)
{
// Input/output state
var state = new State(inhow, outhow);
// Attempt to decompress using the above state
int err;
try
{
err = Decomp(state);
}
catch (IndexOutOfRangeException)
{
// This was originally a jump, which is bad form for C#
err = 2;
}
// Write any leftover output and update the error code if needed
if (err != 1 && state.Next != 0 && !state.ProcessOutput() && err == 0)
err = 1;
return err;
}
/// <summary>
/// Decode PKWare Compression Library stream.
/// </summary>
/// <remarks>
/// First byte is 0 if literals are uncoded or 1 if they are coded. Second
/// byte is 4, 5, or 6 for the number of extra bits in the distance code.
/// This is the base-2 logarithm of the dictionary size minus six.
///
/// Compressed data is a combination of literals and length/distance pairs
/// terminated by an end code. Literals are either Huffman coded or
/// uncoded bytes. A length/distance pair is a coded length followed by a
/// coded distance to represent a string that occurs earlier in the
/// uncompressed data that occurs again at the current location.
///
/// A bit preceding a literal or length/distance pair indicates which comes
/// next, 0 for literals, 1 for length/distance.
///
/// If literals are uncoded, then the next eight bits are the literal, in the
/// normal bit order in the stream, i.e. no bit-reversal is needed. Similarly,
/// no bit reversal is needed for either the length extra bits or the distance
/// extra bits.
///
/// Literal bytes are simply written to the output. A length/distance pair is
/// an instruction to copy previously uncompressed bytes to the output. The
/// copy is from distance bytes back in the output stream, copying for length
/// bytes.
///
/// Distances pointing before the beginning of the output data are not
/// permitted.
///
/// Overlapped copies, where the length is greater than the distance, are
/// allowed and common. For example, a distance of one and a length of 518
/// simply copies the last byte 518 times. A distance of four and a length of
/// twelve copies the last four bytes three times. A simple forward copy
/// ignoring whether the length is greater than the distance or not implements
/// this correctly.
/// </remarks>
private static int Decomp(State state)
{
int symbol; // decoded symbol, extra bits for distance
int len; // length for copy
uint dist; // distance for copy
int copy; // copy counter
int from, to; // copy pointers
// Read header
int lit = state.Bits(8); // true if literals are coded
if (lit > 1)
return -1;
int dict = state.Bits(8); // log2(dictionary size) - 6
if (dict < 4 || dict > 6)
return -2;
// Decode literals and length/distance pairs
do
{
if (state.Bits(1) != 0)
{
// Get length
symbol = lencode.Decode(state);
len = baseLength[symbol] + state.Bits(extra[symbol]);
if (len == 519)
break; // end code
// Get distance
symbol = len == 2 ? 2 : dict;
dist = (uint)(distcode.Decode(state) << symbol);
dist += (uint)state.Bits(symbol);
dist++;
if (state.First && dist > state.Next)
return -3; //distance too far back
// Copy length bytes from distance bytes back
do
{
to = (int)(state.OutputPtr + state.Next);
from = (int)(to - dist);
copy = MAXWIN;
if (state.Next < dist)
{
from += copy;
copy = (int)dist;
}
copy -= (int)state.Next;
if (copy > len)
copy = len;
len -= copy;
state.Next += (uint)copy;
do
{
state.Output[to++] = state.Output[from++];
}
while (--copy != 0);
if (state.Next == MAXWIN)
{
if (!state.ProcessOutput())
return 1;
state.Next = 0;
state.First = false;
}
}
while (len != 0);
}
else
{
// Get literal and write it
symbol = lit != 0 ? litcode.Decode(state) : state.Bits(8);
state.Output[state.Next++] = (byte)symbol;
if (state.Next == MAXWIN)
{
if (!state.ProcessOutput())
return 1;
state.Next = 0;
state.First = false;
}
}
}
while (true);
return 0;
}
}
}

View File

@@ -0,0 +1,15 @@
namespace SabreTools.Compression.Blast
{
public static class Constants
{
/// <summary>
/// Maximum code length
/// </summary>
public const int MAXBITS = 13;
/// <summary>
/// Maximum window size
/// </summary>
public const int MAXWIN = 4096;
}
}

View File

@@ -0,0 +1,207 @@
using System;
using static SabreTools.Compression.Blast.Constants;
namespace SabreTools.Compression.Blast
{
/// <summary>
/// Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of
/// each length, which for a canonical code are stepped through in order.
/// symbol[] are the symbol values in canonical order, where the number of
/// entries is the sum of the counts in count[]. The decoding process can be
/// seen in the function decode() below.
/// </summary>
public class Huffman
{
/// <summary>
/// Number of symbols of each length
/// </summary>
public short[] Count { get; set; }
/// <summary>
/// Pointer to number of symbols of each length
/// </summary>
public int CountPtr { get; set; }
/// <summary>
/// Canonically ordered symbols
/// </summary>
public short[] Symbol { get; set; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="countLength">Length of the Count array</param>
/// <param name="symbolLength">Length of the Symbol array</param>
public Huffman(int countLength, int symbolLength)
{
Count = new short[countLength];
Symbol = new short[symbolLength];
}
/// <summary>
/// Given a list of repeated code lengths rep[0..n-1], where each byte is a
/// count (high four bits + 1) and a code length (low four bits), generate the
/// list of code lengths. This compaction reduces the size of the object code.
/// Then given the list of code lengths length[0..n-1] representing a canonical
/// Huffman code for n symbols, construct the tables required to decode those
/// codes. Those tables are the number of codes of each length, and the symbols
/// sorted by length, retaining their original order within each length. The
/// return value is zero for a complete code set, negative for an over-
/// subscribed code set, and positive for an incomplete code set. The tables
/// can be used if the return value is zero or positive, but they cannot be used
/// if the return value is negative. If the return value is zero, it is not
/// possible for decode() using that table to return an error--any stream of
/// enough bits will resolve to a symbol. If the return value is positive, then
/// it is possible for decode() using that table to return an error for received
/// codes past the end of the incomplete lengths.
/// </summary>
/// <param name="rep">Repeated code length array</param>
public int Initialize(byte[] rep)
{
int n = rep.Length; // Length of the bit length array
short symbol = 0; // Current symbol when stepping through length[]
short len; // Current length when stepping through h.Count[]
int left; // Number of possible codes left of current length
short[] offs = new short[MAXBITS + 1]; // offsets in symbol table for each length
short[] length = new short[256]; // Code lengths
// Convert compact repeat counts into symbol bit length list
int repPtr = 0;
do
{
len = rep[repPtr++];
left = (len >> 4) + 1;
len &= 15;
do
{
length[symbol++] = len;
}
while (--left != 0);
}
while (--n != 0);
n = symbol;
// Count number of codes of each length
for (len = 0; len <= MAXBITS; len++)
{
Count[len] = 0;
}
// Assumes lengths are within bounds
for (symbol = 0; symbol < n; symbol++)
{
(Count[length[symbol]])++;
}
// No codes! Complete, but decode() will fail
if (Count[0] == n)
return 0;
// Check for an over-subscribed or incomplete set of lengths
left = 1; // One possible code of zero length
for (len = 1; len <= MAXBITS; len++)
{
left <<= 1; // One more bit, double codes left
left -= Count[len]; // Deduct count from possible codes
if (left < 0)
return left; // over-subscribed--return negative
}
// Generate offsets into symbol table for each length for sorting
offs[1] = 0;
for (len = 1; len < MAXBITS; len++)
{
offs[len + 1] = (short)(offs[len] + Count[len]);
}
// Put symbols in table sorted by length, by symbol order within each length
for (symbol = 0; symbol < n; symbol++)
{
if (length[symbol] != 0)
Symbol[offs[length[symbol]]++] = symbol;
}
// Return zero for complete set, positive for incomplete set
return left;
}
/// <summary>
/// Decode a code from the stream s using huffman table h. Return the symbol or
/// a negative value if there is an error. If all of the lengths are zero, i.e.
/// an empty code, or if the code is incomplete and an invalid code is received,
/// then -9 is returned after reading MAXBITS bits.
/// </summary>
/// <param name="state">Current input/output state to process</param>
/// <remarks>
/// The codes as stored in the compressed data are bit-reversed relative to
/// a simple integer ordering of codes of the same lengths. Hence below the
/// bits are pulled from the compressed data one at a time and used to
/// build the code value reversed from what is in the stream in order to
/// permit simple integer comparisons for decoding.
///
/// The first code for the shortest length is all ones. Subsequent codes of
/// the same length are simply integer decrements of the previous code. When
/// moving up a length, a one bit is appended to the code. For a complete
/// code, the last code of the longest length will be all zeros. To support
/// this ordering, the bits pulled during decoding are inverted to apply the
/// more "natural" ordering starting with all zeros and incrementing.
/// </remarks>
public int Decode(State state)
{
int len = 1; // Current number of bits in code
int code = 0; // len bits being decoded
int first = 0; // First code of length len
int count; // Number of codes of length len
int index = 0; // Index of first code of length len in symbol table
int bitbuf = state.BitBuf; // Bits from stream
int left = state.BitCnt; // Bits left in next or left to process
int nextPtr = CountPtr + 1; // Next number of codes
while (true)
{
while (left-- != 0)
{
// Invert code
code |= (bitbuf & 1) ^ 1;
bitbuf >>= 1;
count = Count[nextPtr++];
// If length len, return symbol
if (code < first + count)
{
state.BitBuf = bitbuf;
state.BitCnt = (state.BitCnt - len) & 7;
return Symbol[index + (code - first)];
}
// Else update for next length
index += count;
first += count;
first <<= 1;
code <<= 1;
len++;
}
left = (MAXBITS + 1) - len;
if (left == 0)
break;
if (state.Left == 0)
{
state.Left = state.ProcessInput();
if (state.Left == 0)
throw new IndexOutOfRangeException();
}
bitbuf = state.Input[state.InputPtr++];
state.Left--;
if (left > 8)
left = 8;
}
// Ran out of codes
return -9;
}
};
}

View File

@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static SabreTools.Compression.Blast.Constants;
namespace SabreTools.Compression.Blast
{
/// <summary>
/// Input and output state
/// </summary>
public class State
{
#region Input State
/// <summary>
/// Opaque information passed to InputFunction()
/// </summary>
public byte[] InHow { get; set; }
/// <summary>
/// Next input location
/// </summary>
public List<byte> Input { get; set; }
/// <summary>
/// Pointer to the next input location
/// </summary>
public int InputPtr { get; set; }
/// <summary>
/// Available input at in
/// </summary>
public uint Left { get; set; }
/// <summary>
/// Bit buffer
/// </summary>
public int BitBuf { get; set; }
/// <summary>
/// Number of bits in bit buffer
/// </summary>
public int BitCnt { get; set; }
#endregion
#region Output State
/// <summary>
/// Opaque information passed to OutputFunction()
/// </summary>
public List<byte> OutHow { get; set; }
/// <summary>
/// Index of next write location in out[]
/// </summary>
public uint Next { get; set; }
/// <summary>
/// True to check distances (for first 4K)
/// </summary>
public bool First { get; set; }
/// <summary>
/// Output buffer and sliding window
/// </summary>
public byte[] Output { get; set; } = new byte[MAXWIN];
/// <summary>
/// Pointer to the next output location
/// </summary>
public int OutputPtr { get; set; }
#endregion
/// <summary>
/// Constructor
/// </summary>
/// <param name="inhow">Input byte array</param>
/// <param name="outhow">Output byte list</param>
public State(byte[] inhow, List<byte> outhow)
{
InHow = inhow;
Input = new List<byte>();
InputPtr = 0;
Left = 0;
BitBuf = 0;
BitCnt = 0;
OutHow = outhow;
Next = 0;
First = true;
}
/// <summary>
/// Return need bits from the input stream. This always leaves less than
/// eight bits in the buffer. bits() works properly for need == 0.
/// </summary>
/// <param name="need">Number of bits to read</param>
/// <remarks>
/// Bits are stored in bytes from the least significant bit to the most
/// significant bit. Therefore bits are dropped from the bottom of the bit
/// buffer, using shift right, and new bytes are appended to the top of the
/// bit buffer, using shift left.
/// </remarks>
public int Bits(int need)
{
// Load at least need bits into val
int val = BitBuf;
while (BitCnt < need)
{
if (Left == 0)
{
Left = ProcessInput();
if (Left == 0)
throw new IndexOutOfRangeException();
}
// Load eight bits
val |= (int)(Input[InputPtr++]) << BitCnt;
Left--;
BitCnt += 8;
}
// Drop need bits and update buffer, always zero to seven bits left
BitBuf = val >> need;
BitCnt -= need;
// Return need bits, zeroing the bits above that
return val & ((1 << need) - 1);
}
/// <summary>
/// Process input for the current state
/// </summary>
/// <returns>Amount of data in Input</returns>
public uint ProcessInput()
{
Input = new List<byte>(InHow);
return (uint)Input.Count;
}
/// <summary>
/// Process output for the current state
/// </summary>
/// <returns>True if the output could be added, false otherwise</returns>
public bool ProcessOutput()
{
try
{
OutHow.AddRange(Output.Take((int)Next));
return true;
}
catch
{
return false;
}
}
}
}

View File

@@ -1,7 +1,7 @@
using System.IO;
using System.Linq;
using System.Text;
using SabreTools.IO;
using SabreTools.IO.Extensions;
using SabreTools.Models.Compression.LZ;
using static SabreTools.Models.Compression.LZ.Constants;
@@ -552,6 +552,6 @@ namespace SabreTools.Compression.LZ
return fileHeader;
}
#endregion
#endregion
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SabreTools.IO.Streams;
using SabreTools.Models.Compression.MSZIP;
using static SabreTools.Models.Compression.MSZIP.Constants;
@@ -13,7 +14,7 @@ namespace SabreTools.Compression.MSZIP
/// <summary>
/// Internal bitstream to use for decompression
/// </summary>
private readonly BitStream _bitStream;
private readonly ReadOnlyBitStream _bitStream;
/// <summary>
/// Create a new Decompressor from a byte array
@@ -28,8 +29,8 @@ namespace SabreTools.Compression.MSZIP
// Create a memory stream to wrap
var ms = new MemoryStream(input);
// Wrap the stream in a BitStream
_bitStream = new BitStream(ms);
// Wrap the stream in a ReadOnlyBitStream
_bitStream = new ReadOnlyBitStream(ms);
}
/// <summary>
@@ -42,8 +43,8 @@ namespace SabreTools.Compression.MSZIP
if (input == null || !input.CanRead || !input.CanSeek)
throw new ArgumentException(nameof(input));
// Wrap the stream in a BitStream
_bitStream = new BitStream(input);
// Wrap the stream in a ReadOnlyBitStream
_bitStream = new ReadOnlyBitStream(input);
}
/// <summary>
@@ -120,22 +121,24 @@ namespace SabreTools.Compression.MSZIP
/// <summary>
/// Read a FixedHuffmanCompressedBlockHeader from the input stream
/// </summary>
private (FixedCompressedDataHeader, uint, uint) RaadFixedCompressedDataHeader()
private FixedCompressedDataHeader ReadFixedCompressedDataHeader(out uint numLiteral, out uint numDistance)
{
// Nothing needs to be read, all values are fixed
return (new FixedCompressedDataHeader(), 288, 30);
numLiteral = 288;
numDistance = 30;
return new FixedCompressedDataHeader();
}
/// <summary>
/// Read a DynamicHuffmanCompressedBlockHeader from the input stream
/// </summary>
private (DynamicCompressedDataHeader, uint, uint) ReadDynamicCompressedDataHeader()
private DynamicCompressedDataHeader ReadDynamicCompressedDataHeader(out uint numLiteral, out uint numDistance)
{
var header = new DynamicCompressedDataHeader();
// Setup the counts first
uint numLiteral = 257 + _bitStream.ReadBitsLSB(5) ?? 0;
uint numDistance = 1 + _bitStream.ReadBitsLSB(5) ?? 0;
numLiteral = 257 + _bitStream.ReadBitsLSB(5) ?? 0;
numDistance = 1 + _bitStream.ReadBitsLSB(5) ?? 0;
uint numLength = 4 + _bitStream.ReadBitsLSB(4) ?? 0;
// Convert the alphabet based on lengths
@@ -150,7 +153,7 @@ namespace SabreTools.Compression.MSZIP
}
// Make the lengths tree
HuffmanDecoder lengthTree = new HuffmanDecoder(lengthLengths, 19);
var lengthTree = new HuffmanDecoder(lengthLengths, 19);
// Setup the literal and distance lengths
header.LiteralLengths = new uint[288];
@@ -161,7 +164,7 @@ namespace SabreTools.Compression.MSZIP
uint leftover = ReadHuffmanLengths(lengthTree, header.LiteralLengths, numLiteral, 0, ref repeatCode);
_ = ReadHuffmanLengths(lengthTree, header.DistanceCodes, numDistance, leftover, ref repeatCode);
return (header, numLiteral, numDistance);
return header;
}
#endregion
@@ -181,7 +184,7 @@ namespace SabreTools.Compression.MSZIP
{
// If stored with no compression
case CompressionType.NoCompression:
(var header00, var bytes00) = ReadNoCompression();
var header00 = ReadNoCompression(out byte[]? bytes00);
if (header00 == null || bytes00 == null)
return null;
@@ -191,7 +194,7 @@ namespace SabreTools.Compression.MSZIP
// If compressed with fixed Huffman codes
case CompressionType.FixedHuffman:
(var header01, var bytes01) = ReadFixedHuffman();
var header01 = ReadFixedHuffman(out byte[]? bytes01);
if (header01 == null || bytes01 == null)
return null;
@@ -201,7 +204,7 @@ namespace SabreTools.Compression.MSZIP
// If compressed with dynamic Huffman codes
case CompressionType.DynamicHuffman:
(var header10, var bytes10) = ReadDynamicHuffman();
var header10 = ReadDynamicHuffman(out byte[]? bytes10);
if (header10 == null || bytes10 == null)
return null;
@@ -221,7 +224,7 @@ namespace SabreTools.Compression.MSZIP
/// <summary>
/// Read an RFC1951 block with no compression
/// </summary>
private (NonCompressedBlockHeader?, byte[]?) ReadNoCompression()
private NonCompressedBlockHeader? ReadNoCompression(out byte[]? data)
{
// Skip any remaining bits in current partially processed byte
_bitStream.Discard();
@@ -229,44 +232,50 @@ namespace SabreTools.Compression.MSZIP
// Read LEN and NLEN
var header = ReadNonCompressedBlockHeader();
if (header.LEN == 0 && header.NLEN == 0)
return (null, null);
{
data = null;
return null;
}
// Copy LEN bytes of data to output
return (header, _bitStream.ReadBytes(header.LEN));
data = _bitStream.ReadBytes(header.LEN);
return header;
}
/// <summary>
/// Read an RFC1951 block with fixed Huffman compression
/// </summary>
private (FixedCompressedDataHeader, byte[]?) ReadFixedHuffman()
private FixedCompressedDataHeader? ReadFixedHuffman(out byte[]? data)
{
var bytes = new List<byte>();
// Get the fixed huffman header
(var header, uint numLiteral, uint numDistance) = RaadFixedCompressedDataHeader();
var header = ReadFixedCompressedDataHeader(out uint numLiteral, out uint numDistance);
// Make the literal and distance trees
HuffmanDecoder literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
HuffmanDecoder distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
var literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
var distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
// Now loop and decode
return (header, ReadHuffmanBlock(literalTree, distanceTree));
data = ReadHuffmanBlock(literalTree, distanceTree);
return header;
}
/// <summary>
/// Read an RFC1951 block with dynamic Huffman compression
/// </summary>
private (DynamicCompressedDataHeader?, byte[]?) ReadDynamicHuffman()
private DynamicCompressedDataHeader? ReadDynamicHuffman(out byte[]? data)
{
// Get the dynamic huffman header
(var header, uint numLiteral, uint numDistance) = ReadDynamicCompressedDataHeader();
var header = ReadDynamicCompressedDataHeader(out uint numLiteral, out uint numDistance);
// Make the literal and distance trees
HuffmanDecoder literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
HuffmanDecoder distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
var literalTree = new HuffmanDecoder(header.LiteralLengths, numLiteral);
var distanceTree = new HuffmanDecoder(header.DistanceCodes, numDistance);
// Now loop and decode
return (header, ReadHuffmanBlock(literalTree, distanceTree));
data = ReadHuffmanBlock(literalTree, distanceTree);
return header;
}
/// <summary>

View File

@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
using SabreTools.IO.Streams;
namespace SabreTools.Compression.MSZIP
{
@@ -83,7 +84,7 @@ namespace SabreTools.Compression.MSZIP
/// </summary>
/// <param name="input">BitStream representing the input</param>
/// <returns>Value of the node described by the input</returns>
public int Decode(BitStream input)
public int Decode(ReadOnlyBitStream input)
{
// Start at the root of the tree
var node = _root;

View File

@@ -4,48 +4,48 @@ namespace SabreTools.Compression.Quantum
/// TODO: Remove this class when Models gets updated
public static class Constants
{
public static readonly int[] PositionSlot = new int[]
{
public static readonly int[] PositionSlot =
[
0x00000, 0x00001, 0x00002, 0x00003, 0x00004, 0x00006, 0x00008, 0x0000c,
0x00010, 0x00018, 0x00020, 0x00030, 0x00040, 0x00060, 0x00080, 0x000c0,
0x00100, 0x00180, 0x00200, 0x00300, 0x00400, 0x00600, 0x00800, 0x00c00,
0x01000, 0x01800, 0x02000, 0x03000, 0x04000, 0x06000, 0x08000, 0x0c000,
0x10000, 0x18000, 0x20000, 0x30000, 0x40000, 0x60000, 0x80000, 0xc0000,
0x100000, 0x180000
};
];
public static readonly int[] PositionExtraBits = new int[]
{
public static readonly int[] PositionExtraBits =
[
0, 0, 0, 0, 1, 1, 2, 2,
3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10,
11, 11, 12, 12, 13, 13, 14, 14,
15, 15, 16, 16, 17, 17, 18, 18,
19, 19
};
];
public static readonly int[] LengthSlot = new int[]
{
public static readonly int[] LengthSlot =
[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x08,
0x0a, 0x0c, 0x0e, 0x12, 0x16, 0x1a, 0x1e, 0x26,
0x2e, 0x36, 0x3e, 0x4e, 0x5e, 0x6e, 0x7e, 0x9e,
0xbe, 0xde, 0xfe
};
];
public static readonly int[] LengthExtraBits = new int[]
{
public static readonly int[] LengthExtraBits =
[
0, 0, 0, 0, 0, 0, 1, 1,
1, 1, 2, 2, 2, 2, 3, 3,
3, 3, 4, 4, 4, 4, 5, 5,
5, 5, 0
};
];
/// <summary>
/// Number of position slots for (tsize - 10)
/// </summary>
public static readonly int[] NumPositionSlots = new int[]
{
public static readonly int[] NumPositionSlots =
[
20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42
};
];
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using SabreTools.IO.Streams;
using SabreTools.Models.Compression.Quantum;
using static SabreTools.Compression.Quantum.Constants;
@@ -12,7 +13,7 @@ namespace SabreTools.Compression.Quantum
/// <summary>
/// Internal bitstream to use for decompression
/// </summary>
private BitStream _bitStream;
private readonly ReadOnlyBitStream _bitStream;
#region Models
@@ -100,8 +101,8 @@ namespace SabreTools.Compression.Quantum
// Create a memory stream to wrap
var ms = new MemoryStream(input);
// Wrap the stream in a BitStream
_bitStream = new BitStream(ms);
// Wrap the stream in a ReadOnlyBitStream
_bitStream = new ReadOnlyBitStream(ms);
// Initialize literal models
this._model0 = CreateModel(0, 64);
@@ -140,8 +141,8 @@ namespace SabreTools.Compression.Quantum
if (windowBits < 10 || windowBits > 21)
throw new ArgumentOutOfRangeException(nameof(windowBits));
// Wrap the stream in a BitStream
_bitStream = new BitStream(input);
// Wrap the stream in a ReadOnlyBitStream
_bitStream = new ReadOnlyBitStream(input);
// Initialize literal models
this._model0 = CreateModel(0, 64);
@@ -217,7 +218,7 @@ namespace SabreTools.Compression.Quantum
offset = PositionSlot[model4sym] + model4extra + 1;
length = 3;
break;
case 5:
int model5sym = GetSymbol(_model5);
int model5extra = (int)(_bitStream.ReadBitsMSB(PositionExtraBits[model5sym]) ?? 0);

View File

@@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Assembly Properties -->
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Version>0.6.0</Version>
<!-- Package Properties -->
<Authors>Matt Nadareski</Authors>
<Description>Clean compression implementations</Description>
<Copyright>Copyright (c) Matt Nadareski 2022-2024</Copyright>
<PackageProjectUrl>https://github.com/SabreTools/</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/SabreTools/SabreTools.Compression</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>compression decompression lz mszip zlib blast</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<None Include="../README.md" Pack="true" PackagePath="" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SabreTools.IO" Version="1.5.0" />
<PackageReference Include="SabreTools.Models" Version="1.5.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,47 @@
namespace SabreTools.Compression.zlib
{
public static class zlibConst
{
public const int Z_NO_FLUSH = 0;
public const int Z_PARTIAL_FLUSH = 1;
public const int Z_SYNC_FLUSH = 2;
public const int Z_FULL_FLUSH = 3;
public const int Z_FINISH = 4;
public const int Z_BLOCK = 5;
public const int Z_TREES = 6;
public const int Z_OK = 0;
public const int Z_STREAM_END = 1;
public const int Z_NEED_DICT = 2;
public const int Z_ERRNO = (-1);
public const int Z_STREAM_ERROR = (-2);
public const int Z_DATA_ERROR = (-3);
public const int Z_MEM_ERROR = (-4);
public const int Z_BUF_ERROR = (-5);
public const int Z_VERSION_ERROR = (-6);
/// <summary>
/// Get the zlib result name from an integer
/// </summary>
/// <param name="result">Integer to translate to the result name</param>
/// <returns>Name of the result, the integer as a string otherwise</returns>
public static string ToZlibConstName(this int result)
{
return result switch
{
Z_OK => "Z_OK",
Z_STREAM_END => "Z_STREAM_END",
Z_NEED_DICT => "Z_NEED_DICT",
Z_ERRNO => "Z_ERRNO",
Z_STREAM_ERROR => "Z_STREAM_ERROR",
Z_DATA_ERROR => "Z_DATA_ERROR",
Z_MEM_ERROR => "Z_MEM_ERROR",
Z_BUF_ERROR => "Z_BUF_ERROR",
Z_VERSION_ERROR => "Z_VERSION_ERROR",
_ => result.ToString(),
};
}
}
}

12
Test/Program.cs Normal file
View File

@@ -0,0 +1,12 @@
using SabreTools.Compression;
namespace Test
{
public static class Program
{
public static void Main(string[] args)
{
// No implementation, used for experimentation
}
}
}

32
Test/Test.csproj Normal file
View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net20;net35;net40;net452;net462;net472;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Support All Frameworks -->
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net2`)) OR $(TargetFramework.StartsWith(`net3`)) OR $(TargetFramework.StartsWith(`net4`))">
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`netcoreapp`)) OR $(TargetFramework.StartsWith(`net5`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith(`net6`)) OR $(TargetFramework.StartsWith(`net7`)) OR $(TargetFramework.StartsWith(`net8`)) OR $(TargetFramework.StartsWith(`net9`))">
<RuntimeIdentifiers>win-x86;win-x64;win-arm64;linux-x64;linux-arm64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith(`osx-arm`))">
<TargetFrameworks>net6.0;net7.0;net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SabreTools.Compression\SabreTools.Compression.csproj" />
</ItemGroup>
</Project>

36
publish-nix.sh Executable file
View File

@@ -0,0 +1,36 @@
#! /bin/bash
# This batch file assumes the following:
# - .NET 9.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
NO_BUILD=false
while getopts "b" OPTION
do
case $OPTION in
b)
NO_BUILD=true
;;
*)
echo "Invalid option provided"
exit 1
;;
esac
done
# Set the current directory as a variable
BUILD_FOLDER=$PWD
# Only build if requested
if [ $NO_BUILD = false ]
then
# Restore Nuget packages for all builds
echo "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.Compression/SabreTools.Compression.csproj --output $BUILD_FOLDER
fi

26
publish-win.ps1 Normal file
View File

@@ -0,0 +1,26 @@
# This batch file assumes the following:
# - .NET 9.0 (or newer) SDK is installed and in PATH
#
# If any of these are not satisfied, the operation may fail
# in an unpredictable way and result in an incomplete output.
# Optional parameters
param(
[Parameter(Mandatory = $false)]
[Alias("NoBuild")]
[switch]$NO_BUILD
)
# Set the current directory as a variable
$BUILD_FOLDER = $PSScriptRoot
# Only build if requested
if (!$NO_BUILD.IsPresent)
{
# Restore Nuget packages for all builds
Write-Host "Restoring Nuget packages"
dotnet restore
# Create Nuget Package
dotnet pack SabreTools.Compression\SabreTools.Compression.csproj --output $BUILD_FOLDER
}