/*
** winlzexp.c - Windows LZExpand library routines for manipulating compressed
**              files.
**
** Author:  DavidDi
*/


/*
** Notes:
** -----
**
** The LZInit() function returns either DOS handles or LZFile struct
** identifiers of some kind, depending upon how it is called.  The LZ
** functions LZSeek(), LZRead(), and LZClose() needed some way to
** differentiate between DOS file handles and the LZFile struct identifiers.
** As the functions stand now, they use DOS file handles as themselves, and
** table offsets as LZFile identifiers.  The table offsets are incremented by
** some base value, LZ_TABLE_BIAS, in order to push their values past all
** possible DOS file handle values.  The table offsets (- LZ_TABLE_BIAS) are
** used as indices in rghLZFileTable[] to retrieve a global handle to an
** LZFile struct.  The table of global handles is allocated statically from
** the DLL's data segment.  The LZFile struct's are allocated from global
** heap space and are moveable.  This scheme might also be implemented as a
** linked list of global handles.
**
** Now the resulting benefit from this scheme is that DOS file handles and
** LZFile struct identifiers can be differentiated, since DOS file handles
** are always < LZ_TABLE_BIAS, and LZFile struct identifiers are always
** >= LZ_TABLE_BIAS.  This dichotomy may be used in macros, like the sample
** ones provided in lzexpand.h, to select the appropriate function to call
** (e.g., LZSeek() or _llseek()) in order to avoid the overhead of an extra
** function call for uncompressed files.  LZSeek(), LZRead(), and LZClose()
** are, however, "smart" enough to figure out whether they are dealing with
** DOS file handles or table offsets, and take action appropriately.  As an
** extreme example, LZOpenFile(), LZSeek(), LZRead, and LZClose() can be used
** as replacements for OpenFile(), _llseek(), _lread(), and _lclose.  In this
** case, the program using the DLL functions could call them without ever
** caring whether the files it was reading were LZ compressed or not.
*/


/* WIN32 MODS

**  Since the above is a DOS only hack, I have to change the logic for
** for the 0-255 file handle deal'o.  The original code, tests greater than
** LZ_TABLE_BIAS for file structures.  What I will do, is convert file handles
** returned from OpenFile, to a range 0-255.  Once the test is complete, I'll
** use the file handle as an offset into a 255 element array, which will
** contain the WIN32 file handle.  So there will be an additional array
** fhWin32File[255], which will be allocated sequencially starting at 0.
** Unfortunately, this means everywhere the file handle is used, must be converted
*/

// Headers
///////////
#include <basedll.h>
#define LZA_DLL
#include "lz_common.h"
#include "lz_buffers.h"
#include "lz_header.h"
#include "lzcommon.h"

#include "lzpriv.h"
#include "wchar.h"

#if DEBUG
#include "stdio.h"
#endif

// Globals
///////////

// Semaphore for File Table allocation
RTL_CRITICAL_SECTION semTable;
BOOL fCSInit = FALSE;

// table of handles to LZFile structs
HANDLE rghLZFileTable[MAX_LZFILES] = {0};

// next free entry in rghLZFileTable[]
static INT iNextLZFileEntry = 0;

HFILE fhWin32File[MAX_LZFILES] = {0};

