Files
SabreTools.Compression/libmspack/SZDD/Decompressor.cs
Matt Nadareski 82223f3ee4 Checkpoint (nw)
2023-09-20 14:26:09 -04:00

231 lines
8.3 KiB
C#

using static SabreTools.Compression.libmspack.macros;
using static SabreTools.Compression.libmspack.SZDD.Constants;
namespace SabreTools.Compression.libmspack.SZDD
{
/// <summary>
/// A decompressor for SZDD compressed files.
///
/// All fields are READ ONLY.
/// </summary>
/// <see cref="mspack.mspack_create_szdd_decompressor()"/>
/// <see cref="mspack.mspack_destroy_szdd_decompressor(Decompressor)"/>
public unsafe class Decompressor : BaseDecompressor
{
/// <summary>
/// Creates a new SZDD decompressor
/// </summary>
public Decompressor()
{
this.system = new mspack_default_system();
this.error = MSPACK_ERR.MSPACK_ERR_OK;
}
/// <summary>
/// Destroys an existing SZDD decompressor
/// </summary>
~Decompressor()
{
mspack_system sys = this.system;
//sys.free(self);
}
/// <summary>
/// Opens a SZDD file and reads the header.
///
/// If the file opened is a valid SZDD file, all headers will be read and
/// a msszddd_header structure will be returned.
///
/// In the case of an error occuring, null is returned and the error code
/// is available from last_error().
///
/// The filename pointer should be considered "in use" until close() is
/// called on the SZDD file.
/// </summary>
/// <param name="filename">
/// The filename of the SZDD compressed file. This is
/// passed directly to mspack_system::open().
/// </param>
/// <returns>A pointer to a msszddd_header structure, or null on failure</returns>
/// <see cref="close(msszddd_header)"/>
public msszddd_header open(in string filename)
{
mspack_system sys = this.system;
mspack_file fh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_READ);
msszddd_header hdr = new msszddd_header();
hdr.fh = fh;
this.error = ReadHeaders(sys, fh, hdr);
if (this.error != MSPACK_ERR.MSPACK_ERR_OK)
{
if (fh != null) sys.close(fh);
//sys.free(hdr);
hdr = null;
}
return hdr;
}
/// <summary>
/// Closes a previously opened SZDD file.
///
/// This closes a SZDD file and frees the msszddd_header associated with
/// it.
///
/// The SZDD header pointer is now invalid and cannot be used again.
/// </summary>
/// <param name="szdd">The SZDD file to close</param>
public void close(msszddd_header szdd)
{
if (this.system == null) return;
// Close the file handle associated
this.system.close(szdd.fh);
// Free the memory associated
//this.system.free(hdr);
this.error = MSPACK_ERR.MSPACK_ERR_OK;
}
private static readonly byte[] szdd_signature_expand = new byte[8]
{
0x53, 0x5A, 0x44, 0x44, 0x88, 0xF0, 0x27, 0x33
};
private static readonly byte[] szdd_signature_qbasic = new byte[8]
{
0x53, 0x5A, 0x20, 0x88, 0xF0, 0x27, 0x33, 0xD1
};
/// <summary>
/// Reads the headers of an SZDD format file
/// </summary>
/// <param name="sys"></param>
/// <param name="fh"></param>
/// <param name="hdr"></param>
/// <returns></returns>
private static MSPACK_ERR ReadHeaders(mspack_system sys, mspack_file fh, msszddd_header hdr)
{
FixedArray<byte> buf = new FixedArray<byte>(8);
// Read and check signature
if (sys.read(fh, buf, 8) != 8) return MSPACK_ERR.MSPACK_ERR_READ;
if (buf.SequenceEqual(szdd_signature_expand))
{
// Common SZDD
hdr.format = MSSZDD_FMT.MSSZDD_FMT_NORMAL;
// Read the rest of the header
if (sys.read(fh, buf, 6) != 6) return MSPACK_ERR.MSPACK_ERR_READ;
if (buf[0] != 0x41) return MSPACK_ERR.MSPACK_ERR_DATAFORMAT;
hdr.missing_char = (char)buf[1];
hdr.length = EndGetI32(buf, 2);
}
else if (buf.SequenceEqual(szdd_signature_qbasic))
{
// Special QBasic SZDD
hdr.format = MSSZDD_FMT.MSSZDD_FMT_QBASIC;
if (sys.read(fh, buf, 4) != 4) return MSPACK_ERR.MSPACK_ERR_READ;
hdr.missing_char = '\0';
hdr.length = EndGetI32(buf, 0);
}
else
{
return MSPACK_ERR.MSPACK_ERR_SIGNATURE;
}
return MSPACK_ERR.MSPACK_ERR_OK;
}
/// <summary>
/// Extracts the compressed data from a SZDD file.
///
/// This decompresses the compressed SZDD data stream and writes it to
/// an output file.
/// </summary>
/// <param name="szdd">The SZDD file to extract data from</param>
/// <param name="filename">
/// The filename to write the decompressed data to. This
/// is passed directly to mspack_system::open().
/// </param>
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
public MSPACK_ERR extract(msszddd_header szdd, in string filename)
{
if (szdd == null) return this.error = MSPACK_ERR.MSPACK_ERR_ARGS;
mspack_system sys = this.system;
mspack_file fh = szdd.fh;
// Seek to the compressed data
long data_offset = (szdd.format == MSSZDD_FMT.MSSZDD_FMT_NORMAL) ? 14 : 12;
if (sys.seek(fh, data_offset, MSPACK_SYS_SEEK.MSPACK_SYS_SEEK_START) != 0)
{
return this.error = MSPACK_ERR.MSPACK_ERR_SEEK;
}
// Open file for output
mspack_file outfh;
if ((outfh = sys.open(filename, MSPACK_SYS_OPEN.MSPACK_SYS_OPEN_WRITE)) == null)
{
return this.error = MSPACK_ERR.MSPACK_ERR_OPEN;
}
// Decompress the data
this.error = lzss_decompress(sys, fh, outfh, SZDD_INPUT_SIZE,
szdd.format == MSSZDD_FMT.MSSZDD_FMT_NORMAL
? LZSS_MODE.LZSS_MODE_EXPAND
: LZSS_MODE.LZSS_MODE_QBASIC);
// Close output file
sys.close(outfh);
return this.error;
}
/// <summary>
/// Decompresses an SZDD file to an output file in one step.
///
/// This opens an SZDD file as input, reads the header, then decompresses
/// the compressed data immediately to an output file, finally closing
/// both the input and output file. It is more convenient to use than
/// open() then extract() then close(), if you do not need to know the
/// SZDD output size or missing character.
/// </summary>
/// <param name="input">
/// The filename of the input SZDD file. This is passed
/// directly to mspack_system::open().
/// </param>
/// <param name="output">
/// The filename to write the decompressed data to. This
/// is passed directly to mspack_system::open().
/// </param>
/// <returns>An error code, or MSPACK_ERR_OK if successful</returns>
public MSPACK_ERR decompress(in string input, in string output)
{
msszddd_header hdr;
if ((hdr = open(input)) == null) return this.error;
MSPACK_ERR error = extract(hdr, output);
close(hdr);
return this.error = error;
}
/// <summary>
/// Returns the error code set by the most recently called method.
///
/// This is useful for open() which does not return an
/// error code directly.
/// </summary>
/// <returns>The most recent error code</returns>
/// <see cref="open(in string)"/>
/// <see cref="extract(msszddd_header, in string)"/>
/// <see cref="decompress(in string, in string)"/>
public MSPACK_ERR last_error()
{
return this.error;
}
}
}