/* OpenAmiga.org
 *  This file is part of the OpenAmiga "xfdmaster library project"
 *
 * For more information look at:
 *   http://openamiga.org/?function=viewproject&projectid=67
 */

/* XFD external slave for SZDD files (C) 2000 Stuart Caie <kyzer@4u.net>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library 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 Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $VER: SZDD.asm 1.3 (29.06.2000)
 * v1.1: better xfd name, eliminated 4k buffer
 * v1.2: better header text, no code changed
 * v1.3: off-by-one error fixed, spaces-vs-nulls error fixed
 * v1.4: added LPAK format. This is the same algo as the M$ format, but with
 *       less headers and a different window start offset.
 *
 * Files in the SZDD file format are created with Microsoft's COMPRESS.EXE
 * program, and decompressed with Microsoft's EXPAND.EXE program. The file,
 * when compressed, usually has an underscore as the last character in its
 * filename, ie HELLO.EX_ or VBRUN666.DL_
 *
 * File header format:
 * 4 bytes : "SZDD" (0x53,0x5A,0x44,0x44) - magic ID
 * 4 bytes : 0x88,0xF0,0x27,0x33 - more magic ID
 * 1 byte  : compression mode - only 'A' (0x41) known
 * 1 byte  : the character missing from the end of the filename (0=unknown)
 * 4 bytes : A,B,C,D. unpacked size of file is (D<<24)|(C<<16)|(B<<8)|(A)
 * n bytes : the compressed data
 *
 * The compressed data format is basically LZSS
 * - one control byte, whose bits dictate the format of the next 8 elements,
 *   in the order of LSB to MSB:
 *   - if a bit is set, the next element is a single byte literal
 *   - otherwise, the next element is a repeat: two bytes, %AAAAAAAA %BBBBCCCC
 *     where offset = %BBBBAAAAAAAA and length is %CCCC + 3
 * - this repeats until enough data has been unpacked
 *
 * A 4096 byte wraparound buffer must be kept by the decruncher. A wraparound
 * pointer starts at 4096-16 and each outputted byte must be written at that
 * position and the pointer advanced by one. The offsets of the compressed
 * data refer to an absolute position in this buffer, which may be before the
 * current pointer, or after it. The buffer's initial state is that all
 * elements are the space character (0x20).
 *
 * Also, it seems that there can often be superfluous data at the end of a
 * file - I can only go by the uncompressed size, so I can only really test
 * running out of source data. Some files end in the middle of a run, so I
 * can't test if they exactly reach the endpoint and no more.
 *
 * This code was based on code found in a document describing the Windows Help
 * file format, written by M. Winterhoff <100326.2776@compuserve.com>.
 */

#define __NOLIBBASE__
#define __NOGLOBALIFACE__

#include <proto/xfdmaster.h>
#include <proto/exec.h>
#include <exec/memory.h>

static const char USED version[]="\0$VER: SZDD 1.4 (16.09.2000) by <kyzer@4u.net>";

#define SZDD_WNDSIZE 4096
#define SZDD_WNDMASK 4095
#define SZDD_HDRLEN  14
#define LPAK_HDRLEN  8
#define SZDD_OVERRUN 144

#define BE32GET(a)      ((((a)[0])<<24)|(((a)[1])<<16)|(((a)[2])<<8)|((a)[3]))
#define LE32GET(a)      ((((a)[3])<<24)|(((a)[2])<<16)|(((a)[1])<<8)|((a)[0]))
#define GETLONG(x,n)    BE32GET(&x[n])
#define GETLONG_LE(x,n) LE32GET(&x[n])

BOOL LPAK_recog(struct xfdMasterIFace *Self, UBYTE *buf, ULONG length,
  struct xfdRecogResult *rr)
{
  if (GETLONG(buf, 0) == 'LPAK') {
    rr->xfdrr_MinTargetLen = (rr->xfdrr_FinalTargetLen = GETLONG(buf, 4)) + SZDD_OVERRUN;
    return TRUE;
  }
  return FALSE;
}

BOOL SZDD_recog(struct xfdMasterIFace *Self, UBYTE *buf, ULONG length,
  struct xfdRecogResult *rr)
{
  if (GETLONG(buf, 0) == 'SZDD') {
    rr->xfdrr_MinTargetLen = (rr->xfdrr_FinalTargetLen = GETLONG_LE(buf, 10)) + SZDD_OVERRUN;
    return TRUE;
  }
  return FALSE;
}

BOOL SZDD_decrunch(struct xfdMasterIFace *Self, struct xfdBufferInfo *xbi) {
  UBYTE *src  = (UBYTE *)xbi->xfdbi_SourceBuffer;
  UBYTE *dest = (UBYTE *)xbi->xfdbi_UserTargetBuf;
  UBYTE *ends = &src[xbi->xfdbi_SourceBufLen];
  UBYTE *endd = &dest[xbi->xfdbi_TargetBufSaveLen];
  UBYTE *base = dest;
  LONG posn, offset, length;
  UBYTE *rep, mask, bits;

  /* skip header and set up some stuff */
  if (GETLONG(src, 0) == 'SZDD') {
    src += SZDD_HDRLEN;
    posn = SZDD_WNDSIZE - 16;
  } else {
    src += LPAK_HDRLEN;
    posn = SZDD_WNDSIZE - 18;
  }

  while (src < ends) {
    bits = *src++;
    for (mask = 0x01; mask & 0xFF; mask <<= 1) {
      if (bits & mask) { /* literal */
        *dest++ = *src++;
        posn++;
      } else { /* repeat */
        offset  = *src++;
        length  = *src++;
        offset |= (length << 4) & 0xF00;
        length  = (length & 0x0F) + 3;

        /* translate absolute 4k buffer offset into real window offset */
        posn &= SZDD_WNDMASK;
        if (offset > posn) posn += SZDD_WNDSIZE;
        rep = dest + offset - posn;
        posn += length;

        /* if repeat starts before actual data, write spaces */
        while (rep < base && length-- > 0) {
          rep++;
          *dest++ = ' ';
        }

        /* copy out repeat */
        while (length-- > 0) {
          *dest++ = *rep++;
        }
      }
    }

    /* at worst, we could overrun dest by (15+3)*8 = 144 bytes */
    if (dest >= endd) return TRUE;
  }

  /* bad exit - run out of src */
  xbi->xfdbi_Error = XFDERR_CORRUPTEDDATA;
  return FALSE;
}

struct xfdSlave LPAKslv = {
  NULL, XFDS_VERSION, 39, "(LPAK) Data Cruncher",
  XFDPFF_DATA|XFDPFF_RECOGLEN|XFDPFF_USERTARGET,
  0, (BOOL (*)()) LPAK_recog, (BOOL (*)()) SZDD_decrunch,
  NULL, NULL, 0, 0, LPAK_HDRLEN
};

struct xfdSlave SZDDslv = {
  &LPAKslv, XFDS_VERSION, 39, "(SZDD) Microsoft Data Cruncher",
  XFDPFF_DATA|XFDPFF_RECOGLEN|XFDPFF_USERTARGET,
  0, (BOOL (*)()) SZDD_recog, (BOOL (*)()) SZDD_decrunch,
  NULL, NULL, 0, 0, SZDD_HDRLEN
};

struct xfdForeMan xfdForeMan = {
  0x70FF4E75,
  XFDF_ID,
  XFDF_VERSION,
  0,
  NULL,
  ZERO,
  &SZDDslv
};

int _start(void) {
  return RETURN_FAIL;
}