/*
** int  APIENTRY LZInit(int hWin32File);
**
** Sets up LZFile struct for a file that has already been opened by
** OpenFile().
**
** Arguments:  hWin32File - source DOS file handle
**
** Returns:    int - LZFile struct table offset or DOS file handle if
**                   successful.  One of the LZERROR_ codes if unsuccessful.
**
** Globals:    iNextLZFile entry advanced, or returned to beginning from end.
*/
INT  APIENTRY LZInit(INT hWin32File)
{
   HANDLE hLZFile;            // handle to new LZFile struct
   LZFile *lpLZ;          // pointer to new LZFile struct
   FH FHComp;                 // header info structure from input file
   BOOL bHdr;                 // holds GetHdr() return value
   INT iTableIndex,           // holds rghLZFileTable[] slot to be filled by
                              //    new LZFile struct handle
       iStartEntry;           // original value of iNextLZFileEntry

   LONG cblInSize = 0;
   INT nRet;

   if (!fCSInit) {
       if (!NT_SUCCESS(RtlInitializeCriticalSection(&semTable))) {
           return LZERROR_GLOBALLOC;
       }
       fCSInit = TRUE;
   }

   // Did the read succeed?
   if ((bHdr = GetHdr((FH *)&FHComp, hWin32File, &cblInSize)) != TRUE
       && cblInSize >= (LONG)HEADER_LEN) {

      return(LZERROR_BADINHANDLE);
   }

   // Check for uncompressed input file.
   if (bHdr != TRUE || IsCompressed(& FHComp) != TRUE)
   {
      // This is an uncompressed file - rewind it.
      if (FSEEK(hWin32File, 0L, SEEK_SET) != 0L) {
         return(LZERROR_BADINHANDLE);
      }
      else {
         // And return DOS file handle.
        return(ConvertWin32FHToDos(hWin32File));
     }
   }

   // Check compression algorithm used.
   if (RecognizeCompAlg(FHComp.byteAlgorithm) != TRUE) {
      return(LZERROR_UNKNOWNALG);
   }

   // Find next free rghLZFileTable[] entry.  N.b., we depend upon LZClose()
   // to free unused entries up.
   RtlEnterCriticalSection(&semTable);

   iStartEntry = iNextLZFileEntry;

   while (rghLZFileTable[iNextLZFileEntry] != NULL)
   {
      if (++iNextLZFileEntry >= MAX_LZFILES)
         // Return to beginning of table.
         iNextLZFileEntry = 0;

      if (iNextLZFileEntry == iStartEntry) {
         // We've gone full circle through rghLZFileTable[].
         // It's full, so bail out.
         nRet = LZERROR_GLOBALLOC;
         goto LZInitExit;
      }
   }

   // Keep track of the rghLZFileTable() slot to be filled by a handle to the
   // new LZFile struct.
   iTableIndex = iNextLZFileEntry;

   // Allocate global storage for the new LZFile struct, initializing all
   // fields to 0.

   hLZFile = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (DWORD)sizeof(LZFile));
   if (!hLZFile) {

      nRet = LZERROR_GLOBALLOC;
      goto LZInitExit;
   }

   // Lock that baby up.
   if ((lpLZ = (LZFile *)GlobalLock(hLZFile)) == NULL)
   {
      GlobalFree(hLZFile);

      nRet =LZERROR_GLOBLOCK;
      goto LZInitExit;
   }

   // Initialize the new LZFile struct's general information fields.
   lpLZ->dosh = hWin32File;
   lpLZ->byteAlgorithm = FHComp.byteAlgorithm;
   lpLZ->wFlags = 0;
   lpLZ->cbulUncompSize = FHComp.cbulUncompSize;
   lpLZ->cbulCompSize = FHComp.cbulCompSize;
   lpLZ->lCurSeekPos = 0L;

   // LZRead/LZSeeks expansion data is kept on a per file basis
   lpLZ->pLZI = NULL;

   // Enter new handle in table of handles.
   rghLZFileTable[iTableIndex] = hLZFile;

   /* WIN32 NOTE, dont need convert below, as forces a non file handle
    * to the API.
    */

   GlobalUnlock(hLZFile);

   // Advance to next free entry.
   if (++iNextLZFileEntry >= MAX_LZFILES)
      iNextLZFileEntry = 0;

   nRet = LZ_TABLE_BIAS + iTableIndex;

LZInitExit:

   RtlLeaveCriticalSection(&semTable);

   // Return rghLZFileTable[] offset of the new LZFile struct's handle's
   // entry + the table bias.
   return(nRet);
}


/*
** int  APIENTRY GetExpandedNameA(LPSTR lpszSource, LPSTR lpszBuffer);
**
** Looks in the header of a compressed file to find its original expanded
** name.
**
** Arguments:  lpszSource - name of input file
**             lpszBuffer - pointer to a buffer that will be filled in with
**                          the expanded name of the compressed source file
**
** Returns:    int - TRUE if successful.  One of the LZERROR_ codes if
**                   unsuccessful.
**
** Globals:    none
*/
INT  APIENTRY GetExpandedNameA(LPSTR lpszSource, LPSTR lpszBuffer)
{
   INT doshSource,         // source DOS file handle
       bHdr;               // holds GetHdr() return value
   FH FHComp;              // header info structure from input file
   OFSTRUCT ofOpenBuf;     // source struct for OpenFile() call
   LONG cblInSize = 0;

   // Try to open the source file.
   if ((doshSource = (HFILE)MOpenFile(lpszSource, (LPOFSTRUCT)(& ofOpenBuf), OF_READ))
       == -1)
      return(LZERROR_BADVALUE);

   // Get the compressed file header.
   if ((bHdr = GetHdr((FH *)&FHComp, doshSource, &cblInSize)) != TRUE
       && cblInSize >= (LONG)HEADER_LEN)
   {
      FCLOSE(doshSource);
      return(LZERROR_BADVALUE);
   }

   // Close source file.
   FCLOSE(doshSource);

   // Return expanded name same as source name for uncompressed files.
   STRCPY(lpszBuffer, lpszSource);

   // Check for compressed input file.
   if (bHdr == TRUE && IsCompressed(& FHComp) == TRUE)
      MakeExpandedName(lpszBuffer, FHComp.byteExtensionChar);

   return(TRUE);
}

