Mercurial > repos > SharpZipLib
diff BZip2/BZip2InputStream.cs @ 1:94e25b786321
Re #311: can't read ZIP file packed by Linux app Archive Manager/File Roller
Initial commit of clean SharpZipLib 0860 source. Only change is build paths.
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Sat, 30 Oct 2010 14:03:17 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/BZip2/BZip2InputStream.cs Sat Oct 30 14:03:17 2010 +0000 @@ -0,0 +1,1003 @@ +// BZip2InputStream.cs +// +// Copyright (C) 2001 Mike Krueger +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +using System; +using System.IO; + +using ICSharpCode.SharpZipLib.Checksums; + +namespace ICSharpCode.SharpZipLib.BZip2 +{ + + /// <summary> + /// An input stream that decompresses files in the BZip2 format + /// </summary> + public class BZip2InputStream : Stream + { + #region Constants + const int START_BLOCK_STATE = 1; + const int RAND_PART_A_STATE = 2; + const int RAND_PART_B_STATE = 3; + const int RAND_PART_C_STATE = 4; + const int NO_RAND_PART_A_STATE = 5; + const int NO_RAND_PART_B_STATE = 6; + const int NO_RAND_PART_C_STATE = 7; + #endregion + #region Constructors + /// <summary> + /// Construct instance for reading from stream + /// </summary> + /// <param name="stream">Data source</param> + public BZip2InputStream(Stream stream) + { + // init arrays + for (int i = 0; i < BZip2Constants.GroupCount; ++i) + { + limit[i] = new int[BZip2Constants.MaximumAlphaSize]; + baseArray[i] = new int[BZip2Constants.MaximumAlphaSize]; + perm[i] = new int[BZip2Constants.MaximumAlphaSize]; + } + + BsSetStream(stream); + Initialize(); + InitBlock(); + SetupBlock(); + } + + #endregion + + /// <summary> + /// Get/set flag indicating ownership of underlying stream. + /// When the flag is true <see cref="Close"></see> will close the underlying stream also. + /// </summary> + public bool IsStreamOwner + { + get { return isStreamOwner; } + set { isStreamOwner = value; } + } + + + #region Stream Overrides + /// <summary> + /// Gets a value indicating if the stream supports reading + /// </summary> + public override bool CanRead + { + get { + return baseStream.CanRead; + } + } + + /// <summary> + /// Gets a value indicating whether the current stream supports seeking. + /// </summary> + public override bool CanSeek { + get { + return baseStream.CanSeek; + } + } + + /// <summary> + /// Gets a value indicating whether the current stream supports writing. + /// This property always returns false + /// </summary> + public override bool CanWrite { + get { + return false; + } + } + + /// <summary> + /// Gets the length in bytes of the stream. + /// </summary> + public override long Length { + get { + return baseStream.Length; + } + } + + /// <summary> + /// Gets or sets the streams position. + /// Setting the position is not supported and will throw a NotSupportException + /// </summary> + /// <exception cref="NotSupportedException">Any attempt to set the position</exception> + public override long Position { + get { + return baseStream.Position; + } + set { + throw new NotSupportedException("BZip2InputStream position cannot be set"); + } + } + + /// <summary> + /// Flushes the stream. + /// </summary> + public override void Flush() + { + if (baseStream != null) { + baseStream.Flush(); + } + } + + /// <summary> + /// Set the streams position. This operation is not supported and will throw a NotSupportedException + /// </summary> + /// <param name="offset">A byte offset relative to the <paramref name="origin"/> parameter.</param> + /// <param name="origin">A value of type <see cref="SeekOrigin"/> indicating the reference point used to obtain the new position.</param> + /// <returns>The new position of the stream.</returns> + /// <exception cref="NotSupportedException">Any access</exception> + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("BZip2InputStream Seek not supported"); + } + + /// <summary> + /// Sets the length of this stream to the given value. + /// This operation is not supported and will throw a NotSupportedExceptionortedException + /// </summary> + /// <param name="value">The new length for the stream.</param> + /// <exception cref="NotSupportedException">Any access</exception> + public override void SetLength(long value) + { + throw new NotSupportedException("BZip2InputStream SetLength not supported"); + } + + /// <summary> + /// Writes a block of bytes to this stream using data from a buffer. + /// This operation is not supported and will throw a NotSupportedException + /// </summary> + /// <param name="buffer">The buffer to source data from.</param> + /// <param name="offset">The offset to start obtaining data from.</param> + /// <param name="count">The number of bytes of data to write.</param> + /// <exception cref="NotSupportedException">Any access</exception> + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException("BZip2InputStream Write not supported"); + } + + /// <summary> + /// Writes a byte to the current position in the file stream. + /// This operation is not supported and will throw a NotSupportedException + /// </summary> + /// <param name="value">The value to write.</param> + /// <exception cref="NotSupportedException">Any access</exception> + public override void WriteByte(byte value) + { + throw new NotSupportedException("BZip2InputStream WriteByte not supported"); + } + + /// <summary> + /// Read a sequence of bytes and advances the read position by one byte. + /// </summary> + /// <param name="buffer">Array of bytes to store values in</param> + /// <param name="offset">Offset in array to begin storing data</param> + /// <param name="count">The maximum number of bytes to read</param> + /// <returns>The total number of bytes read into the buffer. This might be less + /// than the number of bytes requested if that number of bytes are not + /// currently available or zero if the end of the stream is reached. + /// </returns> + public override int Read(byte[] buffer, int offset, int count) + { + if ( buffer == null ) + { + throw new ArgumentNullException("buffer"); + } + + for (int i = 0; i < count; ++i) { + int rb = ReadByte(); + if (rb == -1) { + return i; + } + buffer[offset + i] = (byte)rb; + } + return count; + } + + /// <summary> + /// Closes the stream, releasing any associated resources. + /// </summary> + public override void Close() + { + if ( IsStreamOwner && (baseStream != null) ) { + baseStream.Close(); + } + } + /// <summary> + /// Read a byte from stream advancing position + /// </summary> + /// <returns>byte read or -1 on end of stream</returns> + public override int ReadByte() + { + if (streamEnd) + { + return -1; // ok + } + + int retChar = currentChar; + switch (currentState) + { + case RAND_PART_B_STATE: + SetupRandPartB(); + break; + case RAND_PART_C_STATE: + SetupRandPartC(); + break; + case NO_RAND_PART_B_STATE: + SetupNoRandPartB(); + break; + case NO_RAND_PART_C_STATE: + SetupNoRandPartC(); + break; + case START_BLOCK_STATE: + case NO_RAND_PART_A_STATE: + case RAND_PART_A_STATE: + break; + default: + break; + } + return retChar; + } + + #endregion + + void MakeMaps() + { + nInUse = 0; + for (int i = 0; i < 256; ++i) { + if (inUse[i]) { + seqToUnseq[nInUse] = (byte)i; + unseqToSeq[i] = (byte)nInUse; + nInUse++; + } + } + } + + void Initialize() + { + char magic1 = BsGetUChar(); + char magic2 = BsGetUChar(); + + char magic3 = BsGetUChar(); + char magic4 = BsGetUChar(); + + if (magic1 != 'B' || magic2 != 'Z' || magic3 != 'h' || magic4 < '1' || magic4 > '9') { + streamEnd = true; + return; + } + + SetDecompressStructureSizes(magic4 - '0'); + computedCombinedCRC = 0; + } + + void InitBlock() + { + char magic1 = BsGetUChar(); + char magic2 = BsGetUChar(); + char magic3 = BsGetUChar(); + char magic4 = BsGetUChar(); + char magic5 = BsGetUChar(); + char magic6 = BsGetUChar(); + + if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90) { + Complete(); + return; + } + + if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) { + BadBlockHeader(); + streamEnd = true; + return; + } + + storedBlockCRC = BsGetInt32(); + + blockRandomised = (BsR(1) == 1); + + GetAndMoveToFrontDecode(); + + mCrc.Reset(); + currentState = START_BLOCK_STATE; + } + + void EndBlock() + { + computedBlockCRC = (int)mCrc.Value; + + // -- A bad CRC is considered a fatal error. -- + if (storedBlockCRC != computedBlockCRC) { + CrcError(); + } + + // 1528150659 + computedCombinedCRC = ((computedCombinedCRC << 1) & 0xFFFFFFFF) | (computedCombinedCRC >> 31); + computedCombinedCRC = computedCombinedCRC ^ (uint)computedBlockCRC; + } + + void Complete() + { + storedCombinedCRC = BsGetInt32(); + if (storedCombinedCRC != (int)computedCombinedCRC) { + CrcError(); + } + + streamEnd = true; + } + + void BsSetStream(Stream stream) + { + baseStream = stream; + bsLive = 0; + bsBuff = 0; + } + + void FillBuffer() + { + int thech = 0; + + try { + thech = baseStream.ReadByte(); + } catch (Exception) { + CompressedStreamEOF(); + } + + if (thech == -1) { + CompressedStreamEOF(); + } + + bsBuff = (bsBuff << 8) | (thech & 0xFF); + bsLive += 8; + } + + int BsR(int n) + { + while (bsLive < n) { + FillBuffer(); + } + + int v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1); + bsLive -= n; + return v; + } + + char BsGetUChar() + { + return (char)BsR(8); + } + + int BsGetIntVS(int numBits) + { + return BsR(numBits); + } + + int BsGetInt32() + { + int result = BsR(8); + result = (result << 8) | BsR(8); + result = (result << 8) | BsR(8); + result = (result << 8) | BsR(8); + return result; + } + + void RecvDecodingTables() + { + char[][] len = new char[BZip2Constants.GroupCount][]; + for (int i = 0; i < BZip2Constants.GroupCount; ++i) { + len[i] = new char[BZip2Constants.MaximumAlphaSize]; + } + + bool[] inUse16 = new bool[16]; + + //--- Receive the mapping table --- + for (int i = 0; i < 16; i++) { + inUse16[i] = (BsR(1) == 1); + } + + for (int i = 0; i < 16; i++) { + if (inUse16[i]) { + for (int j = 0; j < 16; j++) { + inUse[i * 16 + j] = (BsR(1) == 1); + } + } else { + for (int j = 0; j < 16; j++) { + inUse[i * 16 + j] = false; + } + } + } + + MakeMaps(); + int alphaSize = nInUse + 2; + + //--- Now the selectors --- + int nGroups = BsR(3); + int nSelectors = BsR(15); + + for (int i = 0; i < nSelectors; i++) { + int j = 0; + while (BsR(1) == 1) { + j++; + } + selectorMtf[i] = (byte)j; + } + + //--- Undo the MTF values for the selectors. --- + byte[] pos = new byte[BZip2Constants.GroupCount]; + for (int v = 0; v < nGroups; v++) { + pos[v] = (byte)v; + } + + for (int i = 0; i < nSelectors; i++) { + int v = selectorMtf[i]; + byte tmp = pos[v]; + while (v > 0) { + pos[v] = pos[v - 1]; + v--; + } + pos[0] = tmp; + selector[i] = tmp; + } + + //--- Now the coding tables --- + for (int t = 0; t < nGroups; t++) { + int curr = BsR(5); + for (int i = 0; i < alphaSize; i++) { + while (BsR(1) == 1) { + if (BsR(1) == 0) { + curr++; + } else { + curr--; + } + } + len[t][i] = (char)curr; + } + } + + //--- Create the Huffman decoding tables --- + for (int t = 0; t < nGroups; t++) { + int minLen = 32; + int maxLen = 0; + for (int i = 0; i < alphaSize; i++) { + maxLen = Math.Max(maxLen, len[t][i]); + minLen = Math.Min(minLen, len[t][i]); + } + HbCreateDecodeTables(limit[t], baseArray[t], perm[t], len[t], minLen, maxLen, alphaSize); + minLens[t] = minLen; + } + } + + void GetAndMoveToFrontDecode() + { + byte[] yy = new byte[256]; + int nextSym; + + int limitLast = BZip2Constants.BaseBlockSize * blockSize100k; + origPtr = BsGetIntVS(24); + + RecvDecodingTables(); + int EOB = nInUse+1; + int groupNo = -1; + int groupPos = 0; + + /*-- + Setting up the unzftab entries here is not strictly + necessary, but it does save having to do it later + in a separate pass, and so saves a block's worth of + cache misses. + --*/ + for (int i = 0; i <= 255; i++) { + unzftab[i] = 0; + } + + for (int i = 0; i <= 255; i++) { + yy[i] = (byte)i; + } + + last = -1; + + if (groupPos == 0) { + groupNo++; + groupPos = BZip2Constants.GroupSize; + } + + groupPos--; + int zt = selector[groupNo]; + int zn = minLens[zt]; + int zvec = BsR(zn); + int zj; + + while (zvec > limit[zt][zn]) { + if (zn > 20) { // the longest code + throw new BZip2Exception("Bzip data error"); + } + zn++; + while (bsLive < 1) { + FillBuffer(); + } + zj = (bsBuff >> (bsLive-1)) & 1; + bsLive--; + zvec = (zvec << 1) | zj; + } + if (zvec - baseArray[zt][zn] < 0 || zvec - baseArray[zt][zn] >= BZip2Constants.MaximumAlphaSize) { + throw new BZip2Exception("Bzip data error"); + } + nextSym = perm[zt][zvec - baseArray[zt][zn]]; + + while (true) { + if (nextSym == EOB) { + break; + } + + if (nextSym == BZip2Constants.RunA || nextSym == BZip2Constants.RunB) { + int s = -1; + int n = 1; + do { + if (nextSym == BZip2Constants.RunA) { + s += (0 + 1) * n; + } else if (nextSym == BZip2Constants.RunB) { + s += (1 + 1) * n; + } + + n <<= 1; + + if (groupPos == 0) { + groupNo++; + groupPos = BZip2Constants.GroupSize; + } + + groupPos--; + + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + + while (zvec > limit[zt][zn]) { + zn++; + while (bsLive < 1) { + FillBuffer(); + } + zj = (bsBuff >> (bsLive - 1)) & 1; + bsLive--; + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - baseArray[zt][zn]]; + } while (nextSym == BZip2Constants.RunA || nextSym == BZip2Constants.RunB); + + s++; + byte ch = seqToUnseq[yy[0]]; + unzftab[ch] += s; + + while (s > 0) { + last++; + ll8[last] = ch; + s--; + } + + if (last >= limitLast) { + BlockOverrun(); + } + continue; + } else { + last++; + if (last >= limitLast) { + BlockOverrun(); + } + + byte tmp = yy[nextSym - 1]; + unzftab[seqToUnseq[tmp]]++; + ll8[last] = seqToUnseq[tmp]; + + for (int j = nextSym-1; j > 0; --j) { + yy[j] = yy[j - 1]; + } + yy[0] = tmp; + + if (groupPos == 0) { + groupNo++; + groupPos = BZip2Constants.GroupSize; + } + + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = BsR(zn); + while (zvec > limit[zt][zn]) { + zn++; + while (bsLive < 1) { + FillBuffer(); + } + zj = (bsBuff >> (bsLive-1)) & 1; + bsLive--; + zvec = (zvec << 1) | zj; + } + nextSym = perm[zt][zvec - baseArray[zt][zn]]; + continue; + } + } + } + + void SetupBlock() + { + int[] cftab = new int[257]; + + cftab[0] = 0; + Array.Copy(unzftab, 0, cftab, 1, 256); + + for (int i = 1; i <= 256; i++) { + cftab[i] += cftab[i - 1]; + } + + for (int i = 0; i <= last; i++) { + byte ch = ll8[i]; + tt[cftab[ch]] = i; + cftab[ch]++; + } + + cftab = null; + + tPos = tt[origPtr]; + + count = 0; + i2 = 0; + ch2 = 256; /*-- not a char and not EOF --*/ + + if (blockRandomised) { + rNToGo = 0; + rTPos = 0; + SetupRandPartA(); + } else { + SetupNoRandPartA(); + } + } + + void SetupRandPartA() + { + if (i2 <= last) { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) { + rNToGo = BZip2Constants.RandomNumbers[rTPos]; + rTPos++; + if (rTPos == 512) { + rTPos = 0; + } + } + rNToGo--; + ch2 ^= (int)((rNToGo == 1) ? 1 : 0); + i2++; + + currentChar = ch2; + currentState = RAND_PART_B_STATE; + mCrc.Update(ch2); + } else { + EndBlock(); + InitBlock(); + SetupBlock(); + } + } + + void SetupNoRandPartA() + { + if (i2 <= last) { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + i2++; + + currentChar = ch2; + currentState = NO_RAND_PART_B_STATE; + mCrc.Update(ch2); + } else { + EndBlock(); + InitBlock(); + SetupBlock(); + } + } + + void SetupRandPartB() + { + if (ch2 != chPrev) { + currentState = RAND_PART_A_STATE; + count = 1; + SetupRandPartA(); + } else { + count++; + if (count >= 4) { + z = ll8[tPos]; + tPos = tt[tPos]; + if (rNToGo == 0) { + rNToGo = BZip2Constants.RandomNumbers[rTPos]; + rTPos++; + if (rTPos == 512) { + rTPos = 0; + } + } + rNToGo--; + z ^= (byte)((rNToGo == 1) ? 1 : 0); + j2 = 0; + currentState = RAND_PART_C_STATE; + SetupRandPartC(); + } else { + currentState = RAND_PART_A_STATE; + SetupRandPartA(); + } + } + } + + void SetupRandPartC() + { + if (j2 < (int)z) { + currentChar = ch2; + mCrc.Update(ch2); + j2++; + } else { + currentState = RAND_PART_A_STATE; + i2++; + count = 0; + SetupRandPartA(); + } + } + + void SetupNoRandPartB() + { + if (ch2 != chPrev) { + currentState = NO_RAND_PART_A_STATE; + count = 1; + SetupNoRandPartA(); + } else { + count++; + if (count >= 4) { + z = ll8[tPos]; + tPos = tt[tPos]; + currentState = NO_RAND_PART_C_STATE; + j2 = 0; + SetupNoRandPartC(); + } else { + currentState = NO_RAND_PART_A_STATE; + SetupNoRandPartA(); + } + } + } + + void SetupNoRandPartC() + { + if (j2 < (int)z) { + currentChar = ch2; + mCrc.Update(ch2); + j2++; + } else { + currentState = NO_RAND_PART_A_STATE; + i2++; + count = 0; + SetupNoRandPartA(); + } + } + + void SetDecompressStructureSizes(int newSize100k) + { + if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k && blockSize100k <= 9)) { + throw new BZip2Exception("Invalid block size"); + } + + blockSize100k = newSize100k; + + if (newSize100k == 0) { + return; + } + + int n = BZip2Constants.BaseBlockSize * newSize100k; + ll8 = new byte[n]; + tt = new int[n]; + } + + static void CompressedStreamEOF() + { + throw new EndOfStreamException("BZip2 input stream end of compressed stream"); + } + + static void BlockOverrun() + { + throw new BZip2Exception("BZip2 input stream block overrun"); + } + + static void BadBlockHeader() + { + throw new BZip2Exception("BZip2 input stream bad block header"); + } + + static void CrcError() + { + throw new BZip2Exception("BZip2 input stream crc error"); + } + + static void HbCreateDecodeTables(int[] limit, int[] baseArray, int[] perm, char[] length, int minLen, int maxLen, int alphaSize) + { + int pp = 0; + + for (int i = minLen; i <= maxLen; ++i) + { + for (int j = 0; j < alphaSize; ++j) + { + if (length[j] == i) + { + perm[pp] = j; + ++pp; + } + } + } + + for (int i = 0; i < BZip2Constants.MaximumCodeLength; i++) + { + baseArray[i] = 0; + } + + for (int i = 0; i < alphaSize; i++) + { + ++baseArray[length[i] + 1]; + } + + for (int i = 1; i < BZip2Constants.MaximumCodeLength; i++) + { + baseArray[i] += baseArray[i - 1]; + } + + for (int i = 0; i < BZip2Constants.MaximumCodeLength; i++) + { + limit[i] = 0; + } + + int vec = 0; + + for (int i = minLen; i <= maxLen; i++) + { + vec += (baseArray[i + 1] - baseArray[i]); + limit[i] = vec - 1; + vec <<= 1; + } + + for (int i = minLen + 1; i <= maxLen; i++) + { + baseArray[i] = ((limit[i - 1] + 1) << 1) - baseArray[i]; + } + } + + #region Instance Fields + /*-- + index of the last char in the block, so + the block size == last + 1. + --*/ + int last; + + /*-- + index in zptr[] of original string after sorting. + --*/ + int origPtr; + + /*-- + always: in the range 0 .. 9. + The current block size is 100000 * this number. + --*/ + int blockSize100k; + + bool blockRandomised; + + int bsBuff; + int bsLive; + IChecksum mCrc = new StrangeCRC(); + + bool[] inUse = new bool[256]; + int nInUse; + + byte[] seqToUnseq = new byte[256]; + byte[] unseqToSeq = new byte[256]; + + byte[] selector = new byte[BZip2Constants.MaximumSelectors]; + byte[] selectorMtf = new byte[BZip2Constants.MaximumSelectors]; + + int[] tt; + byte[] ll8; + + /*-- + freq table collected to save a pass over the data + during decompression. + --*/ + int[] unzftab = new int[256]; + + int[][] limit = new int[BZip2Constants.GroupCount][]; + int[][] baseArray = new int[BZip2Constants.GroupCount][]; + int[][] perm = new int[BZip2Constants.GroupCount][]; + int[] minLens = new int[BZip2Constants.GroupCount]; + + Stream baseStream; + bool streamEnd; + + int currentChar = -1; + + int currentState = START_BLOCK_STATE; + + int storedBlockCRC, storedCombinedCRC; + int computedBlockCRC; + uint computedCombinedCRC; + + int count, chPrev, ch2; + int tPos; + int rNToGo; + int rTPos; + int i2, j2; + byte z; + bool isStreamOwner = true; + #endregion + } +} +/* This file was derived from a file containing this license: + * + * This file is a part of bzip2 and/or libbzip2, a program and + * library for lossless, block-sorting data compression. + * + * Copyright (C) 1996-1998 Julian R Seward. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. The origin of this software must not be misrepresented; you must + * not claim that you wrote the original software. If you use this + * software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 3. Altered source versions must be plainly marked as such, and must + * not be misrepresented as being the original software. + * + * 4. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Java version ported by Keiron Liddle, Aftex Software <keiron@aftexsw.com> 1999-2001 + */