/*
** int  APIENTRY GetExpandedNameW(LPSTR lpszSource, LPSTR lpszBuffer);
**
** Wide Character version of GetExpandedName.  Converts the filename to
** the ANSI Character set and calls the ANSI version.
**
*/
INT  APIENTRY GetExpandedNameW(LPWSTR lpszSource, LPWSTR lpszBuffer)
{
    UNICODE_STRING TempW;
    ANSI_STRING TempA;
    NTSTATUS Status;
    NTSTATUS StatusR;
    CHAR szBuffer[MAX_PATH];


    TempW.Buffer = lpszSource;
    TempW.Length = wcslen(lpszSource)*sizeof(WCHAR);
    TempW.MaximumLength = TempW.Length + sizeof(WCHAR);

    TempA.Buffer = szBuffer;
    TempA.MaximumLength = MAX_PATH;
    StatusR = RtlUnicodeStringToAnsiString(&TempA, &TempW, FALSE);
    if (!NT_SUCCESS(StatusR))
        return LZERROR_GLOBALLOC;

    Status = GetExpandedNameA(szBuffer, (LPSTR)lpszBuffer);

    if (Status != -1) {
        strcpy(szBuffer, (LPSTR)lpszBuffer);
        TempA.Length = (USHORT) strlen(szBuffer);
        TempA.MaximumLength = TempA.Length+sizeof(CHAR);

        TempW.Buffer = lpszBuffer;
        TempW.MaximumLength = MAX_PATH;
        StatusR = RtlAnsiStringToUnicodeString(&TempW, &TempA, FALSE);
        if (!NT_SUCCESS(StatusR))
            return LZERROR_GLOBALLOC;
    }

    return Status;
}


//
// INT  LZCreateFileW(LPCWSTR lpFileName, DWORD fdwAccess)
//
// Opens a file (using CreateFile) and sets up an LZFile struct for
// expanding it.
//
// Arguments:  lpFileName  - name of input file
//             fdwAccess   - CreateFile access type - (e.g. GENERIC_READ)
//             fdwShareMode - Share mode  - (e.g. FILE_SHARE_READ)
//             fdwCreate - Action to be taken - (e.g. OPEN_EXISTING)
//
// Returns:    INT - LZFile struct table offset or WIN32 file HANDLE if
//             successful.  One of the LZERROR_ codes if unsuccessful.
//
INT
LZCreateFileW(
    LPWSTR lpFileName,
    DWORD fdwAccess,
    DWORD fdwShareMode,
    DWORD fdwCreate,
    LPWSTR lpCompressedName)
{
    HANDLE hWin32;           // WIN32 file HANDLE returned from CreateFileW()
    INT lzh;                 // LZ File struct ID returned from LZInit()
    static WCHAR pszCompName[MAX_PATH];  // buffer for compressed name

    lstrcpyW((LPWSTR)pszCompName, lpFileName);

    // Just for Vlad, only try to open the compressed version of the original
    // file name if we can't find the original file.  All other errors get
    // returned immediately.

    hWin32 = CreateFileW(pszCompName, fdwAccess, fdwShareMode, NULL, fdwCreate,
        FILE_ATTRIBUTE_NORMAL, NULL);

    if (hWin32 == INVALID_HANDLE_VALUE) {
        DWORD dwErr = GetLastError();

        if (dwErr == ERROR_FILE_NOT_FOUND) {

            // Let's try to open the file of the corresponding compressed name.
            MakeCompressedNameW((LPWSTR)pszCompName);

            hWin32 = CreateFileW(pszCompName, fdwAccess, fdwShareMode,
                NULL, fdwCreate, FILE_ATTRIBUTE_NORMAL, NULL);
        }
    }

     // Error opening file?
     if (hWin32 == INVALID_HANDLE_VALUE) {
        return(LZERROR_BADINHANDLE);
     }

    // Don't call LZinit() on files opened in other read only mode

    if (fdwCreate != OPEN_EXISTING) {
       lzh = ConvertWin32FHToDos((HFILE)((DWORD_PTR)hWin32));
       if (lzh == LZERROR_GLOBALLOC) {
          CloseHandle(hWin32);
       }

       return(lzh);
    }

    // File has been opened with read-only action - call LZInit() to detect
    // whether or not it's an LZ file, and to create structures for expansion
    // if it is an LZ file.
    lzh = LZInit((INT)((DWORD_PTR)hWin32));

    // Close DOS file handle if LZInit() is unsuccessful.
    if (lzh < 0)
       CloseHandle(hWin32);

    // Pass real file name to caller
    //
    // we believe the caller have enough buffer.
    //
    if( lpCompressedName != NULL )
        lstrcpyW(lpCompressedName,pszCompName);

    // Return LZ struct ID or LZERROR_ code.
    return(lzh);
}


/*
** int  APIENTRY LZOpenFileA(LPCSTR lpFileName, LPOFSTRUCT lpReOpenBuf,
**                           WORD wStyle);
**
** Opens a file and sets up an LZFile struct for expanding it.
**
** Arguments:  lpFileName  - name of input file
**             lpReOpenBuf - pointer to LPOFSTRUCT to be used by OpenFile()
**             wStyle      - OpenFile() action to take
**
** Returns:    int - LZFile struct table offset or DOS file handle if
**                   successful.  One of the LZERROR_ codes if unsuccessful.
**
** Globals:    none
*/
INT APIENTRY LZOpenFileA(LPSTR lpFileName, LPOFSTRUCT lpReOpenBuf, WORD wStyle)
{
   INT dosh,                     // DOS file handle returned from OpenFile()
       lzh;                      // LZ File struct ID returned from LZInit()
   CHAR pszCompName[MAX_PATH];   // buffer for compressed name

   STRCPY((LPSTR)pszCompName, lpFileName);

   // Just for Vlad, only try to open the compressed version of the original
   // file name if we can't find the original file.  All other errors get
   // returned immediately.

   if ((dosh = OpenFile(pszCompName, lpReOpenBuf, wStyle)) == -1 &&
       lpReOpenBuf->nErrCode == DEE_FILENOTFOUND)
   {
      // Let's try to open the file of the corresponding compressed name.
      MakeCompressedName(pszCompName);

      dosh = (HFILE) MOpenFile((LPSTR)pszCompName, lpReOpenBuf, wStyle);
   }

   // Error opening file?
   if (dosh == -1)
      return(LZERROR_BADINHANDLE);

   // Don't call LZinit() on files opened in other than O_RDONLY mode.
   // Ignore the SHARE bits.
   if ((wStyle & STYLE_MASK) != OF_READ) {
      lzh = ConvertWin32FHToDos(dosh);
      if (lzh == LZERROR_GLOBALLOC) {
         FCLOSE(dosh);
      }
      return(lzh);
   }

   // File has been opened with OF_READ style - call LZInit() to detect
   // whether or not it's an LZ file, and to create structures for expansion
   // if it is an LZ file.
   lzh = LZInit(dosh);

   // Close DOS file handle if LZInit() is unsuccessful.
   if (lzh < 0)
      FCLOSE(dosh);

   // Return LZ struct ID or LZERROR_ code.
   return(lzh);
}

/*
** int  APIENTRY LZOpenFileW(LPCWSTR lpFileName, LPOFSTRUCT lpReOpenBuf,
**                           WORD wStyle);
**
** Wide Character version of LZOpenFile.  Converts the filename to
** the ANSI Character set and calls the ANSI version.
**
*/
INT APIENTRY LZOpenFileW(LPWSTR lpFileName, LPOFSTRUCT lpReOpenBuf, WORD wStyle)
{
    UNICODE_STRING FileName;
    ANSI_STRING AnsiString;
    NTSTATUS StatusR;
    CHAR szFileName[MAX_PATH];


    FileName.Buffer = lpFileName;
    FileName.Length = wcslen(lpFileName)*sizeof(WCHAR);
    FileName.MaximumLength = FileName.Length + sizeof(WCHAR);

    AnsiString.Buffer = szFileName;
    AnsiString.MaximumLength = MAX_PATH;
    StatusR = RtlUnicodeStringToAnsiString(&AnsiString, &FileName, FALSE);
    if (!NT_SUCCESS(StatusR))
        return LZERROR_GLOBALLOC;

    return(LZOpenFileA(szFileName, lpReOpenBuf, wStyle));
}



/*
** LONG  APIENTRY LZSeek(int oLZFile, long lSeekTo, int nMode);
**
** Works like _llseek(), but in the expanded image of a compressed file,
** without expanding the compressed file to disk.
**
** Arguments:  oLZFile - source LZFile struct identifier or DOS file handle
**             lSeekTo - number of bytes past nMode target to seek
**             nMode   - seek mode as in _llseek()
**
** Returns:    LONG - Offset of the seek target if successful.  One of the
**                    LZERROR_ codes if unsuccessful.
**
** Globals:    none
*/
LONG APIENTRY
LZSeek(
   INT oLZFile,
   LONG lSeekTo,
   INT nMode)
{
   HANDLE hSourceStruct;      // handle to LZFile struct
   LZFile *lpLZ;          // pointer to LZFile struct
   LONG lExpSeekTarget;       // target seek offset

   // Check input LZFile struct indentifier / DOS file handle.
   if (oLZFile < 0 || oLZFile >= LZ_TABLE_BIAS + MAX_LZFILES)
      return(LZERROR_BADINHANDLE);

   // We were passed a regular DOS file handle, so just do an _llseek() on it.
   if (oLZFile < LZ_TABLE_BIAS)
      return(FSEEK(ConvertDosFHToWin32(oLZFile), lSeekTo, nMode));

   // We're dealing with a compressed file.  Get the associated LZFile struct.
   hSourceStruct = rghLZFileTable[oLZFile - LZ_TABLE_BIAS];

   if ((lpLZ = (LZFile *)GlobalLock(hSourceStruct)) == NULL)
      return(LZERROR_GLOBLOCK);

   // Figure out what our seek target is.
   if (nMode == SEEK_SET)
      lExpSeekTarget = 0L;
   else if (nMode == SEEK_CUR)
      lExpSeekTarget = lpLZ->lCurSeekPos;
   else if (nMode == SEEK_END)
      lExpSeekTarget = (LONG)lpLZ->cbulUncompSize;
   else
   {
      GlobalUnlock(hSourceStruct);
      return(LZERROR_BADVALUE);
   }

   // Add bias.
   lExpSeekTarget += lSeekTo;

   // Make sure the desired expanded file position is in the expanded file
   // bounds.  It's only an error to seek before the beginning of the file;
   // it's not an error to seek after the end of the file, as in _llseek().
   if (lExpSeekTarget < 0L)
   {
      GlobalUnlock(hSourceStruct);
      return(LZERROR_BADVALUE);
   }

   // Seek target is ok.  Set the file pointer for the expanded file image.
   lpLZ->lCurSeekPos = lExpSeekTarget;

   GlobalUnlock(hSourceStruct);

   // Return the offset of the seek target.
   return(lExpSeekTarget);
}


/*
** int  APIENTRY LZRead(int oLZFile, LPSTR lpBuf, int nCount);
**
** Works like _lread(), but in the expanded image of a compressed file,
** without expanding the compressed file to disk.
**
** Arguments:  oLZFile - source LZFile struct identifier or DOS file handle
**             lpBuf   - pointer to destination buffer for bytes read
**             nCount  - number of bytes to read
**
** Returns:    int - Number of bytes copied to destination buffer if
**                   successful.  One of the LZERROR_ codes if unsuccessful.
**
** Globals:    none
*/
INT  APIENTRY LZRead(INT oLZFile, LPSTR lpBuf, INT nCount)
{
   INT f;
   HANDLE hSourceStruct;      // handle to LZFile struct
   LZFile *lpLZ;          // pointer to LZFile struct

   INT cbWritten = 0,         // total number of bytes copied to
                              //    destination buffer
       cbCopied,              // number of bytes copied to destination
                              // buffer during each read iteration
       iCurReadPos,           // current read offset in expanded buffer
       nNumExpBufBytes;       // number of bytes in expanded data buffer
   LONG lNextDecodeTarget,    // expanded file image read target for decoding
        lStartCopyOffset,     // expanded file buffer offset where we should
                              //    start copying to destination buffer (cast
                              //    to iCurReadPos when this start position
                              //    is actually in the buffer)
        lNextExpEndOffset;    // expanded file offset of the start of the
                              //    next desired block of expanded data
   BOOL bRestartDecoding;     // flag indicating whether or not decoding
                              //    needs to be restarted, set to TRUE when
                              //    the current seek position is smaller than
                              //    the offset of the beginning of the
                              //    expanded file buffer
   BYTE *lpbyteBuf;           // byte pointer version of lpBuf

   LONG lExpBufStart;
   LONG lExpBufEnd;

   PLZINFO pLZI;

   // Check input LZFile struct indentifier / DOS file handle.
   if (oLZFile < 0 || oLZFile >= LZ_TABLE_BIAS + MAX_LZFILES)
      return(LZERROR_BADINHANDLE);

   // Can't read a negative number of bytes.
   if (nCount < 0)
      return(LZERROR_BADVALUE);

   // We were passed a regular DOS file handle, so just do an _lread() on it.
   if (oLZFile < LZ_TABLE_BIAS)
      return(FREAD(ConvertDosFHToWin32(oLZFile), lpBuf, nCount));

   // We're dealing with a compressed file.  Get the associated LZFile struct.
   hSourceStruct = rghLZFileTable[oLZFile - LZ_TABLE_BIAS];

   if ((lpLZ = (LZFile *)GlobalLock(hSourceStruct)) == NULL)
      return(LZERROR_GLOBLOCK);

   if (!(pLZI = lpLZ->pLZI)) {
      // Initialize buffers
      lpLZ->pLZI = InitGlobalBuffers(EXP_BUF_LEN, MAX_RING_BUF_LEN, IN_BUF_LEN + 1);

      if (!(pLZI = lpLZ->pLZI)) {
         return(LZERROR_GLOBALLOC);
      }

      ResetBuffers();
   }

   lExpBufStart = pLZI->cblOutSize - (LONG)(pLZI->pbyteOutBuf - pLZI->rgbyteOutBuf);
   lExpBufEnd = (LONG)(pLZI->pbyteOutBufEnd - pLZI->rgbyteOutBuf);

   // Do we need to restart decoding?
   if (! (lpLZ->wFlags & LZF_INITIALIZED))
   {
      lpLZ->wFlags |= LZF_INITIALIZED;
      bRestartDecoding = TRUE;
   }
   else if (lpLZ->lCurSeekPos < lExpBufStart)
      bRestartDecoding = TRUE;
   else
      bRestartDecoding = FALSE;

   // Set up byte pointer version of lpBuf.
   lpbyteBuf = lpBuf;

   // Copy bytes until buffer is filled or EOF in expanded file image is
   // reached.
   while (nCount > 0 && lpLZ->lCurSeekPos < (LONG)lpLZ->cbulUncompSize)
   {
      /* How many expanded data bytes are in the expanded data buffer?
       * (pbyteOutBuf points AFTER the last valid byte in rgbyteOutBuf[].)
       */
      nNumExpBufBytes = (INT)(pLZI->pbyteOutBuf - pLZI->rgbyteOutBuf);

      /* Is the start of the desired expanded data currently in the bounds of
       * the expanded data buffer?
       */
      lStartCopyOffset = lpLZ->lCurSeekPos - lExpBufStart;
      if (lStartCopyOffset < lExpBufEnd)
         /* It's ok to set iCurReadPos to a negative value here, since we
          * will only use expanded data from the expanded data buffer if
          * iCurReadPos is non-negative.
          */
         iCurReadPos = (INT)lStartCopyOffset;
      else
         iCurReadPos = -1;

      /* Now, if iCurReadPos > 0, some of the expanded data in the expanded
       * data buffer should be copied to the caller's buffer.  If not, we
       * need to continue expanding or restart expanding.
       */
      if (iCurReadPos >= 0)
      {
         /* Copy available expanded data from expanded data buffer. */
         for (cbCopied = 0;
              iCurReadPos < nNumExpBufBytes && nCount > 0;
              cbCopied++, nCount--)
            *lpbyteBuf++ = pLZI->rgbyteOutBuf[iCurReadPos++];

         // Update expanded file pointer.
         lpLZ->lCurSeekPos += (LONG)cbCopied;

         // Keep track of bytes written to buffer.
         cbWritten += cbCopied;
      }

      /* Expand more data, restarting the expansion process first if
       * necessary.
       */
      if (nCount > 0 && lpLZ->lCurSeekPos < (LONG)lpLZ->cbulUncompSize)
      {
         /* If we get here, we've copied all the available expanded data out
          * of rgbyteOutBuf[], through pbyteOutBuf, and we need to expand
          * more data.
          */

         /* Where is the end of the next desired expanded data block? */
         if (bRestartDecoding)
         {
            /* Seek back to start of target data, allowing for buffer
             * overflow.
             */
            lNextExpEndOffset = lpLZ->lCurSeekPos - MAX_OVERRUN;

            /* Don't try to read before offset 0! */
            if (lNextExpEndOffset < 0)
               lNextExpEndOffset = 0;
         }
         else
            /* Continue decoding. */
            lNextExpEndOffset = lExpBufStart
                                + (LONG)nNumExpBufBytes
                                + lExpBufEnd
                                - MAX_OVERRUN;

         /* How much farther should we expand?  The target decoding offset
          * should be the smallest expanded file offset of the following:
          *
          *    1) the last byte in the largest expanded data block that will
          *       safely fit in the expanded data buffer, while guaranteeing
          *       that the start of this block is also in the expanded data
          *       buffer
          *    2) the last requested expanded data byte
          *    3) the last byte in the expanded file
          */
         lNextDecodeTarget = MIN(lNextExpEndOffset,
                                 MIN(lpLZ->lCurSeekPos + (LONG)nCount,
                                     (LONG)lpLZ->cbulUncompSize - 1L));

         // Reset expanded data buffer to empty state.
         pLZI->pbyteOutBuf = pLZI->rgbyteOutBuf;

         // Refill rgbyteOutBuf[] with expanded data.
         switch (lpLZ->byteAlgorithm)
         {
            case ALG_FIRST:
               f = LZDecode(lpLZ->dosh, NO_DOSH, lNextDecodeTarget,
                  bRestartDecoding, TRUE, pLZI);
               break;

            default:
               f = LZERROR_UNKNOWNALG;
               break;
         }

         // Did the decoding go ok?
         if (f != TRUE)
         {
            // Uh oh.  Something went wrong.
            GlobalUnlock(hSourceStruct);
            return(f);
         }

         /* Now how many expanded data bytes are in the expanded data buffer?
          * (pbyteOutBuf points AFTER the last valid byte in rgbyteOutBuf[].)
          */
#if DEBUG
         printf("pbyteOutBuf: 0x%x,  rgbyteOutBuf: 0x%x \n", pLZI->pbyteOutBuf, pLZI->rgbyteOutBuf);
#endif

         nNumExpBufBytes = (INT)(pLZI->pbyteOutBuf - pLZI->rgbyteOutBuf);

         /* Check to make sure we actually read some bytes. */
         if (nNumExpBufBytes <= 0)
         {
            GlobalUnlock(hSourceStruct);
            return(LZERROR_READ);
         }

         /* What is the offset of the start of the expanded data buffer in
          * the expanded file image?
          */
         lExpBufStart = pLZI->cblOutSize - (LONG)nNumExpBufBytes;

         /* Did LZDecode() satisfy the read request, or did the compressed
          * file end prematurely?
          */
         if (pLZI->cblOutSize < lNextDecodeTarget)
         {
            /* Oh oh.  lNextDecodeTarget cannot exceed the expanded file
             * bounds, so the compressed file must have ended prematurely.
             */
            GlobalUnlock(hSourceStruct);
            return(LZERROR_READ);
         }

         // Reset flag so we continue decoding where we left off.
         bRestartDecoding = FALSE;
      }
   }

   GlobalUnlock(hSourceStruct);

   // Return number of bytes copied to destination buffer.
   return(cbWritten);
}

//
// VOID  LZCloseFile(INT oLZFile);
//
// Close a file and free the associated LZFile struct.
//
// Arguments:  oLZFile - source LZFile struct identifier or WIN32 file handle
//
// Returns:    VOID
//
// Globals:    rghLZFileTable[] entry cleared.
//

VOID
LZCloseFile(
    INT oLZFile)
{
   HANDLE hSourceStruct;      // handle to LZFile struct
   LZFile *lpLZ;          // pointer to LZFile struct

   // Check input LZFile struct indentifier / DOS file handle.
   if (oLZFile < 0 || oLZFile >= LZ_TABLE_BIAS + MAX_LZFILES)
      return;

   // We were passed a regular DOS file handle, so just close it.
   if (oLZFile < LZ_TABLE_BIAS) {
      CloseHandle((HANDLE)IntToPtr(ConvertDosFHToWin32(oLZFile)));
      // also need to clean out the file array entry
      fhWin32File[oLZFile] = 0;

      return;
   }

   // We're dealing with a compressed file.  Get the associated LZFile struct.
   hSourceStruct = rghLZFileTable[oLZFile - LZ_TABLE_BIAS];

   // Clear rghLZFIleTable[] entry.
   rghLZFileTable[oLZFile - LZ_TABLE_BIAS] = NULL;

   // Close the file and free the associated LZFile struct.
   if ((lpLZ = (LZFile *)GlobalLock(hSourceStruct)) != NULL)
   {
      CloseHandle((HANDLE)IntToPtr(lpLZ->dosh));

      if (lpLZ->pLZI) {
         FreeGlobalBuffers(lpLZ->pLZI);
      }

      GlobalUnlock(hSourceStruct);

      GlobalFree(hSourceStruct);

   }

   return;
}


/*
** VOID  APIENTRY LZClose(int oLZFile);
**
** Close a file and free the associated LZFile struct.
**
** Arguments:  oLZFile - source LZFile struct identifier or DOS file handle
**
** Returns:    VOID
**
** Globals:    rghLZFileTable[] entry cleared.
*/
VOID  APIENTRY LZClose(INT oLZFile)
{
   HANDLE hSourceStruct;      // handle to LZFile struct
   LZFile *lpLZ;          // pointer to LZFile struct

   // Check input LZFile struct indentifier / DOS file handle.
   if (oLZFile < 0 || oLZFile >= LZ_TABLE_BIAS + MAX_LZFILES)
      return;

   // We were passed a regular DOS file handle, so just close it.
   if (oLZFile < LZ_TABLE_BIAS)
   {
      FCLOSE(ConvertDosFHToWin32(oLZFile));
      /* also need to clean out the file array entry */
      fhWin32File[oLZFile] = 0;

      return;
   }

   // We're dealing with a compressed file.  Get the associated LZFile struct.
   hSourceStruct = rghLZFileTable[oLZFile - LZ_TABLE_BIAS];

   // Clear rghLZFIleTable[] entry.
   rghLZFileTable[oLZFile - LZ_TABLE_BIAS] = NULL;

   // Close the file and free the associated LZFile struct.
   if ((lpLZ = (LZFile *)GlobalLock(hSourceStruct)) != NULL)
   {
      FCLOSE(lpLZ->dosh);

      if (lpLZ->pLZI) {
         FreeGlobalBuffers(lpLZ->pLZI);
      }

      GlobalUnlock(hSourceStruct);

      GlobalFree(hSourceStruct);

   }

   return;
}


/* WIN32 MODS   */

INT ConvertWin32FHToDos(HFILE DoshSource)
{
    INT x;

    if (!fCSInit) {
        if (!NT_SUCCESS(RtlInitializeCriticalSection(&semTable))) {
            return LZERROR_GLOBALLOC;
        }
        fCSInit = TRUE;
    }
 
    /* here we are given an NT file handle, need save this into
     * fhWin32File[], test for overflow, also need see
     * if there is a free slot in the array */

    /* If error, return greater than MAX_LZFILES */

    RtlEnterCriticalSection(&semTable);

    /* walk array, looking for a free slot (free slot = 0) */
    for(x = 0; x < MAX_LZFILES; x++){
        if(fhWin32File[x] == 0)
            break;
    }
    if(x < MAX_LZFILES){
       /* no overflow, save into array*/
       fhWin32File[x] = DoshSource;
    }
    else{
       x = LZERROR_GLOBALLOC;
    }

    RtlLeaveCriticalSection(&semTable);

    return(x);

}


HFILE ConvertDosFHToWin32(INT DoshSource)
{

    /* here, we are given the pseudo Dos File Handle, need convert to
     * real file handle, for use by API.
     */

    if (DoshSource >= MAX_LZFILES || DoshSource < 0 ||
        fhWin32File[DoshSource] == 0) {
        return (HFILE)DoshSource;
    }
    else{
        return(fhWin32File[DoshSource]);
    }

}
